dependabot-nuget 0.276.0 → 0.278.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 (34) hide show
  1. checksums.yaml +4 -4
  2. data/helpers/lib/NuGetUpdater/NuGetUpdater.Cli/Commands/RunCommand.cs +42 -0
  3. data/helpers/lib/NuGetUpdater/NuGetUpdater.Cli/Program.cs +1 -0
  4. data/helpers/lib/NuGetUpdater/NuGetUpdater.Cli.Test/EntryPointTests.Run.cs +132 -0
  5. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core/Analyze/AnalyzeWorker.cs +11 -6
  6. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core/Analyze/DependencyFinder.cs +2 -2
  7. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core/Discover/DiscoveryWorker.cs +9 -4
  8. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core/NuGetUpdater.Core.csproj +2 -1
  9. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core/Run/ApiModel/AllowedUpdate.cs +6 -0
  10. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core/Run/ApiModel/CreatePullRequest.cs +18 -0
  11. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core/Run/ApiModel/DependencyFile.cs +18 -0
  12. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core/Run/ApiModel/IncrementMetric.cs +7 -0
  13. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core/Run/ApiModel/Job.cs +49 -0
  14. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core/Run/ApiModel/JobFile.cs +6 -0
  15. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core/Run/ApiModel/JobSource.cs +11 -0
  16. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core/Run/ApiModel/MarkAsProcessed.cs +9 -0
  17. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core/Run/ApiModel/ReportedDependency.cs +16 -0
  18. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core/Run/ApiModel/ReportedRequirement.cs +9 -0
  19. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core/Run/ApiModel/RequirementSource.cs +7 -0
  20. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core/Run/ApiModel/UpdatedDependencyList.cs +7 -0
  21. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core/Run/HttpApiHandler.cs +59 -0
  22. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core/Run/IApiHandler.cs +11 -0
  23. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core/Run/RunResult.cs +13 -0
  24. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core/Run/RunWorker.cs +283 -0
  25. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core/Updater/LockFileUpdater.cs +28 -0
  26. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core/Updater/UpdaterWorker.cs +16 -4
  27. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core/Utilities/PathHelper.cs +34 -0
  28. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core.Test/Run/RunWorkerTests.cs +223 -0
  29. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core.Test/Run/SerializationTests.cs +60 -0
  30. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core.Test/Run/TestApiHandler.cs +35 -0
  31. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core.Test/Run/UpdatedDependencyListTests.cs +69 -0
  32. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core.Test/Utilities/PathHelperTests.cs +22 -0
  33. data/lib/dependabot/nuget/file_fetcher.rb +17 -0
  34. metadata +29 -5
