dependabot-nuget 0.277.0 → 0.279.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (58) hide show
  1. checksums.yaml +4 -4
  2. data/helpers/build +1 -1
  3. data/helpers/lib/NuGetUpdater/.editorconfig +1 -0
  4. data/helpers/lib/NuGetUpdater/Directory.Build.props +1 -0
  5. data/helpers/lib/NuGetUpdater/Directory.Common.props +1 -1
  6. data/helpers/lib/NuGetUpdater/Directory.Packages.props +6 -1
  7. data/helpers/lib/NuGetUpdater/NuGetUpdater.Cli/Commands/RunCommand.cs +42 -0
  8. data/helpers/lib/NuGetUpdater/NuGetUpdater.Cli/Program.cs +1 -0
  9. data/helpers/lib/NuGetUpdater/NuGetUpdater.Cli.Test/EntryPointTests.Run.cs +132 -0
  10. data/helpers/lib/NuGetUpdater/NuGetUpdater.Cli.Test/EntryPointTests.Update.cs +2 -3
  11. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core/Analyze/AnalyzeWorker.cs +94 -85
  12. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core/Analyze/DependencyFinder.cs +2 -2
  13. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core/Analyze/Requirement.cs +2 -2
  14. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core/Discover/DiscoveryWorker.cs +47 -41
  15. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core/NuGetUpdater.Core.csproj +2 -1
  16. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core/Run/ApiModel/AllowedUpdate.cs +6 -0
  17. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core/Run/ApiModel/CreatePullRequest.cs +18 -0
  18. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core/Run/ApiModel/DependencyFile.cs +18 -0
  19. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core/Run/ApiModel/DependencyFileNotFound.cs +6 -0
  20. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core/Run/ApiModel/IncrementMetric.cs +7 -0
  21. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core/Run/ApiModel/Job.cs +49 -0
  22. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core/Run/ApiModel/JobErrorBase.cs +11 -0
  23. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core/Run/ApiModel/JobFile.cs +6 -0
  24. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core/Run/ApiModel/JobSource.cs +11 -0
  25. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core/Run/ApiModel/MarkAsProcessed.cs +9 -0
  26. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core/Run/ApiModel/PrivateSourceAuthenticationFailure.cs +6 -0
  27. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core/Run/ApiModel/ReportedDependency.cs +16 -0
  28. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core/Run/ApiModel/ReportedRequirement.cs +9 -0
  29. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core/Run/ApiModel/RequirementSource.cs +7 -0
  30. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core/Run/ApiModel/UnknownError.cs +6 -0
  31. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core/Run/ApiModel/UpdatedDependencyList.cs +7 -0
  32. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core/Run/HttpApiHandler.cs +64 -0
  33. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core/Run/IApiHandler.cs +12 -0
  34. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core/Run/RunResult.cs +13 -0
  35. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core/Run/RunWorker.cs +328 -0
  36. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core/Updater/LockFileUpdater.cs +28 -0
  37. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core/Updater/SdkPackageUpdater.cs +1 -1
  38. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core/Updater/UpdaterWorker.cs +53 -37
  39. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core/Utilities/MSBuildHelper.cs +5 -5
  40. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core/Utilities/NuGetHelper.cs +1 -1
  41. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core/Utilities/PathHelper.cs +34 -0
  42. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core/Utilities/ProcessExtensions.cs +2 -4
  43. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core.Test/Analyze/RequirementTests.cs +4 -1
  44. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core.Test/MockNuGetPackage.cs +10 -1
  45. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core.Test/Run/RunWorkerTests.cs +315 -0
  46. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core.Test/Run/SerializationTests.cs +60 -0
  47. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core.Test/Run/TestApiHandler.cs +41 -0
  48. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core.Test/Run/UpdatedDependencyListTests.cs +69 -0
  49. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core.Test/Update/UpdateWorkerTestBase.cs +8 -8
  50. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core.Test/Update/UpdateWorkerTests.Sdk.cs +10 -1
  51. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core.Test/Utilities/PathHelperTests.cs +22 -0
  52. data/helpers/lib/NuGetUpdater/global.json +1 -1
  53. data/lib/dependabot/nuget/file_fetcher.rb +17 -0
  54. data/lib/dependabot/nuget/file_updater.rb +5 -1
  55. data/lib/dependabot/nuget/native_helpers.rb +4 -1
  56. data/lib/dependabot/nuget/requirement.rb +2 -0
  57. data/lib/dependabot/nuget/update_checker/repository_finder.rb +26 -2
  58. metadata +33 -5
