dependabot-nuget 0.265.0 → 0.267.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (43) hide show
  1. checksums.yaml +4 -4
  2. data/helpers/lib/NuGetUpdater/NuGetUpdater.Cli/Commands/UpdateCommand.cs +6 -6
  3. data/helpers/lib/NuGetUpdater/NuGetUpdater.Cli.Test/EntryPointTests.Analyze.cs +173 -0
  4. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core/Analyze/AnalysisResult.cs +1 -1
  5. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core/Analyze/AnalyzeWorker.cs +92 -79
  6. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core/Analyze/CompatabilityChecker.cs +21 -8
  7. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core/Analyze/NuGetContext.cs +36 -9
  8. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core/Analyze/Requirement.cs +88 -45
  9. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core/Analyze/VersionFinder.cs +33 -16
  10. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core/Discover/DiscoveryWorker.cs +44 -25
  11. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core/Discover/WorkspaceDiscoveryResult.cs +1 -1
  12. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core/ErrorType.cs +9 -0
  13. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core/Files/ProjectBuildFile.cs +2 -2
  14. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core/MissingFileException.cs +11 -0
  15. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core/NativeResult.cs +8 -0
  16. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core/Updater/BindingRedirectManager.cs +45 -42
  17. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core/Updater/PackagesConfigUpdater.cs +19 -1
  18. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core/Updater/SdkPackageUpdater.cs +2 -1
  19. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core/Updater/UpdateOperationResult.cs +5 -0
  20. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core/Updater/UpdaterWorker.cs +70 -22
  21. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core/Utilities/MSBuildHelper.cs +29 -1
  22. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core.Test/Analyze/AnalyzeWorkerTestBase.cs +6 -2
  23. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core.Test/Analyze/AnalyzeWorkerTests.cs +450 -0
  24. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core.Test/Analyze/CompatibilityCheckerTests.cs +23 -0
  25. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core.Test/Analyze/RequirementTests.cs +1 -0
  26. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core.Test/Discover/DiscoveryWorkerTestBase.cs +2 -0
  27. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core.Test/Discover/DiscoveryWorkerTests.cs +148 -0
  28. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core.Test/Discover/ExpectedDiscoveryResults.cs +1 -1
  29. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core.Test/MockNuGetPackage.cs +17 -22
  30. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core.Test/TestHttpServer.cs +81 -0
  31. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core.Test/Update/UpdateWorkerTestBase.cs +27 -7
  32. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core.Test/Update/UpdateWorkerTests.Mixed.cs +32 -0
  33. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core.Test/Update/UpdateWorkerTests.PackagesConfig.cs +447 -2
  34. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core.Test/Update/UpdateWorkerTests.Sdk.cs +88 -0
  35. data/lib/dependabot/nuget/analysis/dependency_analysis.rb +3 -0
  36. data/lib/dependabot/nuget/file_fetcher.rb +30 -11
  37. data/lib/dependabot/nuget/file_updater.rb +2 -0
  38. data/lib/dependabot/nuget/metadata_finder.rb +160 -2
  39. data/lib/dependabot/nuget/native_discovery/native_workspace_discovery.rb +3 -0
  40. data/lib/dependabot/nuget/native_helpers.rb +36 -3
  41. data/lib/dependabot/nuget/native_update_checker/native_update_checker.rb +1 -0
  42. data/lib/dependabot/nuget/nuget_config_credential_helpers.rb +3 -0
  43. metadata +12 -7
@@ -1,3 +1,8 @@
1
+ using System.Text;
2
+ using System.Text.Json;
3
+
4
+ using NuGet;
5
+
1
6
  using NuGetUpdater.Core.Analyze;
2
7
 
3
8
  using Xunit;
@@ -301,4 +306,449 @@ public partial class AnalyzeWorkerTests : AnalyzeWorkerTestBase
301
306
  }
302
307
  );
303
308
  }
