dependabot-nuget 0.276.0 → 0.278.0

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