@@ -0,0 +1,283 @@
1
+ using System.Text;
2
+ using System.Text.Json;
3
+ using System.Text.Json.Serialization;
4
+
5
+ using NuGetUpdater.Core.Analyze;
6
+ using NuGetUpdater.Core.Discover;
7
+ using NuGetUpdater.Core.Run.ApiModel;
8
+
9
+ namespace NuGetUpdater.Core.Run;
10
+
11
+ public class RunWorker
12
+ {
13
+ private readonly IApiHandler _apiHandler;
14
+ private readonly Logger _logger;
15
+
16
+ internal static readonly JsonSerializerOptions SerializerOptions = new()
17
+ {
18
+ PropertyNamingPolicy = JsonNamingPolicy.KebabCaseLower,
19
+ WriteIndented = true,
20
+ Converters = { new JsonStringEnumConverter() },
21
+ };
22
+
23
+ public RunWorker(IApiHandler apiHandler, Logger logger)
24
+ {
25
+ _apiHandler = apiHandler;
26
+ _logger = logger;
27
+ }
28
+
29
+ public async Task RunAsync(FileInfo jobFilePath, DirectoryInfo repoContentsPath, string baseCommitSha, FileInfo outputFilePath)
30
+ {
31
+ var jobFileContent = await File.ReadAllTextAsync(jobFilePath.FullName);
32
+ var jobWrapper = Deserialize(jobFileContent);
33
+ var result = await RunAsync(jobWrapper.Job, repoContentsPath, baseCommitSha);
34
+ var resultJson = JsonSerializer.Serialize(result, SerializerOptions);
35
+ await File.WriteAllTextAsync(outputFilePath.FullName, resultJson);
36
+ }
37
+
38
+ public async Task<RunResult> RunAsync(Job job, DirectoryInfo repoContentsPath, string baseCommitSha)
39
+ {
40
+ MSBuildHelper.RegisterMSBuild(repoContentsPath.FullName, repoContentsPath.FullName);
41
+
42
+ var allDependencyFiles = new Dictionary<string, DependencyFile>();
43
+ foreach (var directory in job.GetAllDirectories())
44
+ {
45
+ var result = await RunForDirectory(job, repoContentsPath, directory, baseCommitSha);
46
+ foreach (var dependencyFile in result.Base64DependencyFiles)
47
+ {
48
+ allDependencyFiles[dependencyFile.Name] = dependencyFile;
49
+ }
50
+ }
51
+
52
+ var runResult = new RunResult()
53
+ {
54
+ Base64DependencyFiles = allDependencyFiles.Values.ToArray(),
55
+ BaseCommitSha = baseCommitSha,
56
+ };
57
+ return runResult;
58
+ }
59
+
60
+ private async Task<RunResult> RunForDirectory(Job job, DirectoryInfo repoContentsPath, string repoDirectory, string baseCommitSha)
61
+ {
62
+ var discoveryWorker = new DiscoveryWorker(_logger);
63
+ var discoveryResult = await discoveryWorker.RunAsync(repoContentsPath.FullName, repoDirectory);
64
+ // TODO: check discoveryResult.ErrorType
65
+
66
+ _logger.Log("Discovery JSON content:");
67
+ _logger.Log(JsonSerializer.Serialize(discoveryResult, DiscoveryWorker.SerializerOptions));
68
+
69
+ // report dependencies
70
+ var discoveredUpdatedDependencies = GetUpdatedDependencyListFromDiscovery(discoveryResult);
71
+ await _apiHandler.UpdateDependencyList(discoveredUpdatedDependencies);
72
+
73
+ // TODO: pull out relevant dependencies, then check each for updates and track the changes
74
+ // TODO: for each top-level dependency, _or_ specific dependency (if security, use transitive)
75
+ var originalDependencyFileContents = new Dictionary<string, string>();
76
+ var allowedUpdates = job.AllowedUpdates ?? [];
77
+ var actualUpdatedDependencies = new List<ReportedDependency>();
78
+ if (allowedUpdates.Any(a => a.UpdateType == "all"))
79
+ {
80
+ await _apiHandler.IncrementMetric(new()
81
+ {
82
+ Metric = "updater.started",
83
+ Tags = { ["operation"] = "group_update_all_versions" },
84
+ });
85
+
86
+ // track original contents for later handling
87
+ foreach (var project in discoveryResult.Projects)
88
+ {
89
+ // TODO: include global.json, etc.
90
+ var path = Path.Join(discoveryResult.Path, project.FilePath).NormalizePathToUnix().EnsurePrefix("/");
91
+ var localPath = Path.Join(repoContentsPath.FullName, discoveryResult.Path, project.FilePath);
92
+ var content = await File.ReadAllTextAsync(localPath);
93
+ originalDependencyFileContents[path] = content;
94
+ }
95
+
96
+ // do update
97
+ _logger.Log($"Running update in directory {repoDirectory}");
98
+ foreach (var project in discoveryResult.Projects)
99
+ {
100
+ foreach (var dependency in project.Dependencies.Where(d => !d.IsTransitive))
101
+ {
102
+ if (dependency.Name == "Microsoft.NET.Sdk")
103
+ {
104
+ // this can't be updated
105
+ // TODO: pull this out of discovery?
106
+ continue;
107
+ }
108
+
109
+ if (dependency.Version is null)
110
+ {
111
+ // if we don't know the version, there's nothing we can do
112
+ continue;
113
+ }
114
+
115
+ var analyzeWorker = new AnalyzeWorker(_logger);
116
+ var dependencyInfo = new DependencyInfo()
117
+ {
118
+ Name = dependency.Name,
119
+ Version = dependency.Version!,
120
+ IsVulnerable = false,
121
+ IgnoredVersions = [],
122
+ Vulnerabilities = [],
123
+ };
124
+ var analysisResult = await analyzeWorker.RunAsync(repoContentsPath.FullName, discoveryResult, dependencyInfo);
125
+ // TODO: log analysisResult
126
+ // TODO: check analysisResult.ErrorType
127
+ if (analysisResult.CanUpdate)
128
+ {
129
+ // TODO: this is inefficient, but not likely causing a bottleneck
130
+ var previousDependency = discoveredUpdatedDependencies.Dependencies
131
+ .Single(d => d.Name == dependency.Name && d.Requirements.Single().File == Path.Join(discoveryResult.Path, project.FilePath).NormalizePathToUnix().EnsurePrefix("/"));
132
+ var updatedDependency = new ReportedDependency()
133
+ {
134
+ Name = dependency.Name,
135
+ Version = analysisResult.UpdatedVersion,
136
+ Requirements =
137
+ [
138
+ new ReportedRequirement()
139
+ {
140
+ File = Path.Join(discoveryResult.Path, project.FilePath).NormalizePathToUnix().EnsurePrefix("/"),
141
+ Requirement = analysisResult.UpdatedVersion,
142
+ Groups = previousDependency.Requirements.Single().Groups,
143
+ Source = new RequirementSource()
144
+ {
145
+ SourceUrl = analysisResult.UpdatedDependencies.Single(d => d.Name == dependency.Name).InfoUrl,
146
+ },
147
+ }
148
+ ],
149
+ PreviousVersion = dependency.Version,
150
+ PreviousRequirements = previousDependency.Requirements,
151
+ };
152
+
153
+ var updateWorker = new UpdaterWorker(_logger);
154
+ var dependencyFilePath = Path.Join(discoveryResult.Path, project.FilePath).NormalizePathToUnix();
155
+ var updateResult = await updateWorker.RunAsync(repoContentsPath.FullName, dependencyFilePath, dependency.Name, dependency.Version!, analysisResult.UpdatedVersion, isTransitive: false);
156
+ // TODO: check specific contents of result.ErrorType
157
+ // TODO: need to report if anything was actually updated
158
+ if (updateResult.ErrorType is null || updateResult.ErrorType == ErrorType.None)
159
+ {
160
+ actualUpdatedDependencies.Add(updatedDependency);
161
+ }
162
+ }
163
+ }
164
+ }
165
+
166
+ // create PR - we need to manually check file contents; we can't easily use `git status` in tests
167
+ var updatedDependencyFiles = new List<DependencyFile>();
168
+ foreach (var project in discoveryResult.Projects)
169
+ {
170
+ var path = Path.Join(discoveryResult.Path, project.FilePath).NormalizePathToUnix().EnsurePrefix("/");
171
+ var localPath = Path.Join(repoContentsPath.FullName, discoveryResult.Path, project.FilePath);
172
+ var updatedContent = await File.ReadAllTextAsync(localPath);
173
+ var originalContent = originalDependencyFileContents[path];
174
+ if (updatedContent != originalContent)
175
+ {
176
+ updatedDependencyFiles.Add(new DependencyFile()
177
+ {
178
+ Name = project.FilePath,
179
+ Content = updatedContent,
180
+ Directory = discoveryResult.Path,
181
+ });
182
+ }
183
+ }
184
+
185
+ if (updatedDependencyFiles.Count > 0)
186
+ {
187
+ var createPullRequest = new CreatePullRequest()
188
+ {
189
+ Dependencies = actualUpdatedDependencies.ToArray(),
190
+ UpdatedDependencyFiles = updatedDependencyFiles.ToArray(),
191
+ BaseCommitSha = baseCommitSha,
192
+ CommitMessage = "TODO: message",
193
+ PrTitle = "TODO: title",
194
+ PrBody = "TODO: body",
195
+ };
196
+ await _apiHandler.CreatePullRequest(createPullRequest);
197
+ // TODO: log updated dependencies to console
198
+ }
199
+ else
200
+ {
201
+ // TODO: log or throw if nothing was updated, but was expected to be
202
+ }
203
+ }
204
+ else
205
+ {
206
+ // TODO: throw if no updates performed
207
+ }
208
+
209
+ await _apiHandler.MarkAsProcessed(new() { BaseCommitSha = baseCommitSha });
210
+ var result = new RunResult()
211
+ {
212
+ Base64DependencyFiles = originalDependencyFileContents.Select(kvp => new DependencyFile()
213
+ {
214
+ Name = Path.GetFileName(kvp.Key),
215
+ Content = Convert.ToBase64String(Encoding.UTF8.GetBytes(kvp.Value)),
216
+ Directory = Path.GetDirectoryName(kvp.Key)!.NormalizePathToUnix(),
217
+ }).ToArray(),
218
+ BaseCommitSha = baseCommitSha,
219
+ };
220
+ return result;
221
+ }
222
+
223
+ internal static UpdatedDependencyList GetUpdatedDependencyListFromDiscovery(WorkspaceDiscoveryResult discoveryResult)
224
+ {
225
+ string GetFullRepoPath(string path)
226
+ {
227
+ // ensures `path\to\file` is `/path/to/file`
228
+ return Path.Join(discoveryResult.Path, path).NormalizePathToUnix().NormalizeUnixPathParts().EnsurePrefix("/");
229
+ }
230
+
231
+ var auxiliaryFiles = new List<string>();
232
+ if (discoveryResult.GlobalJson is not null)
233
+ {
234
+ auxiliaryFiles.Add(GetFullRepoPath(discoveryResult.GlobalJson.FilePath));
235
+ }
236
+ if (discoveryResult.DotNetToolsJson is not null)
237
+ {
238
+ auxiliaryFiles.Add(GetFullRepoPath(discoveryResult.DotNetToolsJson.FilePath));
239
+ }
240
+ if (discoveryResult.DirectoryPackagesProps is not null)
241
+ {
242
+ auxiliaryFiles.Add(GetFullRepoPath(discoveryResult.DirectoryPackagesProps.FilePath));
243
+ }
244
+
245
+ var updatedDependencyList = new UpdatedDependencyList()
246
+ {
247
+ Dependencies = discoveryResult.Projects.SelectMany(p =>
248
+ {
249
+ return p.Dependencies.Where(d => d.Version is not null).Select(d =>
250
+ new ReportedDependency()
251
+ {
252
+ Name = d.Name,
253
+ Requirements = d.IsTransitive ? [] : [new ReportedRequirement()
254
+ {
255
+ File = GetFullRepoPath(p.FilePath),
256
+ Requirement = d.Version!,
257
+ Groups = ["dependencies"],
258
+ }],
259
+ Version = d.Version,
260
+ }
261
+ );
262
+ }).ToArray(),
263
+ DependencyFiles = discoveryResult.Projects.Select(p => GetFullRepoPath(p.FilePath)).Concat(auxiliaryFiles).ToArray(),
264
+ };
265
+ return updatedDependencyList;
266
+ }
267
+
268
+ public static JobFile Deserialize(string json)
269
+ {
270
+ var jobFile = JsonSerializer.Deserialize<JobFile>(json, SerializerOptions);
271
+ if (jobFile is null)
272
+ {
273
+ throw new InvalidOperationException("Unable to deserialize job wrapper.");
274
+ }
275
+
276
+ if (jobFile.Job.PackageManager != "nuget")
277
+ {
278
+ throw new InvalidOperationException("Package manager must be 'nuget'");
279
+ }
280
+
281
+ return jobFile;
282
+ }
283
+ }
@@ -0,0 +1,28 @@
1
+ namespace NuGetUpdater.Core;
2
+
3
+ internal static class LockFileUpdater
4
+ {
5
+ public static async Task UpdateLockFileAsync(
6
+ string repoRootPath,
7
+ string projectPath,
8
+ Logger logger)
9
+ {
10
+ var projectDirectory = Path.GetDirectoryName(projectPath);
11
+ var lockPath = Path.Combine(projectDirectory, "packages.lock.json");
12
+ logger.Log($" Updating lock file");
13
+ if (!File.Exists(lockPath))
14
+ {
15
+ logger.Log($" File [{Path.GetRelativePath(repoRootPath, lockPath)}] does not exist.");
16
+ return;
17
+ }
18
+
19
+ await MSBuildHelper.SidelineGlobalJsonAsync(projectDirectory, repoRootPath, async () =>
20
+ {
21
+ var (exitCode, stdout, stderr) = await ProcessEx.RunAsync("dotnet", $"restore --force-evaluate {projectPath}", workingDirectory: projectDirectory);
22
+ if (exitCode != 0)
23
+ {
24
+ logger.Log($" Lock file update failed.\nSTDOUT:\n{stdout}\nSTDERR:\n{stderr}");
25
+ }
26
+ }, retainMSBuildSdks: true);
27
+ }
28
+ }
@@ -24,6 +24,15 @@ public class UpdaterWorker
24
24
  }