309
+
310
+ [Fact]
311
+ public async Task VersionFinderCanHandle404FromPackageSource_V2()
312
+ {
313
+ static (int, byte[]) TestHttpHandler1(string uriString)
314
+ {
315
+ // this is a valid nuget package source, but doesn't contain anything
316
+ var uri = new Uri(uriString, UriKind.Absolute);
317
+ var baseUrl = $"{uri.Scheme}://{uri.Host}:{uri.Port}";
318
+ return uri.PathAndQuery switch
319
+ {
320
+ "/api/v2/" => (200, Encoding.UTF8.GetBytes($"""
321
+ <service xmlns="http://www.w3.org/2007/app" xmlns:atom="http://www.w3.org/2005/Atom" xml:base="{baseUrl}/api/v2">
322
+ <workspace>
323
+ <atom:title type="text">Default</atom:title>
324
+ <collection href="Packages">
325
+ <atom:title type="text">Packages</atom:title>
326
+ </collection>
327
+ </workspace>
328
+ </service>
329
+ """)),
330
+ _ => (404, Encoding.UTF8.GetBytes("{}")), // nothing else is found
331
+ };
332
+ }
333
+ var desktopAppRefPackage = MockNuGetPackage.WellKnownReferencePackage("Microsoft.WindowsDesktop.App", "net8.0");
334
+ (int, byte[]) TestHttpHandler2(string uriString)
335
+ {
336
+ // this contains the actual package
337
+ var uri = new Uri(uriString, UriKind.Absolute);
338
+ var baseUrl = $"{uri.Scheme}://{uri.Host}:{uri.Port}";
339
+ switch (uri.PathAndQuery)
340
+ {
341
+ case "/api/v2/":
342
+ return (200, Encoding.UTF8.GetBytes($"""
343
+ <service xmlns="http://www.w3.org/2007/app" xmlns:atom="http://www.w3.org/2005/Atom" xml:base="{baseUrl}/api/v2">
344
+ <workspace>
345
+ <atom:title type="text">Default</atom:title>
346
+ <collection href="Packages">
347
+ <atom:title type="text">Packages</atom:title>
348
+ </collection>
349
+ </workspace>
350
+ </service>
351
+ """));
352
+ case "/api/v2/FindPackagesById()?id='Some.Package'&semVerLevel=2.0.0":
353
+ return (200, Encoding.UTF8.GetBytes($"""
354
+ <feed xml:base="{baseUrl}/api/v2" xmlns="http://www.w3.org/2005/Atom"
355
+ xmlns:d="http://schemas.microsoft.com/ado/2007/08/dataservices"
356
+ xmlns:m="http://schemas.microsoft.com/ado/2007/08/dataservices/metadata"
357
+ xmlns:georss="http://www.georss.org/georss" xmlns:gml="http://www.opengis.net/gml">
358
+ <m:count>2</m:count>
359
+ <id>http://schemas.datacontract.org/2004/07/</id>
360
+ <title />
361
+ <updated>{DateTime.UtcNow:O}</updated>
362
+ <link rel="self" href="{baseUrl}/api/v2/Packages" />
363
+ <entry>
364
+ <id>{baseUrl}/api/v2/Packages(Id='Some.Package',Version='1.0.0')</id>
365
+ <content type="application/zip" src="{baseUrl}/api/v2/package/Some.Package/1.0.0" />
366
+ <m:properties>
367
+ <d:Version>1.0.0</d:Version>
368
+ </m:properties>
369
+ </entry>
370
+ <entry>
371
+ <id>{baseUrl}/api/v2/Packages(Id='Some.Package',Version='1.2.3')</id>
372
+ <content type="application/zip" src="{baseUrl}/api/v2/package/Some.Package/1.2.3" />
373
+ <m:properties>
374
+ <d:Version>1.2.3</d:Version>
375
+ </m:properties>
376
+ </entry>
377
+ </feed>
378
+ """));
379
+ case "/api/v2/Packages(Id='Some.Package',Version='1.2.3')":
380
+ return (200, Encoding.UTF8.GetBytes($"""
381
+ <entry xml:base="{baseUrl}/api/v2" xmlns="http://www.w3.org/2005/Atom"
382
+ xmlns:d="http://schemas.microsoft.com/ado/2007/08/dataservices"
383
+ xmlns:m="http://schemas.microsoft.com/ado/2007/08/dataservices/metadata"
384
+ xmlns:georss="http://www.georss.org/georss" xmlns:gml="http://www.opengis.net/gml">
385
+ <id>{baseUrl}/api/v2/Packages(Id='Some.Package',Version='1.2.3')</id>
386
+ <updated>{DateTime.UtcNow:O}</updated>
387
+ <content type="application/zip" src="{baseUrl}/api/v2/package/Some.Package/1.2.3" />
388
+ <m:properties>
389
+ <d:Version>1.2.3</d:Version>
390
+ </m:properties>
391
+ </entry>
392
+ """));
393
+ case "/api/v2/package/Some.Package/1.0.0":
394
+ return (200, MockNuGetPackage.CreateSimplePackage("Some.Package", "1.0.0", "net8.0").GetZipStream().ReadAllBytes());
395
+ case "/api/v2/package/Some.Package/1.2.3":
396
+ return (200, MockNuGetPackage.CreateSimplePackage("Some.Package", "1.2.3", "net8.0").GetZipStream().ReadAllBytes());
397
+ case "/api/v2/FindPackagesById()?id='Microsoft.WindowsDesktop.App.Ref'&semVerLevel=2.0.0":
398
+ return (200, Encoding.UTF8.GetBytes($"""
399
+ <feed xml:base="{baseUrl}/api/v2" xmlns="http://www.w3.org/2005/Atom"
400
+ xmlns:d="http://schemas.microsoft.com/ado/2007/08/dataservices"
401
+ xmlns:m="http://schemas.microsoft.com/ado/2007/08/dataservices/metadata"
402
+ xmlns:georss="http://www.georss.org/georss" xmlns:gml="http://www.opengis.net/gml">
403
+ <m:count>1</m:count>
404
+ <id>http://schemas.datacontract.org/2004/07/</id>
405
+ <title />
406
+ <updated>{DateTime.UtcNow:O}</updated>
407
+ <link rel="self" href="{baseUrl}/api/v2/Packages" />
408
+ <entry>
409
+ <id>{baseUrl}/api/v2/Packages(Id='Microsoft.WindowsDesktop.App.Ref',Version='{desktopAppRefPackage.Version}')</id>
410
+ <content type="application/zip" src="{baseUrl}/api/v2/package/Microsoft.WindowsDesktop.App.Ref/{desktopAppRefPackage.Version}" />
411
+ <m:properties>
412
+ <d:Version>{desktopAppRefPackage.Version}</d:Version>
413
+ </m:properties>
414
+ </entry>
415
+ </feed>
416
+ """));
417
+ default:
418
+ if (uri.PathAndQuery == $"/api/v2/package/Microsoft.WindowsDesktop.App.Ref/{desktopAppRefPackage.Version}")
419
+ {
420
+ return (200, desktopAppRefPackage.GetZipStream().ReadAllBytes());
421
+ }
422
+
423
+ // nothing else is found
424
+ return (404, Encoding.UTF8.GetBytes("{}"));
425
+ };
426
+ }
427
+ using var http1 = TestHttpServer.CreateTestServer(TestHttpHandler1);
428
+ using var http2 = TestHttpServer.CreateTestServer(TestHttpHandler2);
429
+ await TestAnalyzeAsync(
430
+ extraFiles:
431
+ [
432
+ ("NuGet.Config", $"""
433
+ <configuration>
434
+ <packageSources>
435
+ <clear />
436
+ <add key="package_feed_1" value="{http1.BaseUrl.TrimEnd('/')}/api/v2/" allowInsecureConnections="true" />
437
+ <add key="package_feed_2" value="{http2.BaseUrl.TrimEnd('/')}/api/v2/" allowInsecureConnections="true" />
438
+ </packageSources>
439
+ </configuration>
440
+ """)
441
+ ],
442
+ discovery: new()
443
+ {
444
+ Path = "/",
445
+ Projects =
446
+ [
447
+ new()
448
+ {
449
+ FilePath = "./project.csproj",
450
+ TargetFrameworks = ["net8.0"],
451
+ Dependencies =
452
+ [
453
+ new("Some.Package", "1.0.0", DependencyType.PackageReference),
454
+ ]
455
+ }
456
+ ]
457
+ },
458
+ dependencyInfo: new()
459
+ {
460
+ Name = "Some.Package",
461
+ Version = "1.0.0",
462
+ IgnoredVersions = [],
463
+ IsVulnerable = false,
464
+ Vulnerabilities = [],
465
+ },
466
+ expectedResult: new()
467
+ {
468
+ UpdatedVersion = "1.2.3",
469
+ CanUpdate = true,
470
+ VersionComesFromMultiDependencyProperty = false,
471
+ UpdatedDependencies =
472
+ [
473
+ new("Some.Package", "1.2.3", DependencyType.Unknown, TargetFrameworks: ["net8.0"]),
474
+ ],
475
+ }
476
+ );
477
+ }
478
+
479
+ [Fact]
480
+ public async Task VersionFinderCanHandle404FromPackageSource_V3()
481
+ {
482
+ static (int, byte[]) TestHttpHandler1(string uriString)
483
+ {
484
+ // this is a valid nuget package source, but doesn't contain anything
485
+ var uri = new Uri(uriString, UriKind.Absolute);
486
+ var baseUrl = $"{uri.Scheme}://{uri.Host}:{uri.Port}";
487
+ return uri.PathAndQuery switch
488
+ {
489
+ "/index.json" => (200, Encoding.UTF8.GetBytes($$"""
490
+ {
491
+ "version": "3.0.0",
492
+ "resources": [
493
+ {
494
+ "@id": "{{baseUrl}}/download",
495
+ "@type": "PackageBaseAddress/3.0.0"
496
+ },
497
+ {
498
+ "@id": "{{baseUrl}}/query",
499
+ "@type": "SearchQueryService"
500
+ },
501
+ {
502
+ "@id": "{{baseUrl}}/registrations",
503
+ "@type": "RegistrationsBaseUrl"
504
+ }
505
+ ]
506
+ }
507
+ """)),
508
+ _ => (404, Encoding.UTF8.GetBytes("{}")), // nothing else is found
509
+ };
510
+ }
511
+ var desktopAppRefPackage = MockNuGetPackage.WellKnownReferencePackage("Microsoft.WindowsDesktop.App", "net8.0");
512
+ (int, byte[]) TestHttpHandler2(string uriString)
513
+ {
514
+ // this contains the actual package
515
+ var uri = new Uri(uriString, UriKind.Absolute);
516
+ var baseUrl = $"{uri.Scheme}://{uri.Host}:{uri.Port}";
517
+ switch (uri.PathAndQuery)
518
+ {
519
+ case "/index.json":
520
+ return (200, Encoding.UTF8.GetBytes($$"""
521
+ {
522
+ "version": "3.0.0",
523
+ "resources": [
524
+ {
525
+ "@id": "{{baseUrl}}/download",
526
+ "@type": "PackageBaseAddress/3.0.0"
527
+ },
528
+ {
529
+ "@id": "{{baseUrl}}/query",
530
+ "@type": "SearchQueryService"
531
+ },
532
+ {
533
+ "@id": "{{baseUrl}}/registrations",
534
+ "@type": "RegistrationsBaseUrl"
535
+ }
536
+ ]
537
+ }
538
+ """));
539
+ case "/registrations/some.package/index.json":
540
+ return (200, Encoding.UTF8.GetBytes("""
541
+ {
542
+ "count": 1,
543
+ "items": [
544
+ {
545
+ "lower": "1.0.0",
546
+ "upper": "1.2.3",
547
+ "items": [
548
+ {
549
+ "catalogEntry": {
550
+ "listed": true,
551
+ "version": "1.0.0"
552
+ }
553
+ },
554
+ {
555
+ "catalogEntry": {
556
+ "listed": true,
557
+ "version": "1.2.3"
558
+ }
559
+ }
560
+ ]
561
+ }
562
+ ]
563
+ }
564
+ """));
565
+ case "/download/some.package/index.json":
566
+ return (200, Encoding.UTF8.GetBytes("""
567
+ {
568
+ "versions": [
569
+ "1.0.0",
570
+ "1.2.3"
571
+ ]
572
+ }
573
+ """));
574
+ case "/download/microsoft.windowsdesktop.app.ref/index.json":
575
+ return (200, Encoding.UTF8.GetBytes($$"""
576
+ {
577
+ "versions": [
578
+ "{{desktopAppRefPackage.Version}}"
579
+ ]
580
+ }
581
+ """));
582
+ case "/download/some.package/1.0.0/some.package.1.0.0.nupkg":
583
+ return (200, MockNuGetPackage.CreateSimplePackage("Some.Package", "1.0.0", "net8.0").GetZipStream().ReadAllBytes());
584
+ case "/download/some.package/1.2.3/some.package.1.2.3.nupkg":
585
+ return (200, MockNuGetPackage.CreateSimplePackage("Some.Package", "1.2.3", "net8.0").GetZipStream().ReadAllBytes());
586
+ default:
587
+ if (uri.PathAndQuery == $"/download/microsoft.windowsdesktop.app.ref/{desktopAppRefPackage.Version}/microsoft.windowsdesktop.app.ref.{desktopAppRefPackage.Version}.nupkg")
588
+ {
589
+ return (200, desktopAppRefPackage.GetZipStream().ReadAllBytes());
590
+ }
591
+
592
+ // nothing else is found
593
+ return (404, Encoding.UTF8.GetBytes("{}"));
594
+ };
595
+ }
596
+ using var http1 = TestHttpServer.CreateTestServer(TestHttpHandler1);
597
+ using var http2 = TestHttpServer.CreateTestServer(TestHttpHandler2);
598
+ await TestAnalyzeAsync(
599
+ extraFiles:
600
+ [
601
+ ("NuGet.Config", $"""
602
+ <configuration>
603
+ <packageSources>
604
+ <clear />
605
+ <add key="package_feed_1" value="{http1.BaseUrl.TrimEnd('/')}/index.json" allowInsecureConnections="true" />
606
+ <add key="package_feed_2" value="{http2.BaseUrl.TrimEnd('/')}/index.json" allowInsecureConnections="true" />
607
+ </packageSources>
608
+ </configuration>
609
+ """)
610
+ ],
611
+ discovery: new()
612
+ {
613
+ Path = "/",
614
+ Projects =
615
+ [
616
+ new()
617
+ {
618
+ FilePath = "./project.csproj",
619
+ TargetFrameworks = ["net8.0"],
620
+ Dependencies =
621
+ [
622
+ new("Some.Package", "1.0.0", DependencyType.PackageReference),
623
+ ]
624
+ }
625
+ ]
626
+ },
627
+ dependencyInfo: new()
628
+ {
629
+ Name = "Some.Package",
630
+ Version = "1.0.0",
631
+ IgnoredVersions = [],
632
+ IsVulnerable = false,
633
+ Vulnerabilities = [],
634
+ },
635
+ expectedResult: new()
636
+ {
637
+ UpdatedVersion = "1.2.3",
638
+ CanUpdate = true,
639
+ VersionComesFromMultiDependencyProperty = false,
640
+ UpdatedDependencies =
641
+ [
642
+ new("Some.Package", "1.2.3", DependencyType.Unknown, TargetFrameworks: ["net8.0"]),
643
+ ],
644
+ }
645
+ );
646
+ }
647
+
648
+ [Fact]
649
+ public async Task ResultFileHasCorrectShapeForAuthenticationFailure()
650
+ {
651
+ using var temporaryDirectory = await TemporaryDirectory.CreateWithContentsAsync([]);
652
+ await AnalyzeWorker.WriteResultsAsync(temporaryDirectory.DirectoryPath, "Some.Dependency", new()
653
+ {
654
+ ErrorType = ErrorType.AuthenticationFailure,
655
+ ErrorDetails = "<some package feed>",
656
+ UpdatedVersion = "",
657
+ UpdatedDependencies = [],
658
+ }, new Logger(false));
659
+ var discoveryContents = await File.ReadAllTextAsync(Path.Combine(temporaryDirectory.DirectoryPath, "Some.Dependency.json"));
660
+
661
+ // raw result file should look like this:
662
+ // {
663
+ // ...
664
+ // "ErrorType": "AuthenticationFailure",
665
+ // "ErrorDetails": "<some package feed>",
666
+ // ...
667
+ // }
668
+ var jsonDocument = JsonDocument.Parse(discoveryContents);
669
+ var errorType = jsonDocument.RootElement.GetProperty("ErrorType");
670
+ var errorDetails = jsonDocument.RootElement.GetProperty("ErrorDetails");
671
+
672
+ Assert.Equal("AuthenticationFailure", errorType.GetString());
673
+ Assert.Equal("<some package feed>", errorDetails.GetString());
674
+ }
675
+
676
+ [Fact]
677
+ public async Task ReportsPrivateSourceAuthenticationFailure()
678
+ {
679
+ static (int, string) TestHttpHandler(string uriString)
680
+ {
681
+ var uri = new Uri(uriString, UriKind.Absolute);
682
+ var baseUrl = $"{uri.Scheme}://{uri.Host}:{uri.Port}";
683
+ return uri.PathAndQuery switch
684
+ {
685
+ // initial request is good
686
+ "/index.json" => (200, $$"""
687
+ {
688
+ "version": "3.0.0",
689
+ "resources": [
690
+ {
691
+ "@id": "{{baseUrl}}/download",
692
+ "@type": "PackageBaseAddress/3.0.0"
693
+ },
694
+ {
695
+ "@id": "{{baseUrl}}/query",
696
+ "@type": "SearchQueryService"
697
+ },
698
+ {
699
+ "@id": "{{baseUrl}}/registrations",
700
+ "@type": "RegistrationsBaseUrl"
701
+ }
702
+ ]
703
+ }
704
+ """),
705
+ // all other requests are unauthorized
706
+ _ => (401, "{}"),
707
+ };
708
+ }
709
+ using var http = TestHttpServer.CreateTestStringServer(TestHttpHandler);
710
+ await TestAnalyzeAsync(
711
+ extraFiles:
712
+ [
713
+ ("NuGet.Config", $"""
714
+ <configuration>
715
+ <packageSources>
716
+ <clear />
717
+ <add key="private_feed" value="{http.BaseUrl.TrimEnd('/')}/index.json" allowInsecureConnections="true" />
718
+ </packageSources>
719
+ </configuration>
720
+ """)
721
+ ],
722
+ discovery: new()
723
+ {
724
+ Path = "/",
725
+ Projects = [
726
+ new()
727
+ {
728
+ FilePath = "./project.csproj",
729
+ TargetFrameworks = ["net8.0"],
730
+ Dependencies = [
731
+ new("Some.Package", "1.2.3", DependencyType.PackageReference),
732
+ ],
733
+ }
734
+ ]
735
+ },
736
+ dependencyInfo: new()
737
+ {
738
+ Name = "Some.Package",
739
+ Version = "1.2.3",
740
+ IgnoredVersions = [],
741
+ IsVulnerable = false,
742
+ Vulnerabilities = [],
743
+ },
744
+ expectedResult: new()
745
+ {
746
+ ErrorType = ErrorType.AuthenticationFailure,
747
+ ErrorDetails = $"({http.BaseUrl.TrimEnd('/')}/index.json)",
748
+ UpdatedVersion = string.Empty,
749
+ CanUpdate = false,
750
+ UpdatedDependencies = [],
751
+ }
752
+ );
753
+ }
304
754
  }