@@ -25,48 +25,19 @@ public class UpdaterWorker
25
25
 
26
26
  public async Task RunAsync(string repoRootPath, string workspacePath, string dependencyName, string previousDependencyVersion, string newDependencyVersion, bool isTransitive, string? resultOutputPath = null)
27
27
  {
28
- MSBuildHelper.RegisterMSBuild(Environment.CurrentDirectory, repoRootPath);
29
- UpdateOperationResult result;
30
-
31
- if (!Path.IsPathRooted(workspacePath) || !File.Exists(workspacePath))
32
- {
33
- workspacePath = Path.GetFullPath(Path.Join(repoRootPath, workspacePath));
34
- }
35
-
28
+ UpdateOperationResult result = new(); // assumed to be ok until proven otherwise
36
29
  try
37
30
  {
38
- if (!isTransitive)
39
- {
40
- await DotNetToolsJsonUpdater.UpdateDependencyAsync(repoRootPath, workspacePath, dependencyName, previousDependencyVersion, newDependencyVersion, _logger);
41
- await GlobalJsonUpdater.UpdateDependencyAsync(repoRootPath, workspacePath, dependencyName, previousDependencyVersion, newDependencyVersion, _logger);
42
- }
43
-
44
- var extension = Path.GetExtension(workspacePath).ToLowerInvariant();
45
- switch (extension)
46
- {
47
- case ".sln":
48
- await RunForSolutionAsync(repoRootPath, workspacePath, dependencyName, previousDependencyVersion, newDependencyVersion, isTransitive);
49
- break;
50
- case ".proj":
51
- await RunForProjFileAsync(repoRootPath, workspacePath, dependencyName, previousDependencyVersion, newDependencyVersion, isTransitive);
52
- break;
53
- case ".csproj":
54
- case ".fsproj":
55
- case ".vbproj":
56
- await RunForProjectAsync(repoRootPath, workspacePath, dependencyName, previousDependencyVersion, newDependencyVersion, isTransitive);
57
- break;
58
- default:
59
- _logger.Log($"File extension [{extension}] is not supported.");
60
- break;
61
- }
62
-
63
- result = new(); // all ok
64
- _logger.Log("Update complete.");
31
+ result = await RunAsync(repoRootPath, workspacePath, dependencyName, previousDependencyVersion, newDependencyVersion, isTransitive);
65
32
  }
66
33
  catch (HttpRequestException ex)
67
34
  when (ex.StatusCode == HttpStatusCode.Unauthorized || ex.StatusCode == HttpStatusCode.Forbidden)
68
35
  {
69
- // TODO: consolidate this error handling between AnalyzeWorker, DiscoveryWorker, and UpdateWorker
36
+ if (!Path.IsPathRooted(workspacePath) || !File.Exists(workspacePath))
37
+ {
38
+ workspacePath = Path.GetFullPath(Path.Join(repoRootPath, workspacePath));
39
+ }
40
+
70
41
  result = new()
71
42
  {
72
43
  ErrorType = ErrorType.AuthenticationFailure,
@@ -82,13 +53,52 @@ public class UpdaterWorker
82
53
  };
83
54
  }
84
55
 
85
- _processedProjectPaths.Clear();
86
56
  if (resultOutputPath is { })
87
57
  {
88
58
  await WriteResultFile(result, resultOutputPath, _logger);
89
59
  }
90
60
  }
91
61
 