25
25
 
26
26
  public async Task RunAsync(string repoRootPath, string workspacePath, string dependencyName, string previousDependencyVersion, string newDependencyVersion, bool isTransitive, string? resultOutputPath = null)
27
+ {
28
+ var result = await RunAsync(repoRootPath, workspacePath, dependencyName, previousDependencyVersion, newDependencyVersion, isTransitive);
29
+ if (resultOutputPath is { })
30
+ {
31
+ await WriteResultFile(result, resultOutputPath, _logger);
32
+ }
33
+ }
34
+
35
+ public async Task<UpdateOperationResult> RunAsync(string repoRootPath, string workspacePath, string dependencyName, string previousDependencyVersion, string newDependencyVersion, bool isTransitive)
27
36
  {
28
37
  MSBuildHelper.RegisterMSBuild(Environment.CurrentDirectory, repoRootPath);
29
38
  UpdateOperationResult result;
@@ -83,10 +92,7 @@ public class UpdaterWorker
83
92
  }
84
93
 
85
94
  _processedProjectPaths.Clear();
86
- if (resultOutputPath is { })
87
- {
88
- await WriteResultFile(result, resultOutputPath, _logger);
89
- }
95
+ return result;
90
96
  }
91
97
 
92
98
  internal static async Task WriteResultFile(UpdateOperationResult result, string resultOutputPath, Logger logger)