@@ -142,4 +142,27 @@ public class CompatibilityCheckerTests
142
142
 
143
143
  Assert.False(result);
144
144
  }
145
+
146
+ [Theory]
147
+ [InlineData("netstandard2.0")]
148
+ [InlineData("net472")]
149
+ [InlineData("net6.0")]
150
+ [InlineData("net7.0")]
151
+ [InlineData("net8.0")]
152
+ public void EverythingIsCompatibleWithAnyVersion0Framework(string projectFramework)
153
+ {
154
+ var package = new PackageIdentity("Dependency", NuGetVersion.Parse("1.0.0"));
155
+ ImmutableArray<NuGetFramework> projectFrameworks = [NuGetFramework.Parse(projectFramework)];
156
+ var isDevDependency = false;
157
+ ImmutableArray<NuGetFramework> packageFrameworks = [NuGetFramework.Parse("Any,Version=v0.0")];
158
+
159
+ var result = CompatibilityChecker.PerformCheck(
160
+ package,
161
+ projectFrameworks,
162
+ isDevDependency,
163
+ packageFrameworks,
164
+ new Logger(verbose: false));
165
+
166
+ Assert.True(result);
167
+ }
145
168
  }
@@ -32,6 +32,7 @@ public class RequirementTests
32
32
  [InlineData("2.0", "~> 1.0", false)]