62
+ public async Task<UpdateOperationResult> RunAsync(string repoRootPath, string workspacePath, string dependencyName, string previousDependencyVersion, string newDependencyVersion, bool isTransitive)
63
+ {
64
+ MSBuildHelper.RegisterMSBuild(Environment.CurrentDirectory, repoRootPath);
65
+
66
+ if (!Path.IsPathRooted(workspacePath) || !File.Exists(workspacePath))
67
+ {
68
+ workspacePath = Path.GetFullPath(Path.Join(repoRootPath, workspacePath));
69
+ }
70
+
71
+ if (!isTransitive)
72
+ {
73
+ await DotNetToolsJsonUpdater.UpdateDependencyAsync(repoRootPath, workspacePath, dependencyName, previousDependencyVersion, newDependencyVersion, _logger);
74
+ await GlobalJsonUpdater.UpdateDependencyAsync(repoRootPath, workspacePath, dependencyName, previousDependencyVersion, newDependencyVersion, _logger);
75
+ }
76
+
77
+ var extension = Path.GetExtension(workspacePath).ToLowerInvariant();
78
+ switch (extension)
79
+ {
80
+ case ".sln":
81
+ await RunForSolutionAsync(repoRootPath, workspacePath, dependencyName, previousDependencyVersion, newDependencyVersion, isTransitive);
82
+ break;
83
+ case ".proj":
84
+ await RunForProjFileAsync(repoRootPath, workspacePath, dependencyName, previousDependencyVersion, newDependencyVersion, isTransitive);
85
+ break;
86
+ case ".csproj":
87
+ case ".fsproj":
88
+ case ".vbproj":
89
+ await RunForProjectAsync(repoRootPath, workspacePath, dependencyName, previousDependencyVersion, newDependencyVersion, isTransitive);
90
+ break;
91
+ default:
92
+ _logger.Log($"File extension [{extension}] is not supported.");
93
+ break;
94
+ }
95
+
96
+ _logger.Log("Update complete.");
97
+
98
+ _processedProjectPaths.Clear();
99
+ return new UpdateOperationResult();
100
+ }
101
+
92
102
  internal static async Task WriteResultFile(UpdateOperationResult result, string resultOutputPath, Logger logger)
93
103
  {
94
104
  logger.Log($" Writing update result to [{resultOutputPath}].");
@@ -189,5 +199,11 @@ public class UpdaterWorker
189
199
 
190
200
  // Some repos use a mix of packages.config and PackageReference
191
201
  await SdkPackageUpdater.UpdateDependencyAsync(repoRootPath, projectPath, dependencyName, previousDependencyVersion, newDependencyVersion, isTransitive, _logger);
202
+
203
+ // Update lock file if exists
204
+ if (File.Exists(Path.Combine(Path.GetDirectoryName(projectPath), "packages.lock.json")))
205
+ {
206
+ await LockFileUpdater.UpdateLockFileAsync(repoRootPath, projectPath, _logger);
207
+ }
192
208
  }
193
209
  }
@@ -322,7 +322,7 @@ internal static partial class MSBuildHelper
322
322
  try
