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.
- checksums.yaml +4 -4
- data/helpers/lib/NuGetUpdater/NuGetUpdater.Cli/Commands/RunCommand.cs +42 -0
- data/helpers/lib/NuGetUpdater/NuGetUpdater.Cli/Program.cs +1 -0
- data/helpers/lib/NuGetUpdater/NuGetUpdater.Cli.Test/EntryPointTests.Run.cs +132 -0
- data/helpers/lib/NuGetUpdater/NuGetUpdater.Core/Analyze/AnalyzeWorker.cs +11 -6
- data/helpers/lib/NuGetUpdater/NuGetUpdater.Core/Analyze/DependencyFinder.cs +2 -2
- data/helpers/lib/NuGetUpdater/NuGetUpdater.Core/Discover/DiscoveryWorker.cs +9 -4
- data/helpers/lib/NuGetUpdater/NuGetUpdater.Core/NuGetUpdater.Core.csproj +2 -1
- data/helpers/lib/NuGetUpdater/NuGetUpdater.Core/Run/ApiModel/AllowedUpdate.cs +6 -0
- data/helpers/lib/NuGetUpdater/NuGetUpdater.Core/Run/ApiModel/CreatePullRequest.cs +18 -0
- data/helpers/lib/NuGetUpdater/NuGetUpdater.Core/Run/ApiModel/DependencyFile.cs +18 -0
- data/helpers/lib/NuGetUpdater/NuGetUpdater.Core/Run/ApiModel/IncrementMetric.cs +7 -0
- data/helpers/lib/NuGetUpdater/NuGetUpdater.Core/Run/ApiModel/Job.cs +49 -0
- data/helpers/lib/NuGetUpdater/NuGetUpdater.Core/Run/ApiModel/JobFile.cs +6 -0
- data/helpers/lib/NuGetUpdater/NuGetUpdater.Core/Run/ApiModel/JobSource.cs +11 -0
- data/helpers/lib/NuGetUpdater/NuGetUpdater.Core/Run/ApiModel/MarkAsProcessed.cs +9 -0
- data/helpers/lib/NuGetUpdater/NuGetUpdater.Core/Run/ApiModel/ReportedDependency.cs +16 -0
- data/helpers/lib/NuGetUpdater/NuGetUpdater.Core/Run/ApiModel/ReportedRequirement.cs +9 -0
- data/helpers/lib/NuGetUpdater/NuGetUpdater.Core/Run/ApiModel/RequirementSource.cs +7 -0
- data/helpers/lib/NuGetUpdater/NuGetUpdater.Core/Run/ApiModel/UpdatedDependencyList.cs +7 -0
- data/helpers/lib/NuGetUpdater/NuGetUpdater.Core/Run/HttpApiHandler.cs +59 -0
- data/helpers/lib/NuGetUpdater/NuGetUpdater.Core/Run/IApiHandler.cs +11 -0
- data/helpers/lib/NuGetUpdater/NuGetUpdater.Core/Run/RunResult.cs +13 -0
- data/helpers/lib/NuGetUpdater/NuGetUpdater.Core/Run/RunWorker.cs +283 -0
- data/helpers/lib/NuGetUpdater/NuGetUpdater.Core/Updater/LockFileUpdater.cs +28 -0
- data/helpers/lib/NuGetUpdater/NuGetUpdater.Core/Updater/UpdaterWorker.cs +16 -4
- data/helpers/lib/NuGetUpdater/NuGetUpdater.Core/Utilities/PathHelper.cs +34 -0
- data/helpers/lib/NuGetUpdater/NuGetUpdater.Core.Test/Run/RunWorkerTests.cs +223 -0
- data/helpers/lib/NuGetUpdater/NuGetUpdater.Core.Test/Run/SerializationTests.cs +60 -0
- data/helpers/lib/NuGetUpdater/NuGetUpdater.Core.Test/Run/TestApiHandler.cs +35 -0
- data/helpers/lib/NuGetUpdater/NuGetUpdater.Core.Test/Run/UpdatedDependencyListTests.cs +69 -0
- data/helpers/lib/NuGetUpdater/NuGetUpdater.Core.Test/Utilities/PathHelperTests.cs +22 -0
- data/lib/dependabot/nuget/file_fetcher.rb +17 -0
- 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
|
-
|
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
|
+
}
|