33
33
  [InlineData("1", "~> 1", true)]
34
34
  [InlineData("2", "~> 1", false)]
35
+ [InlineData("5.3.8", "< 6, > 5.2.4", true)]
35
36
  public void IsSatisfiedBy(string versionString, string requirementString, bool expected)
36
37
  {
37
38
  var version = NuGetVersion.Parse(versionString);
@@ -40,6 +40,8 @@ public class DiscoveryWorkerTestBase
40
40
  ValidateResultWithDependencies(expectedResult.DotNetToolsJson, actualResult.DotNetToolsJson);
41
41
  ValidateProjectResults(expectedResult.Projects, actualResult.Projects);
42
42
  Assert.Equal(expectedResult.ExpectedProjectCount ?? expectedResult.Projects.Length, actualResult.Projects.Length);
43
+ Assert.Equal(expectedResult.ErrorType, actualResult.ErrorType);
44
+ Assert.Equal(expectedResult.ErrorDetails, actualResult.ErrorDetails);
43
45
 
44
46
  return;
45
47
 
@@ -1,3 +1,7 @@
1
+ using System.Text.Json;
2
+
3
+ using NuGetUpdater.Core.Discover;
4
+
1
5
  using Xunit;
2
6
 
3
7
  namespace NuGetUpdater.Core.Test.Discover;
@@ -55,6 +59,54 @@ public partial class DiscoveryWorkerTests : DiscoveryWorkerTestBase
55
59
  );
