dependabot-nuget 0.265.0 → 0.267.0

Sign up to get free protection for your applications and to get access to all the features.
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
  }