323
323
  {
324
324
  var tempProjectPath = await CreateTempProjectAsync(tempDirectory, repoRoot, projectPath, targetFramework, packages);
325
- var (exitCode, stdOut, stdErr) = await ProcessEx.RunAsync("dotnet", $"restore \"{tempProjectPath}\"", workingDirectory: tempDirectory.FullName);
325
+ var (exitCode, stdOut, stdErr) = await ProcessEx.RunAsync("dotnet", ["restore", tempProjectPath], workingDirectory: tempDirectory.FullName);
326
326
 
327
327
  // NU1608: Detected package version outside of dependency constraint
328
328
 
@@ -359,7 +359,7 @@ internal static partial class MSBuildHelper
359
359
  try
360
360
  {
361
361
  string tempProjectPath = await CreateTempProjectAsync(tempDirectory, repoRoot, projectPath, targetFramework, packages);
362
- var (exitCode, stdOut, stdErr) = await ProcessEx.RunAsync("dotnet", $"restore \"{tempProjectPath}\"", workingDirectory: tempDirectory.FullName);
362
+ var (exitCode, stdOut, stdErr) = await ProcessEx.RunAsync("dotnet", ["restore", tempProjectPath], workingDirectory: tempDirectory.FullName);
363
363
 
364
364
  // Add Dependency[] packages to List<PackageToUpdate> existingPackages
365
365
  List<PackageToUpdate> existingPackages = packages
@@ -515,7 +515,7 @@ internal static partial class MSBuildHelper
515
515
  try
516
516
  {
517
517
  var tempProjectPath = await CreateTempProjectAsync(tempDirectory, repoRoot, projectPath, targetFramework, packages);
518
- var (exitCode, stdOut, stdErr) = await ProcessEx.RunAsync("dotnet", $"restore \"{tempProjectPath}\"", workingDirectory: tempDirectory.FullName);
518
+ var (exitCode, stdOut, stdErr) = await ProcessEx.RunAsync("dotnet", ["restore", tempProjectPath], workingDirectory: tempDirectory.FullName);
519
519
  ThrowOnUnauthenticatedFeed(stdOut);
520
520
 
521
521
  // simple cases first
@@ -540,7 +540,7 @@ internal static partial class MSBuildHelper
540
540
  foreach ((string PackageName, NuGetVersion packageVersion) in badPackagesAndVersions)
541
541
  {
542
542
  // this command dumps a JSON object with all versions of the specified package from all package sources
543
- (exitCode, stdOut, stdErr) = await ProcessEx.RunAsync("dotnet", $"package search {PackageName} --exact-match --format json", workingDirectory: tempDirectory.FullName);
543
+ (exitCode, stdOut, stdErr) = await ProcessEx.RunAsync("dotnet", ["package", "search", PackageName, "--exact-match", "--format", "json"], workingDirectory: tempDirectory.FullName);
544
544
  if (exitCode != 0)
545
545
  {
546
546
  continue;
@@ -770,7 +770,7 @@ internal static partial class MSBuildHelper
770
770
  var topLevelPackagesNames = packages.Select(p => p.Name).ToHashSet(StringComparer.OrdinalIgnoreCase);
771
771
  var tempProjectPath = await CreateTempProjectAsync(tempDirectory, repoRoot, projectPath, targetFramework, packages);
772
772
 
773
- var (exitCode, stdout, stderr) = await ProcessEx.RunAsync("dotnet", $"build \"{tempProjectPath}\" /t:_ReportDependencies", workingDirectory: tempDirectory.FullName);
773
+ var (exitCode, stdout, stderr) = await ProcessEx.RunAsync("dotnet", ["build", tempProjectPath, "/t:_ReportDependencies"], workingDirectory: tempDirectory.FullName);
774
774
  ThrowOnUnauthenticatedFeed(stdout);
775
775
 
776
776
  if (exitCode == 0)
@@ -26,7 +26,7 @@ internal static class NuGetHelper
26
26
  try
27
27
  {
28
28
  var tempProjectPath = await MSBuildHelper.CreateTempProjectAsync(tempDirectory, repoRoot, projectPath, "netstandard2.0", packages, usePackageDownload: true);
29
- var (exitCode, stdOut, stdErr) = await ProcessEx.RunAsync("dotnet", $"restore \"{tempProjectPath}\"");
29
+ var (exitCode, stdOut, stdErr) = await ProcessEx.RunAsync("dotnet", ["restore", tempProjectPath]);
30
30
 
31
31
  return exitCode == 0;
32
32
  }
@@ -25,8 +25,42 @@ internal static class PathHelper
25
25
  : Path.Combine(path1, path2);
26
26
  }
27
27
 
28
+ public static string EnsurePrefix(this string s, string prefix) => s.StartsWith(prefix) ? s : prefix + s;
29
+
28
30
  public static string NormalizePathToUnix(this string path) => path.Replace("\\", "/");
29
31
 
32
+ public static string NormalizeUnixPathParts(this string path)
33
+ {
34
+ var parts = path.Split('/');
35
+ var resultantParts = new List<string>();
36
+ foreach (var part in parts)
37
+ {
38
+ switch (part)
39
+ {
40
+ case "":
41
+ case ".":
42
+ break;
43
+ case "..":
44
+ if (resultantParts.Count > 0)
45
+ {
46
+ resultantParts.RemoveAt(resultantParts.Count - 1);
47
+ }
48
+ break;
49
+ default:
50
+ resultantParts.Add(part);
51
+ break;
52
+ }
53
+ }
54
+
55
+ var result = string.Join("/", resultantParts);
56
+ if (path.StartsWith("/") && !result.StartsWith("/"))
57
+ {
58
+ result = "/" + result;
59
+ }
60
+
61
+ return result;
62
+ }
63
+
30
64
  public static string GetFullPathFromRelative(string rootPath, string relativePath)
31
65
  => Path.GetFullPath(JoinPath(rootPath, relativePath.NormalizePathToUnix()));
32
66
 
@@ -5,17 +5,15 @@ namespace NuGetUpdater.Core;
5
5
 
6
6
  public static class ProcessEx
7
7
  {
8
- public static Task<(int ExitCode, string Output, string Error)> RunAsync(string fileName, string arguments = "", string? workingDirectory = null)
8
+ public static Task<(int ExitCode, string Output, string Error)> RunAsync(string fileName, IEnumerable<string>? arguments = null, string? workingDirectory = null)
9
9
  {
10
10
  var tcs = new TaskCompletionSource<(int, string, string)>();
11
11
 
12
12
  var redirectInitiated = new ManualResetEventSlim();
13
13
  var process = new Process
14
14
  {
15
- StartInfo =
15
+ StartInfo = new ProcessStartInfo(fileName, arguments ?? [])
16
16
  {
17
- FileName = fileName,
18
- Arguments = arguments,
19
17
  UseShellExecute = false, // required to redirect output
20
18
  RedirectStandardOutput = true,
21
19
  RedirectStandardError = true,
@@ -46,11 +46,14 @@ public class RequirementTests
46
46
  }
47
47
 
48
48
  [Theory]
49
+ [InlineData("> *", "> 0")] // standard wildcard, no digits
49
50
  [InlineData("> 1.*", "> 1.0")] // standard wildcard, single digit
50
51
  [InlineData("> 1.2.*", "> 1.2.0")] // standard wildcard, multiple digit
52
+ [InlineData("> a", "> 0")] // alternate wildcard, no digits
51
53
  [InlineData("> 1.a", "> 1.0")] // alternate wildcard, single digit
52
54
  [InlineData("> 1.2.a", "> 1.2.0")] // alternate wildcard, multiple digit
53
- public void Parse_ConvertsWildcardInVersion(string givenRequirementString, string expectedRequirementString)
55
+ [InlineData(">= 1.40.0, ", ">= 1.40.0")] // empty string following comma
56
+ public void Parse_Requirement(string givenRequirementString, string expectedRequirementString)
54
57
  {
55
58
  var parsedRequirement = Requirement.Parse(givenRequirementString);
56
59
  var actualRequirementString = parsedRequirement.ToString();
@@ -315,7 +315,7 @@ namespace NuGetUpdater.Core.Test
315
315
  </Project>
316
316
  """
317
317
  );
318
- var (exitCode, stdout, stderr) = ProcessEx.RunAsync("dotnet", $"msbuild {projectPath} /t:_ReportCurrentSdkVersion").Result;
318
+ var (exitCode, stdout, stderr) = ProcessEx.RunAsync("dotnet", ["msbuild", projectPath, "/t:_ReportCurrentSdkVersion"]).Result;
319
319
  if (exitCode != 0)
320
320
  {
321
321
  throw new Exception($"Failed to report the current SDK version:\n{stdout}\n{stderr}");
@@ -391,6 +391,7 @@ namespace NuGetUpdater.Core.Test
391
391
  WellKnownReferencePackage("Microsoft.AspNetCore.App", "net6.0"),
392
392
  WellKnownReferencePackage("Microsoft.AspNetCore.App", "net7.0"),
393
393
  WellKnownReferencePackage("Microsoft.AspNetCore.App", "net8.0"),
394
+ WellKnownReferencePackage("Microsoft.AspNetCore.App", "net9.0"),
394
395
  WellKnownReferencePackage("Microsoft.NETCore.App", "net6.0",
395
396
  [
396
397
  ("data/FrameworkList.xml", Encoding.UTF8.GetBytes("""
@@ -412,9 +413,17 @@ namespace NuGetUpdater.Core.Test
412
413
  </FileList>
413
414
  """))
414
415
  ]),
416
+ WellKnownReferencePackage("Microsoft.NETCore.App", "net9.0",
417
+ [
418
+ ("data/FrameworkList.xml", Encoding.UTF8.GetBytes("""
419
+ <FileList TargetFrameworkIdentifier=".NETCoreApp" TargetFrameworkVersion="9.0" FrameworkName="Microsoft.NETCore.App" Name=".NET Runtime">
420
+ </FileList>
421
+ """))
422
+ ]),
415
423
  WellKnownReferencePackage("Microsoft.WindowsDesktop.App", "net6.0"),
416
424
  WellKnownReferencePackage("Microsoft.WindowsDesktop.App", "net7.0"),
417
425
  WellKnownReferencePackage("Microsoft.WindowsDesktop.App", "net8.0"),
426
+ WellKnownReferencePackage("Microsoft.WindowsDesktop.App", "net9.0"),
418
427
  ];
419
428
  }
420
429
  }
@@ -0,0 +1,315 @@
1
+ using System.Text;
2
+ using System.Text.Json;
3
+ using System.Xml.Linq;
4
+
5
+ using NuGetUpdater.Core.Run;
6
+ using NuGetUpdater.Core.Run.ApiModel;
7
+ using NuGetUpdater.Core.Test.Update;
8
+
9
+ using Xunit;
10
+
11
+ namespace NuGetUpdater.Core.Test.Run;
12
+
13
+ using TestFile = (string Path, string Content);
14
+
15
+ public class RunWorkerTests
16
+ {
17
+ [Fact]
18
+ public async Task UpdateSinglePackageProducedExpectedAPIMessages()
19
+ {
20
+ var repoMetadata = XElement.Parse("""<repository type="git" url="https://nuget.example.com/some-package" />""");
21
+ await RunAsync(
22
+ packages:
23
+ [
24
+ MockNuGetPackage.CreateSimplePackage("Some.Package", "1.0.0", "net8.0", additionalMetadata: [repoMetadata]),
25
+ MockNuGetPackage.CreateSimplePackage("Some.Package", "1.0.1", "net8.0", additionalMetadata: [repoMetadata]),
26
+ ],
27
+ job: new Job()
28
+ {
29
+ PackageManager = "nuget",
30
+ Source = new()
31
+ {
32
+ Provider = "github",
33
+ Repo = "test/repo",
34
+ Directory = "some-dir",
35
+ },
36
+ AllowedUpdates =
37
+ [
38
+ new() { UpdateType = "all" }
39
+ ]
40
+ },
41
+ files:
42
+ [
43
+ ("some-dir/project.csproj", """
44
+ <Project Sdk="Microsoft.NET.Sdk">
45
+ <PropertyGroup>
46
+ <TargetFramework>net8.0</TargetFramework>
47
+ </PropertyGroup>
48
+ <ItemGroup>
49
+ <PackageReference Include="Some.Package" Version="1.0.0" />
50
+ </ItemGroup>
51
+ </Project>
52
+ """)
53
+ ],
54
+ expectedResult: new RunResult()
55
+ {
56
+ Base64DependencyFiles =
57
+ [
58
+ new DependencyFile()
59
+ {
60
+ Directory = "/some-dir",
61
+ Name = "project.csproj",
62
+ Content = Convert.ToBase64String(Encoding.UTF8.GetBytes("""
63
+ <Project Sdk="Microsoft.NET.Sdk">
64
+ <PropertyGroup>
65
+ <TargetFramework>net8.0</TargetFramework>
66
+ </PropertyGroup>
67
+ <ItemGroup>
68
+ <PackageReference Include="Some.Package" Version="1.0.0" />
69
+ </ItemGroup>
70
+ </Project>
71
+ """))
72
+ }
73
+ ],
74
+ BaseCommitSha = "TEST-COMMIT-SHA",
75
+ },
76
+ expectedApiMessages:
77
+ [
78
+ new UpdatedDependencyList()
79
+ {
80
+ Dependencies =
81
+ [
82
+ new ReportedDependency()
83
+ {
84
+ Name = "Some.Package",
85
+ Version = "1.0.0",
86
+ Requirements =
87
+ [
88
+ new ReportedRequirement()
89
+ {
90
+ Requirement = "1.0.0",
91
+ File = "/some-dir/project.csproj",
92
+ Groups = ["dependencies"],
93
+ }
94
+ ]
95
+ }
96
+ ],
97
+ DependencyFiles = ["/some-dir/project.csproj"],
98
+ },
99
+ new IncrementMetric()
100
+ {
101
+ Metric = "updater.started",
102
+ Tags = new()
103
+ {
104
+ ["operation"] = "group_update_all_versions"
105
+ }
106
+ },
107
+ new CreatePullRequest()
108
+ {
109
+ Dependencies =
110
+ [
111
+ new ReportedDependency()
112
+ {
113
+ Name = "Some.Package",
114
+ Version = "1.0.1",
115
+ Requirements =
116
+ [
117
+ new ReportedRequirement()
118
+ {
119
+ Requirement = "1.0.1",
120
+ File = "/some-dir/project.csproj",
121
+ Groups = ["dependencies"],
122
+ Source = new()
123
+ {
124
+ SourceUrl = "https://nuget.example.com/some-package",
125
+ Type = "nuget_repo",
126
+ }
127
+ }
128
+ ],
129
+ PreviousVersion = "1.0.0",
130
+ PreviousRequirements =
131
+ [
132
+ new ReportedRequirement()
133
+ {
134
+ Requirement = "1.0.0",
135
+ File = "/some-dir/project.csproj",
136
+ Groups = ["dependencies"],
137
+ }
138
+ ],
139
+ }
140
+ ],
141
+ UpdatedDependencyFiles =
142
+ [
143
+ new DependencyFile()
144
+ {
145
+ Name = "project.csproj",
146
+ Directory = "some-dir",
147
+ Content = """
148
+ <Project Sdk="Microsoft.NET.Sdk">
149
+ <PropertyGroup>
150
+ <TargetFramework>net8.0</TargetFramework>
151
+ </PropertyGroup>
152
+ <ItemGroup>
153
+ <PackageReference Include="Some.Package" Version="1.0.1" />
154
+ </ItemGroup>
155
+ </Project>
156
+ """,
157
+ },
158
+ ],
159
+ BaseCommitSha = "TEST-COMMIT-SHA",
160
+ CommitMessage = "TODO: message",
161
+ PrTitle = "TODO: title",
162
+ PrBody = "TODO: body",
163
+ },
164
+ new MarkAsProcessed()
165
+ {
166
+ BaseCommitSha = "TEST-COMMIT-SHA",
167
+ }
168
+ ]
169
+ );
170
+ }
171
+
172
+ [Fact]
173
+ public async Task PrivateSourceAuthenticationFailureIsForwaredToApiHandler()
174
+ {
175
+ static (int, string) TestHttpHandler(string uriString)
176
+ {
177
+ var uri = new Uri(uriString, UriKind.Absolute);
178
+ var baseUrl = $"{uri.Scheme}://{uri.Host}:{uri.Port}";
179
+ return uri.PathAndQuery switch
180
+ {
181
+ // initial request is good
182
+ "/index.json" => (200, $$"""
183
+ {
184
+ "version": "3.0.0",
185
+ "resources": [
186
+ {
187
+ "@id": "{{baseUrl}}/download",
188
+ "@type": "PackageBaseAddress/3.0.0"
189
+ },
190
+ {
191
+ "@id": "{{baseUrl}}/query",
192
+ "@type": "SearchQueryService"
193
+ },
194
+ {
195
+ "@id": "{{baseUrl}}/registrations",
196
+ "@type": "RegistrationsBaseUrl"
197
+ }
198
+ ]
199
+ }
200
+ """),
201
+ // all other requests are unauthorized
202
+ _ => (401, "{}"),
203
+ };
204
+ }
205
+ using var http = TestHttpServer.CreateTestStringServer(TestHttpHandler);
206
+ await RunAsync(
207
+ packages:
208
+ [
209
+ ],
210
+ job: new Job()
211
+ {
212
+ PackageManager = "nuget",
213
+ Source = new()
214
+ {
215
+ Provider = "github",
216
+ Repo = "test/repo",
217
+ Directory = "/",
218
+ },
219
+ AllowedUpdates =
220
+ [
221
+ new() { UpdateType = "all" }
222
+ ]
223
+ },
224
+ files:
225
+ [
226
+ ("NuGet.Config", $"""
227
+ <configuration>
228
+ <packageSources>
229
+ <clear />
230
+ <add key="private_feed" value="{http.BaseUrl.TrimEnd('/')}/index.json" allowInsecureConnections="true" />
231
+ </packageSources>
232
+ </configuration>
233
+ """),
234
+ ("project.csproj", """
235
+ <Project Sdk="Microsoft.NET.Sdk">
236
+ <PropertyGroup>
237
+ <TargetFramework>net8.0</TargetFramework>
238
+ </PropertyGroup>
239
+ <ItemGroup>
240
+ <PackageReference Include="Some.Package" Version="1.0.0" />
241
+ </ItemGroup>
242
+ </Project>
243
+ """)
244
+ ],
245
+ expectedResult: new RunResult()
246
+ {
247
+ Base64DependencyFiles = [],
248
+ BaseCommitSha = "TEST-COMMIT-SHA",
249
+ },
250
+ expectedApiMessages:
251
+ [
252
+ new PrivateSourceAuthenticationFailure()
253
+ {
254
+ Details = $"({http.BaseUrl.TrimEnd('/')}/index.json)"
255
+ },
256
+ new MarkAsProcessed()
257
+ {
258
+ BaseCommitSha = "TEST-COMMIT-SHA",
259
+ }
260
+ ]
261
+ );
262
+ }
263
+
264
+ private static async Task RunAsync(Job job, TestFile[] files, RunResult expectedResult, object[] expectedApiMessages, MockNuGetPackage[]? packages = null)
265
+ {
266
+ // arrange
267
+ using var tempDirectory = new TemporaryDirectory();
268
+ await UpdateWorkerTestBase.MockNuGetPackagesInDirectory(packages, tempDirectory.DirectoryPath);
269
+ foreach (var (path, content) in files)
270
+ {
271
+ var fullPath = Path.Combine(tempDirectory.DirectoryPath, path);
272
+ var directory = Path.GetDirectoryName(fullPath)!;
273
+ Directory.CreateDirectory(directory);
274
+ await File.WriteAllTextAsync(fullPath, content);
275
+ }
276
+
277
+ // act
278
+ var testApiHandler = new TestApiHandler();
279
+ var worker = new RunWorker(testApiHandler, new Logger(verbose: false));
280
+ var repoContentsPath = new DirectoryInfo(tempDirectory.DirectoryPath);
281
+ var actualResult = await worker.RunAsync(job, repoContentsPath, "TEST-COMMIT-SHA");
282
+ var actualApiMessages = testApiHandler.ReceivedMessages.ToArray();
283
+
284
+ // assert
285
+ var actualRunResultJson = JsonSerializer.Serialize(actualResult);
286
+ var expectedRunResultJson = JsonSerializer.Serialize(expectedResult);
287
+ Assert.Equal(expectedRunResultJson, actualRunResultJson);
288
+ for (int i = 0; i < Math.Min(actualApiMessages.Length, expectedApiMessages.Length); i++)
289
+ {
290
+ var actualMessage = actualApiMessages[i];
291
+ var expectedMessage = expectedApiMessages[i];
292
+ Assert.Equal(expectedMessage.GetType(), actualMessage.Type);
293
+
294
+ var expectedContent = SerializeObjectAndType(expectedMessage);
295
+ var actualContent = SerializeObjectAndType(actualMessage.Object);
296
+ Assert.Equal(expectedContent, actualContent);
297
+ }
298
+
299
+ if (actualApiMessages.Length > expectedApiMessages.Length)
300
+ {
301
+ var extraApiMessages = actualApiMessages.Skip(expectedApiMessages.Length).Select(m => SerializeObjectAndType(m.Object)).ToArray();
302
+ Assert.Fail($"Expected {expectedApiMessages.Length} API messages, but got {extraApiMessages.Length} extra:\n\t{string.Join("\n\t", extraApiMessages)}");
303
+ }
304
+ if (expectedApiMessages.Length > actualApiMessages.Length)
305
+ {
306
+ var missingApiMessages = expectedApiMessages.Skip(actualApiMessages.Length).Select(m => SerializeObjectAndType(m)).ToArray();
307
+ Assert.Fail($"Expected {expectedApiMessages.Length} API messages, but only got {actualApiMessages.Length}; missing:\n\t{string.Join("\n\t", missingApiMessages)}");
308
+ }
309
+ }
310
+
311
+ private static string SerializeObjectAndType(object obj)
312
+ {
313
+ return $"{obj.GetType().Name}:{JsonSerializer.Serialize(obj)}";
314
+ }
315
+ }
@@ -0,0 +1,60 @@
1
+ using NuGetUpdater.Core.Run;
2
+
3
+ using Xunit;
4
+
5
+ namespace NuGetUpdater.Core.Test.Run;
6
+
7
+ public class SerializationTests
8
+ {
9
+ [Fact]
10
+ public void DeserializeJob()
11
+ {
12
+ var jobWrapper = RunWorker.Deserialize("""
13
+ {
14
+ "job": {
15
+ "package-manager": "nuget",
16
+ "allowed-updates": [
17
+ {
18
+ "update-type": "all"
19
+ }
20
+ ],
21
+ "debug": false,
22
+ "dependency-groups": [],
23
+ "dependencies": null,
24
+ "dependency-group-to-refresh": null,
25
+ "existing-pull-requests": [],
26
+ "existing-group-pull-requests": [],
27
+ "experiments": null,
28
+ "ignore-conditions": [],
29
+ "lockfile-only": false,
30
+ "requirements-update-strategy": null,
31
+ "security-advisories": [],
32
+ "security-updates-only": false,
33
+ "source": {
34
+ "provider": "github",
35
+ "repo": "some-org/some-repo",
36
+ "directory": "specific-sdk",
37
+ "hostname": null,
38
+ "api-endpoint": null
39
+ },
40
+ "update-subdependencies": false,
41
+ "updating-a-pull-request": false,
42
+ "vendor-dependencies": false,
43
+ "reject-external-code": false,
44
+ "repo-private": false,
45
+ "commit-message-options": null,
46
+ "credentials-metadata": [
47
+ {
48
+ "host": "github.com",
49
+ "type": "git_source"
50
+ }
51
+ ],
52
+ "max-updater-run-time": 0
53
+ }
54
+ }
55
+ """);
56
+ Assert.Equal("github", jobWrapper.Job.Source.Provider);
57
+ Assert.Equal("some-org/some-repo", jobWrapper.Job.Source.Repo);
58
+ Assert.Equal("specific-sdk", jobWrapper.Job.Source.Directory);
59
+ }
60
+ }