56
60
  }
57
61
 
62
+ [Fact]
63
+ public async Task TestDependencyWithTrailingSpacesInAttribute()
64
+ {
65
+ await TestDiscoveryAsync(
66
+ packages:
67
+ [
68
+ MockNuGetPackage.CreateSimplePackage("Some.Package", "9.0.1", "net8.0"),
69
+ ],
70
+ workspacePath: "src",
71
+ files: new[]
72
+ {
73
+ ("src/project.csproj", """
74
+ <Project Sdk="Microsoft.NET.Sdk">
75
+ <PropertyGroup>
76
+ <TargetFramework>net8.0</TargetFramework>
77
+ <SomePackageVersion>9.0.1</SomePackageVersion>
78
+ </PropertyGroup>
79
+
80
+ <ItemGroup>
81
+ <PackageReference Include=" Some.Package " Version="$(SomePackageVersion)" />
82
+ </ItemGroup>
83
+ </Project>
84
+ """)
85
+ },
86
+ expectedResult: new()
87
+ {
88
+ Path = "src",
89
+ Projects = [
90
+ new()
91
+ {
92
+ FilePath = "project.csproj",
93
+ TargetFrameworks = ["net8.0"],
94
+ ReferencedProjectPaths = [],
95
+ ExpectedDependencyCount = 2,
96
+ Dependencies = [
97
+ new("Microsoft.NET.Sdk", null, DependencyType.MSBuildSdk),
98
+ new("Some.Package", "9.0.1", DependencyType.PackageReference, TargetFrameworks: ["net8.0"], IsDirect: true)
99
+ ],
100
+ Properties = [
101
+ new("SomePackageVersion", "9.0.1", "src/project.csproj"),
102
+ new("TargetFramework", "net8.0", "src/project.csproj"),
103
+ ]
104
+ }
105
+ ]
106
+ }
107
+ );
108
+ }
109
+
58
110
  [Fact]