@@ -189,5 +195,11 @@ public class UpdaterWorker
189
195
 
190
196
  // Some repos use a mix of packages.config and PackageReference
191
197
  await SdkPackageUpdater.UpdateDependencyAsync(repoRootPath, projectPath, dependencyName, previousDependencyVersion, newDependencyVersion, isTransitive, _logger);
198
+
199
+ // Update lock file if exists
200
+ if (File.Exists(Path.Combine(Path.GetDirectoryName(projectPath), "packages.lock.json")))
201
+ {
202
+ await LockFileUpdater.UpdateLockFileAsync(repoRootPath, projectPath, _logger);
203
+ }
192
204
  }
193
205
  }
@@ -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
 
@@ -0,0 +1,223 @@
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
+ private static async Task RunAsync(Job job, TestFile[] files, RunResult expectedResult, object[] expectedApiMessages, MockNuGetPackage[]? packages = null)
173
+ {
174
+ // arrange
175
+ using var tempDirectory = new TemporaryDirectory();
176
+ await UpdateWorkerTestBase.MockNuGetPackagesInDirectory(packages, tempDirectory.DirectoryPath);
177
+ foreach (var (path, content) in files)
178
+ {
179
+ var fullPath = Path.Combine(tempDirectory.DirectoryPath, path);
180
+ var directory = Path.GetDirectoryName(fullPath)!;
181
+ Directory.CreateDirectory(directory);
182
+ await File.WriteAllTextAsync(fullPath, content);
183
+ }
184
+
185
+ // act
186
+ var testApiHandler = new TestApiHandler();
187
+ var worker = new RunWorker(testApiHandler, new Logger(verbose: false));
188
+ var repoContentsPath = new DirectoryInfo(tempDirectory.DirectoryPath);
189
+ var actualResult = await worker.RunAsync(job, repoContentsPath, "TEST-COMMIT-SHA");
190
+ var actualApiMessages = testApiHandler.ReceivedMessages.ToArray();
191
+
192
+ // assert
193
+ var actualRunResultJson = JsonSerializer.Serialize(actualResult);
194
+ var expectedRunResultJson = JsonSerializer.Serialize(expectedResult);
195
+ Assert.Equal(expectedRunResultJson, actualRunResultJson);
196
+ for (int i = 0; i < Math.Min(actualApiMessages.Length, expectedApiMessages.Length); i++)
197
+ {
198
+ var actualMessage = actualApiMessages[i];
199
+ var expectedMessage = expectedApiMessages[i];
200
+ Assert.Equal(expectedMessage.GetType(), actualMessage.Type);
201
+
202
+ var expectedContent = SerializeObjectAndType(expectedMessage);
203
+ var actualContent = SerializeObjectAndType(actualMessage.Object);
204
+ Assert.Equal(expectedContent, actualContent);
205
+ }
206
+
207
+ if (actualApiMessages.Length > expectedApiMessages.Length)
208
+ {
209
+ var extraApiMessages = actualApiMessages.Skip(expectedApiMessages.Length).Select(m => SerializeObjectAndType(m.Object)).ToArray();
210
+ Assert.Fail($"Expected {expectedApiMessages.Length} API messages, but got {extraApiMessages.Length} extra:\n\t{string.Join("\n\t", extraApiMessages)}");
211
+ }
212
+ if (expectedApiMessages.Length > actualApiMessages.Length)
213
+ {
214
+ var missingApiMessages = expectedApiMessages.Skip(actualApiMessages.Length).Select(m => SerializeObjectAndType(m)).ToArray();
215
+ Assert.Fail($"Expected {expectedApiMessages.Length} API messages, but only got {actualApiMessages.Length}; missing:\n\t{string.Join("\n\t", missingApiMessages)}");
216
+ }
217
+ }
218
+
219
+ private static string SerializeObjectAndType(object obj)
220
+ {
221
+ return $"{obj.GetType().Name}:{JsonSerializer.Serialize(obj)}";
222
+ }
223
+ }
@@ -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
+ }