59
111
  public async Task TestPackageConfig()
60
112
  {
@@ -322,4 +374,100 @@ public partial class DiscoveryWorkerTests : DiscoveryWorkerTestBase
322
374
  }
323
375
  );
324
376
  }
377
+
378
+ [Fact]
379
+ public async Task ResultFileHasCorrectShapeForAuthenticationFailure()
380
+ {
381
+ using var temporaryDirectory = await TemporaryDirectory.CreateWithContentsAsync([]);
382
+ var discoveryResultPath = Path.Combine(temporaryDirectory.DirectoryPath, DiscoveryWorker.DiscoveryResultFileName);
383
+ await DiscoveryWorker.WriteResultsAsync(temporaryDirectory.DirectoryPath, discoveryResultPath, new()
384
+ {
385
+ ErrorType = ErrorType.AuthenticationFailure,
386
+ ErrorDetails = "<some package feed>",
387
+ Path = "/",
388
+ Projects = [],
389
+ });
390
+ var discoveryContents = await File.ReadAllTextAsync(discoveryResultPath);
391
+
392
+ // raw result file should look like this:
393
+ // {
394
+ // ...
395
+ // "ErrorType": "AuthenticationFailure",
396
+ // "ErrorDetails": "<some package feed>",
397
+ // ...
398
+ // }
399
+ var jsonDocument = JsonDocument.Parse(discoveryContents);
400
+ var errorType = jsonDocument.RootElement.GetProperty("ErrorType");
401
+ var errorDetails = jsonDocument.RootElement.GetProperty("ErrorDetails");
402
+
403
+ Assert.Equal("AuthenticationFailure", errorType.GetString());
404
+ Assert.Equal("<some package feed>", errorDetails.GetString());
405
+ }
406
+
407
+ [Fact]
408
+ public async Task ReportsPrivateSourceAuthenticationFailure()
409
+ {
410
+ static (int, string) TestHttpHandler(string uriString)
411
+ {
412
+ var uri = new Uri(uriString, UriKind.Absolute);
413
+ var baseUrl = $"{uri.Scheme}://{uri.Host}:{uri.Port}";
414
+ return uri.PathAndQuery switch
415
+ {
416
+ // initial request is good
417
+ "/index.json" => (200, $$"""
418
+ {
419
+ "version": "3.0.0",
420
+ "resources": [
421
+ {
422
+ "@id": "{{baseUrl}}/download",
423
+ "@type": "PackageBaseAddress/3.0.0"
424
+ },
425
+ {
426
+ "@id": "{{baseUrl}}/query",
427
+ "@type": "SearchQueryService"
428
+ },
429
+ {
430
+ "@id": "{{baseUrl}}/registrations",
431
+ "@type": "RegistrationsBaseUrl"
432
+ }
433
+ ]
434
+ }
435
+ """),
436
+ // all other requests are unauthorized
437
+ _ => (401, "{}"),
438
+ };
439
+ }
440
+ using var http = TestHttpServer.CreateTestStringServer(TestHttpHandler);
441
+ await TestDiscoveryAsync(
442
+ workspacePath: "",
443
+ files:
444
+ [
445
+ ("project.csproj", """
446
+ <Project Sdk="Microsoft.NET.Sdk">
447
+ <PropertyGroup>
448
+ <TargetFramework>net8.0</TargetFramework>
449
+ </PropertyGroup>
450
+ <ItemGroup>
451
+ <PackageReference Include="Some.Package" Version="1.2.3" />
452
+ </ItemGroup>
453
+ </Project>
454
+ """),
455
+ ("NuGet.Config", $"""
456
+ <configuration>
457
+ <packageSources>
458
+ <clear />
459
+ <add key="private_feed" value="{http.BaseUrl.TrimEnd('/')}/index.json" allowInsecureConnections="true" />
460
+ </packageSources>
461
+ </configuration>
462
+ """),
463
+ ],
464
+ expectedResult: new()
465
+ {
466
+ ErrorType = ErrorType.AuthenticationFailure,
467
+ ErrorDetails = $"({http.BaseUrl.TrimEnd('/')}/index.json)",
468
+ Path = "",
469
+ Projects = [],
470
+ }
471
+ );
472
+ }
325
473
  }