dependabot-nuget 0.294.0 → 0.295.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.Core/Discover/DiscoveryWorker.cs +2 -1
- data/helpers/lib/NuGetUpdater/NuGetUpdater.Core/Discover/SdkProjectDiscovery.cs +1 -1
- data/helpers/lib/NuGetUpdater/NuGetUpdater.Core/Run/RunWorker.cs +165 -123
- data/helpers/lib/NuGetUpdater/NuGetUpdater.Core/Updater/PackageReferenceUpdater.cs +1 -1
- data/helpers/lib/NuGetUpdater/NuGetUpdater.Core/Updater/PackagesConfigUpdater.cs +3 -6
- data/helpers/lib/NuGetUpdater/NuGetUpdater.Core/Utilities/MSBuildHelper.cs +36 -18
- data/helpers/lib/NuGetUpdater/NuGetUpdater.Core.Test/Run/RunWorkerTests.cs +338 -0
- data/helpers/lib/NuGetUpdater/NuGetUpdater.Core.Test/Run/UpdateAllowedTests.cs +286 -0
- data/helpers/lib/NuGetUpdater/NuGetUpdater.Core.Test/Run/UpdatedDependencyListTests.cs +9 -1
- data/helpers/lib/NuGetUpdater/NuGetUpdater.Core.Test/Utilities/MSBuildHelperTests.cs +88 -2
- metadata +6 -5
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 8ac3a95f5211adff79f8e7954308a4d3656b63c8be20b70a7b6f129f04402881
|
4
|
+
data.tar.gz: a79771c0cc6c0a08bfaa2a7cef8253902c7e8b92b868568c231064098ba77a1c
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 800097bc4856927e983da508d16b4e6d0fd75cf40b5ca5ed25901fbb467f0f99e8f8dc724341c0da6cc6126303c6437a5bd8c7521132c3363ed752181402ee1b
|
7
|
+
data.tar.gz: c0a1675e7cdf06ec0314d58092de09d633fa46892ae8f01c20ec74553d756265fda431434babb6eb140184b3380b78576f6309c1ec010f41e0e09e6ad6b28adb
|
@@ -262,7 +262,8 @@ public partial class DiscoveryWorker : IDiscoveryWorker
|
|
262
262
|
}
|
263
263
|
}
|
264
264
|
|
265
|
-
|
265
|
+
var result = expandedProjects.OrderBy(p => p).ToImmutableArray();
|
266
|
+
return result;
|
266
267
|
}
|
267
268
|
|
268
269
|
private static IEnumerable<string> ExpandItemGroupFilesFromProject(string projectPath, params string[] itemTypes)
|
@@ -114,7 +114,7 @@ internal static class SdkProjectDiscovery
|
|
114
114
|
var (exitCode, stdOut, stdErr) = await ProcessEx.RunDotnetWithoutMSBuildEnvironmentVariablesAsync(args, startingProjectDirectory, experimentsManager);
|
115
115
|
return (exitCode, stdOut, stdErr);
|
116
116
|
}, logger, retainMSBuildSdks: true);
|
117
|
-
MSBuildHelper.
|
117
|
+
MSBuildHelper.ThrowOnError(stdOut);
|
118
118
|
if (stdOut.Contains("""error MSB4057: The target "GenerateBuildDependencyFile" does not exist in the project."""))
|
119
119
|
{
|
120
120
|
// this can happen if it's a non-SDK-style project; totally normal, not worth examining the binlog
|
@@ -4,6 +4,8 @@ using System.Text;
|
|
4
4
|
using System.Text.Json;
|
5
5
|
using System.Text.Json.Serialization;
|
6
6
|
|
7
|
+
using Microsoft.Extensions.FileSystemGlobbing;
|
8
|
+
|
7
9
|
using NuGet.Versioning;
|
8
10
|
|
9
11
|
using NuGetUpdater.Core.Analyze;
|
@@ -115,161 +117,145 @@ public class RunWorker
|
|
115
117
|
await _apiHandler.UpdateDependencyList(discoveredUpdatedDependencies);
|
116
118
|
|
117
119
|
// TODO: pull out relevant dependencies, then check each for updates and track the changes
|
118
|
-
// TODO: for each top-level dependency, _or_ specific dependency (if security, use transitive)
|
119
120
|
var originalDependencyFileContents = new Dictionary<string, string>();
|
120
121
|
var actualUpdatedDependencies = new List<ReportedDependency>();
|
121
|
-
|
122
|
+
await _apiHandler.IncrementMetric(new()
|
122
123
|
{
|
123
|
-
|
124
|
-
{
|
125
|
-
|
126
|
-
|
127
|
-
|
124
|
+
Metric = "updater.started",
|
125
|
+
Tags = { ["operation"] = "group_update_all_versions" },
|
126
|
+
});
|
127
|
+
|
128
|
+
// track original contents for later handling
|
129
|
+
async Task TrackOriginalContentsAsync(string directory, string fileName)
|
130
|
+
{
|
131
|
+
var repoFullPath = Path.Join(directory, fileName).FullyNormalizedRootedPath();
|
132
|
+
var localFullPath = Path.Join(repoContentsPath.FullName, repoFullPath);
|
133
|
+
var content = await File.ReadAllTextAsync(localFullPath);
|
134
|
+
originalDependencyFileContents[repoFullPath] = content;
|
135
|
+
}
|
128
136
|
|
129
|
-
|
130
|
-
|
137
|
+
foreach (var project in discoveryResult.Projects)
|
138
|
+
{
|
139
|
+
var projectDirectory = Path.GetDirectoryName(project.FilePath);
|
140
|
+
await TrackOriginalContentsAsync(discoveryResult.Path, project.FilePath);
|
141
|
+
foreach (var extraFile in project.ImportedFiles.Concat(project.AdditionalFiles))
|
131
142
|
{
|
132
|
-
var
|
133
|
-
|
134
|
-
var content = await File.ReadAllTextAsync(localFullPath);
|
135
|
-
originalDependencyFileContents[repoFullPath] = content;
|
143
|
+
var extraFilePath = Path.Join(projectDirectory, extraFile);
|
144
|
+
await TrackOriginalContentsAsync(discoveryResult.Path, extraFilePath);
|
136
145
|
}
|
146
|
+
// TODO: include global.json, etc.
|
147
|
+
}
|
137
148
|
|
138
|
-
|
149
|
+
// do update
|
150
|
+
_logger.Info($"Running update in directory {repoDirectory}");
|
151
|
+
foreach (var project in discoveryResult.Projects)
|
152
|
+
{
|
153
|
+
foreach (var dependency in project.Dependencies)
|
139
154
|
{
|
140
|
-
|
141
|
-
await TrackOriginalContentsAsync(discoveryResult.Path, project.FilePath);
|
142
|
-
foreach (var extraFile in project.ImportedFiles.Concat(project.AdditionalFiles))
|
155
|
+
if (!IsUpdateAllowed(job, dependency))
|
143
156
|
{
|
144
|
-
|
145
|
-
await TrackOriginalContentsAsync(discoveryResult.Path, extraFilePath);
|
157
|
+
continue;
|
146
158
|
}
|
147
|
-
// TODO: include global.json, etc.
|
148
|
-
}
|
149
159
|
|
150
|
-
|
151
|
-
|
152
|
-
|
153
|
-
|
154
|
-
foreach (var dependency in project.Dependencies.Where(d => !d.IsTransitive))
|
160
|
+
var dependencyInfo = GetDependencyInfo(job, dependency);
|
161
|
+
var analysisResult = await _analyzeWorker.RunAsync(repoContentsPath.FullName, discoveryResult, dependencyInfo);
|
162
|
+
// TODO: log analysisResult
|
163
|
+
if (analysisResult.CanUpdate)
|
155
164
|
{
|
156
|
-
|
157
|
-
{
|
158
|
-
// this can't be updated
|
159
|
-
// TODO: pull this out of discovery?
|
160
|
-
continue;
|
161
|
-
}
|
162
|
-
|
163
|
-
if (dependency.Version is null)
|
164
|
-
{
|
165
|
-
// if we don't know the version, there's nothing we can do
|
166
|
-
continue;
|
167
|
-
}
|
165
|
+
var dependencyLocation = Path.Join(discoveryResult.Path, project.FilePath).FullyNormalizedRootedPath();
|
168
166
|
|
169
|
-
|
170
|
-
var
|
171
|
-
|
172
|
-
|
167
|
+
// TODO: this is inefficient, but not likely causing a bottleneck
|
168
|
+
var previousDependency = discoveredUpdatedDependencies.Dependencies
|
169
|
+
.Single(d => d.Name == dependency.Name && d.Requirements.Single().File == dependencyLocation);
|
170
|
+
var updatedDependency = new ReportedDependency()
|
173
171
|
{
|
174
|
-
|
175
|
-
|
176
|
-
|
177
|
-
|
178
|
-
|
179
|
-
var updatedDependency = new ReportedDependency()
|
180
|
-
{
|
181
|
-
Name = dependency.Name,
|
182
|
-
Version = analysisResult.UpdatedVersion,
|
183
|
-
Requirements =
|
184
|
-
[
|
185
|
-
new ReportedRequirement()
|
186
|
-
{
|
187
|
-
File = dependencyLocation,
|
188
|
-
Requirement = analysisResult.UpdatedVersion,
|
189
|
-
Groups = previousDependency.Requirements.Single().Groups,
|
190
|
-
Source = new RequirementSource()
|
191
|
-
{
|
192
|
-
SourceUrl = analysisResult.UpdatedDependencies.FirstOrDefault(d => d.Name == dependency.Name)?.InfoUrl,
|
193
|
-
},
|
194
|
-
}
|
195
|
-
],
|
196
|
-
PreviousVersion = dependency.Version,
|
197
|
-
PreviousRequirements = previousDependency.Requirements,
|
198
|
-
};
|
199
|
-
|
200
|
-
var dependencyFilePath = Path.Join(discoveryResult.Path, project.FilePath).FullyNormalizedRootedPath();
|
201
|
-
var updateResult = await _updaterWorker.RunAsync(repoContentsPath.FullName, dependencyFilePath, dependency.Name, dependency.Version!, analysisResult.UpdatedVersion, isTransitive: false);
|
202
|
-
// TODO: need to report if anything was actually updated
|
203
|
-
if (updateResult.Error is null)
|
204
|
-
{
|
205
|
-
if (dependencyLocation != dependencyFilePath)
|
172
|
+
Name = dependency.Name,
|
173
|
+
Version = analysisResult.UpdatedVersion,
|
174
|
+
Requirements =
|
175
|
+
[
|
176
|
+
new ReportedRequirement()
|
206
177
|
{
|
207
|
-
|
178
|
+
File = dependencyLocation,
|
179
|
+
Requirement = analysisResult.UpdatedVersion,
|
180
|
+
Groups = previousDependency.Requirements.Single().Groups,
|
181
|
+
Source = new RequirementSource()
|
182
|
+
{
|
183
|
+
SourceUrl = analysisResult.UpdatedDependencies.FirstOrDefault(d => d.Name == dependency.Name)?.InfoUrl,
|
184
|
+
},
|
208
185
|
}
|
186
|
+
],
|
187
|
+
PreviousVersion = dependency.Version,
|
188
|
+
PreviousRequirements = previousDependency.Requirements,
|
189
|
+
};
|
209
190
|
|
210
|
-
|
191
|
+
var dependencyFilePath = Path.Join(discoveryResult.Path, project.FilePath).FullyNormalizedRootedPath();
|
192
|
+
var updateResult = await _updaterWorker.RunAsync(repoContentsPath.FullName, dependencyFilePath, dependency.Name, dependency.Version!, analysisResult.UpdatedVersion, isTransitive: dependency.IsTransitive);
|
193
|
+
// TODO: need to report if anything was actually updated
|
194
|
+
if (updateResult.Error is null)
|
195
|
+
{
|
196
|
+
if (dependencyLocation != dependencyFilePath)
|
197
|
+
{
|
198
|
+
updatedDependency.Requirements.All(r => r.File == dependencyFilePath);
|
211
199
|
}
|
200
|
+
|
201
|
+
actualUpdatedDependencies.Add(updatedDependency);
|
212
202
|
}
|
213
203
|
}
|
214
204
|
}
|
205
|
+
}
|
215
206
|
|
216
|
-
|
217
|
-
|
218
|
-
|
207
|
+
// create PR - we need to manually check file contents; we can't easily use `git status` in tests
|
208
|
+
var updatedDependencyFiles = new Dictionary<string, DependencyFile>();
|
209
|
+
async Task AddUpdatedFileIfDifferentAsync(string directory, string fileName)
|
210
|
+
{
|
211
|
+
var repoFullPath = Path.Join(directory, fileName).FullyNormalizedRootedPath();
|
212
|
+
var localFullPath = Path.GetFullPath(Path.Join(repoContentsPath.FullName, repoFullPath));
|
213
|
+
var originalContent = originalDependencyFileContents[repoFullPath];
|
214
|
+
var updatedContent = await File.ReadAllTextAsync(localFullPath);
|
215
|
+
if (updatedContent != originalContent)
|
219
216
|
{
|
220
|
-
|
221
|
-
var localFullPath = Path.GetFullPath(Path.Join(repoContentsPath.FullName, repoFullPath));
|
222
|
-
var originalContent = originalDependencyFileContents[repoFullPath];
|
223
|
-
var updatedContent = await File.ReadAllTextAsync(localFullPath);
|
224
|
-
if (updatedContent != originalContent)
|
217
|
+
updatedDependencyFiles[localFullPath] = new DependencyFile()
|
225
218
|
{
|
226
|
-
|
227
|
-
|
228
|
-
|
229
|
-
|
230
|
-
Content = updatedContent,
|
231
|
-
};
|
232
|
-
}
|
219
|
+
Name = Path.GetFileName(repoFullPath),
|
220
|
+
Directory = Path.GetDirectoryName(repoFullPath)!.NormalizePathToUnix(),
|
221
|
+
Content = updatedContent,
|
222
|
+
};
|
233
223
|
}
|
224
|
+
}
|
234
225
|
|
235
|
-
|
226
|
+
foreach (var project in discoveryResult.Projects)
|
227
|
+
{
|
228
|
+
await AddUpdatedFileIfDifferentAsync(discoveryResult.Path, project.FilePath);
|
229
|
+
var projectDirectory = Path.GetDirectoryName(project.FilePath);
|
230
|
+
foreach (var extraFile in project.ImportedFiles.Concat(project.AdditionalFiles))
|
236
231
|
{
|
237
|
-
|
238
|
-
|
239
|
-
foreach (var extraFile in project.ImportedFiles.Concat(project.AdditionalFiles))
|
240
|
-
{
|
241
|
-
var extraFilePath = Path.Join(projectDirectory, extraFile);
|
242
|
-
await AddUpdatedFileIfDifferentAsync(discoveryResult.Path, extraFilePath);
|
243
|
-
}
|
244
|
-
// TODO: handle global.json, etc.
|
232
|
+
var extraFilePath = Path.Join(projectDirectory, extraFile);
|
233
|
+
await AddUpdatedFileIfDifferentAsync(discoveryResult.Path, extraFilePath);
|
245
234
|
}
|
235
|
+
// TODO: handle global.json, etc.
|
236
|
+
}
|
246
237
|
|
247
|
-
|
248
|
-
|
249
|
-
|
250
|
-
|
251
|
-
|
252
|
-
|
253
|
-
|
254
|
-
{
|
255
|
-
Dependencies = actualUpdatedDependencies.ToArray(),
|
256
|
-
UpdatedDependencyFiles = updatedDependencyFileList,
|
257
|
-
BaseCommitSha = baseCommitSha,
|
258
|
-
CommitMessage = "TODO: message",
|
259
|
-
PrTitle = "TODO: title",
|
260
|
-
PrBody = "TODO: body",
|
261
|
-
};
|
262
|
-
await _apiHandler.CreatePullRequest(createPullRequest);
|
263
|
-
// TODO: log updated dependencies to console
|
264
|
-
}
|
265
|
-
else
|
238
|
+
if (updatedDependencyFiles.Count > 0)
|
239
|
+
{
|
240
|
+
var updatedDependencyFileList = updatedDependencyFiles
|
241
|
+
.OrderBy(kvp => kvp.Key)
|
242
|
+
.Select(kvp => kvp.Value)
|
243
|
+
.ToArray();
|
244
|
+
var createPullRequest = new CreatePullRequest()
|
266
245
|
{
|
267
|
-
|
268
|
-
|
246
|
+
Dependencies = actualUpdatedDependencies.ToArray(),
|
247
|
+
UpdatedDependencyFiles = updatedDependencyFileList,
|
248
|
+
BaseCommitSha = baseCommitSha,
|
249
|
+
CommitMessage = "TODO: message",
|
250
|
+
PrTitle = "TODO: title",
|
251
|
+
PrBody = "TODO: body",
|
252
|
+
};
|
253
|
+
await _apiHandler.CreatePullRequest(createPullRequest);
|
254
|
+
// TODO: log updated dependencies to console
|
269
255
|
}
|
270
256
|
else
|
271
257
|
{
|
272
|
-
// TODO: throw if
|
258
|
+
// TODO: log or throw if nothing was updated, but was expected to be
|
273
259
|
}
|
274
260
|
|
275
261
|
var result = new RunResult()
|
@@ -289,6 +275,62 @@ public class RunWorker
|
|
289
275
|
return result;
|
290
276
|
}
|
291
277
|
|
278
|
+
internal static bool IsUpdateAllowed(Job job, Dependency dependency)
|
279
|
+
{
|
280
|
+
if (dependency.Name.Equals("Microsoft.NET.Sdk", StringComparison.OrdinalIgnoreCase))
|
281
|
+
{
|
282
|
+
// this can't be updated
|
283
|
+
// TODO: pull this out of discovery?
|
284
|
+
return false;
|
285
|
+
}
|
286
|
+
|
287
|
+
if (dependency.Version is null)
|
288
|
+
{
|
289
|
+
// if we don't know the version, there's nothing we can do
|
290
|
+
// TODO: pull this out of discovery?
|
291
|
+
return false;
|
292
|
+
}
|
293
|
+
|
294
|
+
var version = NuGetVersion.Parse(dependency.Version);
|
295
|
+
var dependencyInfo = GetDependencyInfo(job, dependency);
|
296
|
+
var isVulnerable = dependencyInfo.Vulnerabilities.Any(v => v.IsVulnerable(version));
|
297
|
+
var allowed = job.AllowedUpdates.Any(allowedUpdate =>
|
298
|
+
{
|
299
|
+
// check name restriction, if any
|
300
|
+
if (allowedUpdate.DependencyName is not null)
|
301
|
+
{
|
302
|
+
var matcher = new Matcher(StringComparison.OrdinalIgnoreCase)
|
303
|
+
.AddInclude(allowedUpdate.DependencyName);
|
304
|
+
var result = matcher.Match(dependency.Name);
|
305
|
+
if (!result.HasMatches)
|
306
|
+
{
|
307
|
+
return false;
|
308
|
+
}
|
309
|
+
}
|
310
|
+
|
311
|
+
var isSecurityUpdate = allowedUpdate.UpdateType == UpdateType.Security || job.SecurityUpdatesOnly;
|
312
|
+
if (isSecurityUpdate)
|
313
|
+
{
|
314
|
+
// only update if it's vulnerable
|
315
|
+
return isVulnerable;
|
316
|
+
}
|
317
|
+
else
|
318
|
+
{
|
319
|
+
// not a security update, so only update if...
|
320
|
+
// ...we've been explicitly asked to update this
|
321
|
+
if ((job.Dependencies ?? []).Any(d => d.Equals(dependency.Name, StringComparison.OrdinalIgnoreCase)))
|
322
|
+
{
|
323
|
+
return true;
|
324
|
+
}
|
325
|
+
|
326
|
+
// ...no specific update being performed, do it if it's not transitive
|
327
|
+
return !dependency.IsTransitive;
|
328
|
+
}
|
329
|
+
});
|
330
|
+
|
331
|
+
return allowed;
|
332
|
+
}
|
333
|
+
|
292
334
|
internal static ImmutableArray<Requirement> GetIgnoredRequirementsForDependency(Job job, string dependencyName)
|
293
335
|
{
|
294
336
|
var ignoreConditions = job.IgnoreConditions
|
@@ -375,7 +417,7 @@ public class RunWorker
|
|
375
417
|
new ReportedDependency()
|
376
418
|
{
|
377
419
|
Name = d.Name,
|
378
|
-
Requirements =
|
420
|
+
Requirements = [new ReportedRequirement()
|
379
421
|
{
|
380
422
|
File = GetFullRepoPath(p.FilePath),
|
381
423
|
Requirement = d.Version!,
|
@@ -347,7 +347,7 @@ internal static class PackageReferenceUpdater
|
|
347
347
|
projectDirectory,
|
348
348
|
experimentsManager
|
349
349
|
);
|
350
|
-
MSBuildHelper.
|
350
|
+
MSBuildHelper.ThrowOnError(stdout);
|
351
351
|
if (exitCode != 0)
|
352
352
|
{
|
353
353
|
logger.Warn($" Transitive dependency [{dependencyName}/{newDependencyVersion}] was not added.\nSTDOUT:\n{stdout}\nSTDERR:\n{stderr}");
|
@@ -148,18 +148,15 @@ internal static partial class PackagesConfigUpdater
|
|
148
148
|
|
149
149
|
if (exitCodeAgain != 0)
|
150
150
|
{
|
151
|
-
MSBuildHelper.
|
152
|
-
MSBuildHelper.
|
153
|
-
MSBuildHelper.ThrowOnMissingPackages(restoreOutput);
|
151
|
+
MSBuildHelper.ThrowOnError(fullOutput);
|
152
|
+
MSBuildHelper.ThrowOnError(restoreOutput);
|
154
153
|
throw new Exception($"Unable to restore.\nOutput:\n${restoreOutput}\n");
|
155
154
|
}
|
156
155
|
|
157
156
|
goto doRestore;
|
158
157
|
}
|
159
158
|
|
160
|
-
MSBuildHelper.
|
161
|
-
MSBuildHelper.ThrowOnMissingFile(fullOutput);
|
162
|
-
MSBuildHelper.ThrowOnMissingPackages(fullOutput);
|
159
|
+
MSBuildHelper.ThrowOnError(fullOutput);
|
163
160
|
throw new Exception(fullOutput);
|
164
161
|
}
|
165
162
|
}
|
@@ -903,21 +903,6 @@ internal static partial class MSBuildHelper
|
|
903
903
|
}
|
904
904
|
}
|
905
905
|
|
906
|
-
internal static void ThrowOnUnauthenticatedFeed(string stdout)
|
907
|
-
{
|
908
|
-
var unauthorizedMessageSnippets = new string[]
|
909
|
-
{
|
910
|
-
"The plugin credential provider could not acquire credentials",
|
911
|
-
"401 (Unauthorized)",
|
912
|
-
"error NU1301: Unable to load the service index for source",
|
913
|
-
"Response status code does not indicate success: 403",
|
914
|
-
};
|
915
|
-
if (unauthorizedMessageSnippets.Any(stdout.Contains))
|
916
|
-
{
|
917
|
-
throw new HttpRequestException(message: stdout, inner: null, statusCode: System.Net.HttpStatusCode.Unauthorized);
|
918
|
-
}
|
919
|
-
}
|
920
|
-
|
921
906
|
internal static string? GetMissingFile(string output)
|
922
907
|
{
|
923
908
|
var missingFilePatterns = new[]
|
@@ -934,7 +919,30 @@ internal static partial class MSBuildHelper
|
|
934
919
|
return null;
|
935
920
|
}
|
936
921
|
|
937
|
-
internal static void
|
922
|
+
internal static void ThrowOnError(string output)
|
923
|
+
{
|
924
|
+
ThrowOnUnauthenticatedFeed(output);
|
925
|
+
ThrowOnMissingFile(output);
|
926
|
+
ThrowOnMissingPackages(output);
|
927
|
+
ThrowOnUnresolvableDependencies(output);
|
928
|
+
}
|
929
|
+
|
930
|
+
private static void ThrowOnUnauthenticatedFeed(string stdout)
|
931
|
+
{
|
932
|
+
var unauthorizedMessageSnippets = new string[]
|
933
|
+
{
|
934
|
+
"The plugin credential provider could not acquire credentials",
|
935
|
+
"401 (Unauthorized)",
|
936
|
+
"error NU1301: Unable to load the service index for source",
|
937
|
+
"Response status code does not indicate success: 403",
|
938
|
+
};
|
939
|
+
if (unauthorizedMessageSnippets.Any(stdout.Contains))
|
940
|
+
{
|
941
|
+
throw new HttpRequestException(message: stdout, inner: null, statusCode: System.Net.HttpStatusCode.Unauthorized);
|
942
|
+
}
|
943
|
+
}
|
944
|
+
|
945
|
+
private static void ThrowOnMissingFile(string output)
|
938
946
|
{
|
939
947
|
var missingFile = GetMissingFile(output);
|
940
948
|
if (missingFile is not null)
|
@@ -943,9 +951,9 @@ internal static partial class MSBuildHelper
|
|
943
951
|
}
|
944
952
|
}
|
945
953
|
|
946
|
-
|
954
|
+
private static void ThrowOnMissingPackages(string output)
|
947
955
|
{
|
948
|
-
var missingPackagesPattern = new Regex(@"Package '(?<PackageName>[^']
|
956
|
+
var missingPackagesPattern = new Regex(@"Package '(?<PackageName>[^']*)' is not found on source");
|
949
957
|
var matchCollection = missingPackagesPattern.Matches(output);
|
950
958
|
var missingPackages = matchCollection.Select(m => m.Groups["PackageName"].Value).Distinct().ToArray();
|
951
959
|
if (missingPackages.Length > 0)
|
@@ -954,6 +962,16 @@ internal static partial class MSBuildHelper
|
|
954
962
|
}
|
955
963
|
}
|
956
964
|
|
965
|
+
private static void ThrowOnUnresolvableDependencies(string output)
|
966
|
+
{
|
967
|
+
var unresolvablePackagePattern = new Regex(@"Unable to resolve dependencies\. '(?<PackageName>[^ ]+) (?<PackageVersion>[^']+)'");
|
968
|
+
var match = unresolvablePackagePattern.Match(output);
|
969
|
+
if (match.Success)
|
970
|
+
{
|
971
|
+
throw new UpdateNotPossibleException([$"{match.Groups["PackageName"].Value}.{match.Groups["PackageVersion"].Value}"]);
|
972
|
+
}
|
973
|
+
}
|
974
|
+
|
957
975
|
internal static bool TryGetGlobalJsonPath(string repoRootPath, string workspacePath, [NotNullWhen(returnValue: true)] out string? globalJsonPath)
|
958
976
|
{
|
959
977
|
globalJsonPath = PathHelper.GetFileInDirectoryOrParent(workspacePath, repoRootPath, "global.json", caseSensitive: false);
|
@@ -1711,6 +1711,344 @@ public class RunWorkerTests
|
|
1711
1711
|
);
|
1712
1712
|
}
|
1713
1713
|
|
1714
|
+
[Fact]
|
1715
|
+
public async Task UpdatePackageWithDifferentVersionsInDifferentDirectories()
|
1716
|
+
{
|
1717
|
+
// this test passes `null` for discovery, analyze, and update workers to fully test the desired behavior
|
1718
|
+
|
1719
|
+
// the same dependency Some.Package is reported for 3 cases:
|
1720
|
+
// library1.csproj - top level dependency, already up to date
|
1721
|
+
// library2.csproj - top level dependency, needs direct update
|
1722
|
+
// library3.csproj - transitive dependency, needs pin
|
1723
|
+
await RunAsync(
|
1724
|
+
experimentsManager: new ExperimentsManager() { UseDirectDiscovery = true },
|
1725
|
+
packages: [
|
1726
|
+
MockNuGetPackage.CreateSimplePackage("Some.Package", "1.0.0", "net8.0"),
|
1727
|
+
MockNuGetPackage.CreateSimplePackage("Some.Package", "2.0.0", "net8.0"),
|
1728
|
+
MockNuGetPackage.CreateSimplePackage("Package.With.Transitive.Dependency", "0.1.0", "net8.0", [(null, [("Some.Package", "1.0.0")])]),
|
1729
|
+
],
|
1730
|
+
job: new Job()
|
1731
|
+
{
|
1732
|
+
AllowedUpdates = [new() { UpdateType = UpdateType.Security }],
|
1733
|
+
SecurityAdvisories =
|
1734
|
+
[
|
1735
|
+
new()
|
1736
|
+
{
|
1737
|
+
DependencyName = "Some.Package",
|
1738
|
+
AffectedVersions = [Requirement.Parse("= 1.0.0")]
|
1739
|
+
}
|
1740
|
+
],
|
1741
|
+
Source = new()
|
1742
|
+
{
|
1743
|
+
Provider = "github",
|
1744
|
+
Repo = "test/repo",
|
1745
|
+
Directory = "/"
|
1746
|
+
}
|
1747
|
+
},
|
1748
|
+
files: [
|
1749
|
+
("dirs.proj", """
|
1750
|
+
<Project>
|
1751
|
+
<ItemGroup>
|
1752
|
+
<ProjectFile Include="library1\library1.csproj" />
|
1753
|
+
<ProjectFile Include="library2\library2.csproj" />
|
1754
|
+
<ProjectFile Include="library3\library3.csproj" />
|
1755
|
+
</ItemGroup>
|
1756
|
+
</Project>
|
1757
|
+
"""),
|
1758
|
+
("Directory.Build.props", "<Project />"),
|
1759
|
+
("Directory.Build.targets", "<Project />"),
|
1760
|
+
("Directory.Packages.props", """
|
1761
|
+
<Project>
|
1762
|
+
<PropertyGroup>
|
1763
|
+
<ManagePackageVersionsCentrally>false</ManagePackageVersionsCentrally>
|
1764
|
+
</PropertyGroup>
|
1765
|
+
</Project>
|
1766
|
+
"""),
|
1767
|
+
("library1/library1.csproj", """
|
1768
|
+
<Project Sdk="Microsoft.NET.Sdk">
|
1769
|
+
<PropertyGroup>
|
1770
|
+
<TargetFramework>net8.0</TargetFramework>
|
1771
|
+
</PropertyGroup>
|
1772
|
+
<ItemGroup>
|
1773
|
+
<PackageReference Include="Some.Package" Version="2.0.0" />
|
1774
|
+
</ItemGroup>
|
1775
|
+
</Project>
|
1776
|
+
"""),
|
1777
|
+
("library2/library2.csproj", """
|
1778
|
+
<Project Sdk="Microsoft.NET.Sdk">
|
1779
|
+
<PropertyGroup>
|
1780
|
+
<TargetFramework>net8.0</TargetFramework>
|
1781
|
+
</PropertyGroup>
|
1782
|
+
<ItemGroup>
|
1783
|
+
<PackageReference Include="Some.Package" Version="1.0.0" />
|
1784
|
+
</ItemGroup>
|
1785
|
+
</Project>
|
1786
|
+
"""),
|
1787
|
+
("library3/library3.csproj", """
|
1788
|
+
<Project Sdk="Microsoft.NET.Sdk">
|
1789
|
+
<PropertyGroup>
|
1790
|
+
<TargetFramework>net8.0</TargetFramework>
|
1791
|
+
</PropertyGroup>
|
1792
|
+
<ItemGroup>
|
1793
|
+
<PackageReference Include="Package.With.Transitive.Dependency" Version="0.1.0" />
|
1794
|
+
</ItemGroup>
|
1795
|
+
</Project>
|
1796
|
+
"""),
|
1797
|
+
],
|
1798
|
+
discoveryWorker: null,
|
1799
|
+
analyzeWorker: null,
|
1800
|
+
updaterWorker: null,
|
1801
|
+
expectedResult: new RunResult()
|
1802
|
+
{
|
1803
|
+
Base64DependencyFiles =
|
1804
|
+
[
|
1805
|
+
new DependencyFile()
|
1806
|
+
{
|
1807
|
+
Directory = "/",
|
1808
|
+
Name = "Directory.Build.props",
|
1809
|
+
Content = Convert.ToBase64String(Encoding.UTF8.GetBytes("<Project />"))
|
1810
|
+
},
|
1811
|
+
new DependencyFile()
|
1812
|
+
{
|
1813
|
+
Directory = "/",
|
1814
|
+
Name = "Directory.Build.targets",
|
1815
|
+
Content = Convert.ToBase64String(Encoding.UTF8.GetBytes("<Project />"))
|
1816
|
+
},
|
1817
|
+
new DependencyFile()
|
1818
|
+
{
|
1819
|
+
Directory = "/",
|
1820
|
+
Name = "Directory.Packages.props",
|
1821
|
+
Content = Convert.ToBase64String(Encoding.UTF8.GetBytes("""
|
1822
|
+
<Project>
|
1823
|
+
<PropertyGroup>
|
1824
|
+
<ManagePackageVersionsCentrally>false</ManagePackageVersionsCentrally>
|
1825
|
+
</PropertyGroup>
|
1826
|
+
</Project>
|
1827
|
+
"""))
|
1828
|
+
},
|
1829
|
+
new DependencyFile()
|
1830
|
+
{
|
1831
|
+
Directory = "/library1",
|
1832
|
+
Name = "library1.csproj",
|
1833
|
+
Content = Convert.ToBase64String(Encoding.UTF8.GetBytes("""
|
1834
|
+
<Project Sdk="Microsoft.NET.Sdk">
|
1835
|
+
<PropertyGroup>
|
1836
|
+
<TargetFramework>net8.0</TargetFramework>
|
1837
|
+
</PropertyGroup>
|
1838
|
+
<ItemGroup>
|
1839
|
+
<PackageReference Include="Some.Package" Version="2.0.0" />
|
1840
|
+
</ItemGroup>
|
1841
|
+
</Project>
|
1842
|
+
"""))
|
1843
|
+
},
|
1844
|
+
new DependencyFile()
|
1845
|
+
{
|
1846
|
+
Directory = "/library2",
|
1847
|
+
Name = "library2.csproj",
|
1848
|
+
Content = Convert.ToBase64String(Encoding.UTF8.GetBytes("""
|
1849
|
+
<Project Sdk="Microsoft.NET.Sdk">
|
1850
|
+
<PropertyGroup>
|
1851
|
+
<TargetFramework>net8.0</TargetFramework>
|
1852
|
+
</PropertyGroup>
|
1853
|
+
<ItemGroup>
|
1854
|
+
<PackageReference Include="Some.Package" Version="1.0.0" />
|
1855
|
+
</ItemGroup>
|
1856
|
+
</Project>
|
1857
|
+
"""))
|
1858
|
+
},
|
1859
|
+
new DependencyFile()
|
1860
|
+
{
|
1861
|
+
Directory = "/library3",
|
1862
|
+
Name = "library3.csproj",
|
1863
|
+
Content = Convert.ToBase64String(Encoding.UTF8.GetBytes("""
|
1864
|
+
<Project Sdk="Microsoft.NET.Sdk">
|
1865
|
+
<PropertyGroup>
|
1866
|
+
<TargetFramework>net8.0</TargetFramework>
|
1867
|
+
</PropertyGroup>
|
1868
|
+
<ItemGroup>
|
1869
|
+
<PackageReference Include="Package.With.Transitive.Dependency" Version="0.1.0" />
|
1870
|
+
</ItemGroup>
|
1871
|
+
</Project>
|
1872
|
+
"""))
|
1873
|
+
}
|
1874
|
+
],
|
1875
|
+
BaseCommitSha = "TEST-COMMIT-SHA",
|
1876
|
+
},
|
1877
|
+
expectedApiMessages: [
|
1878
|
+
new UpdatedDependencyList()
|
1879
|
+
{
|
1880
|
+
Dependencies = [
|
1881
|
+
new()
|
1882
|
+
{
|
1883
|
+
Name = "Some.Package",
|
1884
|
+
Version = "2.0.0",
|
1885
|
+
Requirements = [
|
1886
|
+
new()
|
1887
|
+
{
|
1888
|
+
Requirement = "2.0.0",
|
1889
|
+
File = "/library1/library1.csproj",
|
1890
|
+
Groups = ["dependencies"],
|
1891
|
+
}
|
1892
|
+
]
|
1893
|
+
},
|
1894
|
+
new()
|
1895
|
+
{
|
1896
|
+
Name = "Some.Package",
|
1897
|
+
Version = "1.0.0",
|
1898
|
+
Requirements = [
|
1899
|
+
new()
|
1900
|
+
{
|
1901
|
+
Requirement = "1.0.0",
|
1902
|
+
File = "/library2/library2.csproj",
|
1903
|
+
Groups = ["dependencies"],
|
1904
|
+
}
|
1905
|
+
]
|
1906
|
+
},
|
1907
|
+
new()
|
1908
|
+
{
|
1909
|
+
Name = "Package.With.Transitive.Dependency",
|
1910
|
+
Version = "0.1.0",
|
1911
|
+
Requirements = [
|
1912
|
+
new()
|
1913
|
+
{
|
1914
|
+
Requirement = "0.1.0",
|
1915
|
+
File = "/library3/library3.csproj",
|
1916
|
+
Groups = ["dependencies"],
|
1917
|
+
}
|
1918
|
+
]
|
1919
|
+
},
|
1920
|
+
new()
|
1921
|
+
{
|
1922
|
+
Name = "Some.Package",
|
1923
|
+
Version = "1.0.0",
|
1924
|
+
Requirements = [
|
1925
|
+
new()
|
1926
|
+
{
|
1927
|
+
Requirement = "1.0.0",
|
1928
|
+
File = "/library3/library3.csproj",
|
1929
|
+
Groups = ["dependencies"],
|
1930
|
+
}
|
1931
|
+
]
|
1932
|
+
},
|
1933
|
+
],
|
1934
|
+
DependencyFiles = [
|
1935
|
+
"/Directory.Build.props",
|
1936
|
+
"/Directory.Build.targets",
|
1937
|
+
"/Directory.Packages.props",
|
1938
|
+
"/library1/library1.csproj",
|
1939
|
+
"/library2/library2.csproj",
|
1940
|
+
"/library3/library3.csproj",
|
1941
|
+
],
|
1942
|
+
},
|
1943
|
+
new IncrementMetric()
|
1944
|
+
{
|
1945
|
+
Metric = "updater.started",
|
1946
|
+
Tags = new()
|
1947
|
+
{
|
1948
|
+
["operation"] = "group_update_all_versions"
|
1949
|
+
}
|
1950
|
+
},
|
1951
|
+
new CreatePullRequest()
|
1952
|
+
{
|
1953
|
+
Dependencies = [
|
1954
|
+
new()
|
1955
|
+
{
|
1956
|
+
Name = "Some.Package",
|
1957
|
+
Version = "2.0.0",
|
1958
|
+
Requirements = [
|
1959
|
+
new()
|
1960
|
+
{
|
1961
|
+
Requirement = "2.0.0",
|
1962
|
+
File = "/library2/library2.csproj",
|
1963
|
+
Groups = ["dependencies"],
|
1964
|
+
Source = new()
|
1965
|
+
{
|
1966
|
+
SourceUrl = null,
|
1967
|
+
Type = "nuget_repo",
|
1968
|
+
}
|
1969
|
+
}
|
1970
|
+
],
|
1971
|
+
PreviousVersion = "1.0.0",
|
1972
|
+
PreviousRequirements = [
|
1973
|
+
new()
|
1974
|
+
{
|
1975
|
+
Requirement = "1.0.0",
|
1976
|
+
File = "/library2/library2.csproj",
|
1977
|
+
Groups = ["dependencies"],
|
1978
|
+
}
|
1979
|
+
],
|
1980
|
+
},
|
1981
|
+
new()
|
1982
|
+
{
|
1983
|
+
Name = "Some.Package",
|
1984
|
+
Version = "2.0.0",
|
1985
|
+
Requirements = [
|
1986
|
+
new()
|
1987
|
+
{
|
1988
|
+
Requirement = "2.0.0",
|
1989
|
+
File = "/library3/library3.csproj",
|
1990
|
+
Groups = ["dependencies"],
|
1991
|
+
Source = new()
|
1992
|
+
{
|
1993
|
+
SourceUrl = null,
|
1994
|
+
Type = "nuget_repo",
|
1995
|
+
}
|
1996
|
+
}
|
1997
|
+
],
|
1998
|
+
PreviousVersion = "1.0.0",
|
1999
|
+
PreviousRequirements = [
|
2000
|
+
new()
|
2001
|
+
{
|
2002
|
+
Requirement = "1.0.0",
|
2003
|
+
File = "/library3/library3.csproj",
|
2004
|
+
Groups = ["dependencies"],
|
2005
|
+
}
|
2006
|
+
],
|
2007
|
+
},
|
2008
|
+
],
|
2009
|
+
UpdatedDependencyFiles = [
|
2010
|
+
new()
|
2011
|
+
{
|
2012
|
+
Directory = "/library2",
|
2013
|
+
Name = "library2.csproj",
|
2014
|
+
Content = """
|
2015
|
+
<Project Sdk="Microsoft.NET.Sdk">
|
2016
|
+
<PropertyGroup>
|
2017
|
+
<TargetFramework>net8.0</TargetFramework>
|
2018
|
+
</PropertyGroup>
|
2019
|
+
<ItemGroup>
|
2020
|
+
<PackageReference Include="Some.Package" Version="2.0.0" />
|
2021
|
+
</ItemGroup>
|
2022
|
+
</Project>
|
2023
|
+
"""
|
2024
|
+
},
|
2025
|
+
new()
|
2026
|
+
{
|
2027
|
+
Directory = "/library3",
|
2028
|
+
Name = "library3.csproj",
|
2029
|
+
Content = """
|
2030
|
+
<Project Sdk="Microsoft.NET.Sdk">
|
2031
|
+
<PropertyGroup>
|
2032
|
+
<TargetFramework>net8.0</TargetFramework>
|
2033
|
+
</PropertyGroup>
|
2034
|
+
<ItemGroup>
|
2035
|
+
<PackageReference Include="Package.With.Transitive.Dependency" Version="0.1.0" />
|
2036
|
+
<PackageReference Include="Some.Package" Version="2.0.0" />
|
2037
|
+
</ItemGroup>
|
2038
|
+
</Project>
|
2039
|
+
"""
|
2040
|
+
}
|
2041
|
+
],
|
2042
|
+
BaseCommitSha = "TEST-COMMIT-SHA",
|
2043
|
+
CommitMessage = "TODO: message",
|
2044
|
+
PrTitle = "TODO: title",
|
2045
|
+
PrBody = "TODO: body"
|
2046
|
+
},
|
2047
|
+
new MarkAsProcessed("TEST-COMMIT-SHA")
|
2048
|
+
]
|
2049
|
+
);
|
2050
|
+
}
|
2051
|
+
|
1714
2052
|
private static async Task RunAsync(Job job, TestFile[] files, IDiscoveryWorker? discoveryWorker, IAnalyzeWorker? analyzeWorker, IUpdaterWorker? updaterWorker, RunResult expectedResult, object[] expectedApiMessages, MockNuGetPackage[]? packages = null, ExperimentsManager? experimentsManager = null, string? repoContentsPath = null)
|
1715
2053
|
{
|
1716
2054
|
// arrange
|
@@ -0,0 +1,286 @@
|
|
1
|
+
using System.Collections.Immutable;
|
2
|
+
|
3
|
+
using NuGetUpdater.Core.Analyze;
|
4
|
+
using NuGetUpdater.Core.Run;
|
5
|
+
using NuGetUpdater.Core.Run.ApiModel;
|
6
|
+
|
7
|
+
using Xunit;
|
8
|
+
|
9
|
+
using DepType = NuGetUpdater.Core.Run.ApiModel.DependencyType;
|
10
|
+
|
11
|
+
namespace NuGetUpdater.Core.Test.Run;
|
12
|
+
|
13
|
+
public class UpdateAllowedTests
|
14
|
+
{
|
15
|
+
[Theory]
|
16
|
+
[MemberData(nameof(IsUpdateAllowedTestData))]
|
17
|
+
public void IsUpdateAllowed(Job job, Dependency dependency, bool expectedResult)
|
18
|
+
{
|
19
|
+
var actualResult = RunWorker.IsUpdateAllowed(job, dependency);
|
20
|
+
Assert.Equal(expectedResult, actualResult);
|
21
|
+
}
|
22
|
+
|
23
|
+
public static IEnumerable<object[]> IsUpdateAllowedTestData()
|
24
|
+
{
|
25
|
+
// with default allowed updates on a transitive dependency
|
26
|
+
yield return
|
27
|
+
[
|
28
|
+
CreateJob(
|
29
|
+
allowedUpdates: [
|
30
|
+
new AllowedUpdate() { DependencyType = DepType.Direct, UpdateType = UpdateType.All }
|
31
|
+
],
|
32
|
+
securityAdvisories: [
|
33
|
+
new Advisory() { DependencyName = "Some.Package", AffectedVersions = [], PatchedVersions = [Requirement.Parse(">= 1.11.0")], UnaffectedVersions = [] }
|
34
|
+
],
|
35
|
+
securityUpdatesOnly: false),
|
36
|
+
new Dependency("Some.Package", "1.8.0", DependencyType.PackageReference, IsTransitive: true),
|
37
|
+
// expectedResult
|
38
|
+
false,
|
39
|
+
];
|
40
|
+
|
41
|
+
// when dealing with a security update
|
42
|
+
yield return
|
43
|
+
[
|
44
|
+
CreateJob(
|
45
|
+
allowedUpdates: [
|
46
|
+
new AllowedUpdate() { DependencyType = DepType.Direct, UpdateType = UpdateType.All }
|
47
|
+
],
|
48
|
+
securityAdvisories: [
|
49
|
+
new Advisory() { DependencyName = "Some.Package", AffectedVersions = [], PatchedVersions = [Requirement.Parse(">= 1.11.0")], UnaffectedVersions = [] }
|
50
|
+
],
|
51
|
+
securityUpdatesOnly: true),
|
52
|
+
new Dependency("Some.Package", "1.8.0", DependencyType.PackageReference, IsTransitive: true),
|
53
|
+
// expectedResult
|
54
|
+
true,
|
55
|
+
];
|
56
|
+
|
57
|
+
// with a top-level dependency
|
58
|
+
yield return
|
59
|
+
[
|
60
|
+
CreateJob(
|
61
|
+
allowedUpdates: [
|
62
|
+
new AllowedUpdate() { DependencyType = DepType.Direct, UpdateType = UpdateType.All },
|
63
|
+
new AllowedUpdate() { DependencyType = DepType.Indirect, UpdateType = UpdateType.Security }
|
64
|
+
],
|
65
|
+
securityAdvisories: [],
|
66
|
+
securityUpdatesOnly: false),
|
67
|
+
new Dependency("Some.Package", "1.8.0", DependencyType.PackageReference, IsTransitive: false),
|
68
|
+
// expectedResult
|
69
|
+
true,
|
70
|
+
];
|
71
|
+
|
72
|
+
// with a sub-dependency
|
73
|
+
yield return
|
74
|
+
[
|
75
|
+
CreateJob(
|
76
|
+
allowedUpdates: [
|
77
|
+
new AllowedUpdate() { DependencyType = DepType.Direct, UpdateType = UpdateType.All },
|
78
|
+
new AllowedUpdate() { DependencyType = DepType.Indirect, UpdateType = UpdateType.Security }
|
79
|
+
],
|
80
|
+
securityAdvisories: [],
|
81
|
+
securityUpdatesOnly: false),
|
82
|
+
new Dependency("Some.Package", "1.8.0", DependencyType.PackageReference, IsTransitive: true),
|
83
|
+
// expectedResult
|
84
|
+
false,
|
85
|
+
];
|
86
|
+
|
87
|
+
// when insecure
|
88
|
+
yield return
|
89
|
+
[
|
90
|
+
CreateJob(
|
91
|
+
allowedUpdates: [
|
92
|
+
new AllowedUpdate() { DependencyType = DepType.Direct, UpdateType = UpdateType.All },
|
93
|
+
new AllowedUpdate() { DependencyType = DepType.Indirect, UpdateType = UpdateType.Security }
|
94
|
+
],
|
95
|
+
securityAdvisories: [
|
96
|
+
new Advisory() { DependencyName = "Some.Package", AffectedVersions = [], PatchedVersions = [Requirement.Parse(">= 1.11.0")], UnaffectedVersions = [] }
|
97
|
+
],
|
98
|
+
securityUpdatesOnly: false),
|
99
|
+
new Dependency("Some.Package", "1.8.0", DependencyType.PackageReference, IsTransitive: true),
|
100
|
+
// expectedResult
|
101
|
+
true,
|
102
|
+
];
|
103
|
+
|
104
|
+
// when only security fixes are allowed
|
105
|
+
yield return
|
106
|
+
[
|
107
|
+
CreateJob(
|
108
|
+
allowedUpdates: [
|
109
|
+
new AllowedUpdate() { DependencyType = DepType.Direct, UpdateType = UpdateType.All },
|
110
|
+
new AllowedUpdate() { DependencyType = DepType.Indirect, UpdateType = UpdateType.Security }
|
111
|
+
],
|
112
|
+
securityAdvisories: [],
|
113
|
+
securityUpdatesOnly: true),
|
114
|
+
new Dependency("Some.Package", "1.8.0", DependencyType.PackageReference, IsTransitive: false),
|
115
|
+
// expectedResult
|
116
|
+
false,
|
117
|
+
];
|
118
|
+
|
119
|
+
// when dealing with a security fix
|
120
|
+
yield return
|
121
|
+
[
|
122
|
+
CreateJob(
|
123
|
+
allowedUpdates: [
|
124
|
+
new AllowedUpdate() { DependencyType = DepType.Direct, UpdateType = UpdateType.All },
|
125
|
+
new AllowedUpdate() { DependencyType = DepType.Indirect, UpdateType = UpdateType.Security }
|
126
|
+
],
|
127
|
+
securityAdvisories: [
|
128
|
+
new Advisory() { DependencyName = "Some.Package", AffectedVersions = [], PatchedVersions = [Requirement.Parse(">= 1.11.0")], UnaffectedVersions = [] }
|
129
|
+
],
|
130
|
+
securityUpdatesOnly: true),
|
131
|
+
new Dependency("Some.Package", "1.8.0", DependencyType.PackageReference, IsTransitive: false),
|
132
|
+
// expectedResult
|
133
|
+
true,
|
134
|
+
];
|
135
|
+
|
136
|
+
// when dealing with a security fix that doesn't apply
|
137
|
+
yield return
|
138
|
+
[
|
139
|
+
CreateJob(
|
140
|
+
allowedUpdates: [
|
141
|
+
new AllowedUpdate() { DependencyType = DepType.Direct, UpdateType = UpdateType.All },
|
142
|
+
new AllowedUpdate() { DependencyType = DepType.Indirect, UpdateType = UpdateType.Security }
|
143
|
+
],
|
144
|
+
securityAdvisories: [
|
145
|
+
new Advisory() { DependencyName = "Some.Package", AffectedVersions = [Requirement.Parse("> 1.8.0")], PatchedVersions = [], UnaffectedVersions = [] }
|
146
|
+
],
|
147
|
+
securityUpdatesOnly: true),
|
148
|
+
new Dependency("Some.Package", "1.8.0", DependencyType.PackageReference, IsTransitive: false),
|
149
|
+
// expectedResult
|
150
|
+
false,
|
151
|
+
];
|
152
|
+
|
153
|
+
// when dealing with a security fix that doesn't apply to some versions
|
154
|
+
yield return
|
155
|
+
[
|
156
|
+
CreateJob(
|
157
|
+
allowedUpdates: [
|
158
|
+
new AllowedUpdate() { DependencyType = DepType.Direct, UpdateType = UpdateType.All },
|
159
|
+
new AllowedUpdate() { DependencyType = DepType.Indirect, UpdateType = UpdateType.Security }
|
160
|
+
],
|
161
|
+
securityAdvisories: [
|
162
|
+
new Advisory() { DependencyName = "Some.Package", AffectedVersions = [Requirement.Parse("< 1.8.0"), Requirement.Parse("> 1.8.0")], PatchedVersions = [], UnaffectedVersions = [] }
|
163
|
+
],
|
164
|
+
securityUpdatesOnly: true),
|
165
|
+
new Dependency("Some.Package", "1.8.1", DependencyType.PackageReference, IsTransitive: false),
|
166
|
+
// expectedResult
|
167
|
+
true,
|
168
|
+
];
|
169
|
+
|
170
|
+
// when a dependency allow list that includes the dependency
|
171
|
+
yield return
|
172
|
+
[
|
173
|
+
CreateJob(
|
174
|
+
allowedUpdates: [
|
175
|
+
new AllowedUpdate() { DependencyName = "Some.Package" }
|
176
|
+
],
|
177
|
+
securityAdvisories: [],
|
178
|
+
securityUpdatesOnly: false),
|
179
|
+
new Dependency("Some.Package", "1.8.0", DependencyType.PackageReference, IsTransitive: false),
|
180
|
+
// expectedResult
|
181
|
+
true,
|
182
|
+
];
|
183
|
+
|
184
|
+
// with a dependency allow list that uses a wildcard
|
185
|
+
yield return
|
186
|
+
[
|
187
|
+
CreateJob(
|
188
|
+
allowedUpdates: [
|
189
|
+
new AllowedUpdate() { DependencyName = "Some.*" }
|
190
|
+
],
|
191
|
+
securityAdvisories: [],
|
192
|
+
securityUpdatesOnly: false),
|
193
|
+
new Dependency("Some.Package", "1.8.0", DependencyType.PackageReference, IsTransitive: false),
|
194
|
+
// expectedResult
|
195
|
+
true,
|
196
|
+
];
|
197
|
+
|
198
|
+
// when dependency allow list that excludes the dependency
|
199
|
+
yield return
|
200
|
+
[
|
201
|
+
CreateJob(
|
202
|
+
allowedUpdates: [
|
203
|
+
new AllowedUpdate() { DependencyName = "Unrelated.Package" }
|
204
|
+
],
|
205
|
+
securityAdvisories: [],
|
206
|
+
securityUpdatesOnly: false),
|
207
|
+
new Dependency("Some.Package", "1.8.0", DependencyType.PackageReference, IsTransitive: false),
|
208
|
+
// expectedResult
|
209
|
+
false,
|
210
|
+
];
|
211
|
+
|
212
|
+
// when matching with an incomplete dependency name
|
213
|
+
yield return
|
214
|
+
[
|
215
|
+
CreateJob(
|
216
|
+
allowedUpdates: [
|
217
|
+
new AllowedUpdate() { DependencyName = "Some" }
|
218
|
+
],
|
219
|
+
securityAdvisories: [],
|
220
|
+
securityUpdatesOnly: false),
|
221
|
+
new Dependency("Some.Package", "1.8.0", DependencyType.PackageReference, IsTransitive: false),
|
222
|
+
// expectedResult
|
223
|
+
false,
|
224
|
+
];
|
225
|
+
|
226
|
+
// with a dependency allow list that uses a wildcard
|
227
|
+
yield return
|
228
|
+
[
|
229
|
+
CreateJob(
|
230
|
+
allowedUpdates: [
|
231
|
+
new AllowedUpdate() { DependencyName = "Unrelated.*" }
|
232
|
+
],
|
233
|
+
securityAdvisories: [],
|
234
|
+
securityUpdatesOnly: false),
|
235
|
+
new Dependency("Some.Package", "1.8.0", DependencyType.PackageReference, IsTransitive: false),
|
236
|
+
// expectedResult
|
237
|
+
false,
|
238
|
+
];
|
239
|
+
|
240
|
+
// when security fixes are also allowed
|
241
|
+
yield return
|
242
|
+
[
|
243
|
+
CreateJob(
|
244
|
+
allowedUpdates: [
|
245
|
+
new AllowedUpdate() { DependencyName = "Unrelated.Package" },
|
246
|
+
new AllowedUpdate() { UpdateType = UpdateType.Security }
|
247
|
+
],
|
248
|
+
securityAdvisories: [],
|
249
|
+
securityUpdatesOnly: false),
|
250
|
+
new Dependency("Some.Package", "1.8.0", DependencyType.PackageReference, IsTransitive: false),
|
251
|
+
// expectedResult
|
252
|
+
false,
|
253
|
+
];
|
254
|
+
|
255
|
+
// when dealing with a security fix
|
256
|
+
yield return
|
257
|
+
[
|
258
|
+
CreateJob(
|
259
|
+
allowedUpdates: [
|
260
|
+
new AllowedUpdate() { DependencyName = "Unrelated.Package"}, new AllowedUpdate(){ UpdateType = UpdateType.Security }
|
261
|
+
],
|
262
|
+
securityAdvisories: [
|
263
|
+
new Advisory() { DependencyName = "Some.Package", AffectedVersions = [], PatchedVersions = [Requirement.Parse(">= 1.11.0")], UnaffectedVersions = [] }
|
264
|
+
],
|
265
|
+
securityUpdatesOnly: false),
|
266
|
+
new Dependency("Some.Package", "1.8.0", DependencyType.PackageReference, IsTransitive: false),
|
267
|
+
// expectedResult
|
268
|
+
true,
|
269
|
+
];
|
270
|
+
}
|
271
|
+
|
272
|
+
private static Job CreateJob(AllowedUpdate[] allowedUpdates, Advisory[] securityAdvisories, bool securityUpdatesOnly)
|
273
|
+
{
|
274
|
+
return new Job()
|
275
|
+
{
|
276
|
+
AllowedUpdates = allowedUpdates.ToImmutableArray(),
|
277
|
+
SecurityAdvisories = securityAdvisories.ToImmutableArray(),
|
278
|
+
SecurityUpdatesOnly = securityUpdatesOnly,
|
279
|
+
Source = new()
|
280
|
+
{
|
281
|
+
Provider = "nuget",
|
282
|
+
Repo = "test/repo",
|
283
|
+
}
|
284
|
+
};
|
285
|
+
}
|
286
|
+
}
|
@@ -94,7 +94,15 @@ public class UpdatedDependencyListTests
|
|
94
94
|
{
|
95
95
|
Name = "System.Text.Json",
|
96
96
|
Version = "6.0.0",
|
97
|
-
Requirements =
|
97
|
+
Requirements =
|
98
|
+
[
|
99
|
+
new ReportedRequirement()
|
100
|
+
{
|
101
|
+
Requirement = "6.0.0",
|
102
|
+
File = "/src/c/project.csproj",
|
103
|
+
Groups = ["dependencies"],
|
104
|
+
}
|
105
|
+
],
|
98
106
|
},
|
99
107
|
new ReportedDependency()
|
100
108
|
{
|
@@ -1,7 +1,8 @@
|
|
1
1
|
using System.Collections.Immutable;
|
2
|
+
using System.Text.Json;
|
2
3
|
|
3
|
-
using
|
4
|
-
|
4
|
+
using NuGetUpdater.Core.Run;
|
5
|
+
using NuGetUpdater.Core.Run.ApiModel;
|
5
6
|
using NuGetUpdater.Core.Test.Update;
|
6
7
|
|
7
8
|
using Xunit;
|
@@ -1366,6 +1367,91 @@ public class MSBuildHelperTests : TestBase
|
|
1366
1367
|
}
|
1367
1368
|
#endregion
|
1368
1369
|
|
1370
|
+
[Theory]
|
1371
|
+
[MemberData(nameof(GenerateErrorFromToolOutputTestData))]
|
1372
|
+
public async Task GenerateErrorFromToolOutput(string output, JobErrorBase? expectedError)
|
1373
|
+
{
|
1374
|
+
Exception? exception = null;
|
1375
|
+
try
|
1376
|
+
{
|
1377
|
+
MSBuildHelper.ThrowOnError(output);
|
1378
|
+
}
|
1379
|
+
catch (Exception ex)
|
1380
|
+
{
|
1381
|
+
exception = ex;
|
1382
|
+
}
|
1383
|
+
|
1384
|
+
if (expectedError is null)
|
1385
|
+
{
|
1386
|
+
Assert.Null(exception);
|
1387
|
+
}
|
1388
|
+
else
|
1389
|
+
{
|
1390
|
+
Assert.NotNull(exception);
|
1391
|
+
using var tempDir = await TemporaryDirectory.CreateWithContentsAsync([("NuGet.Config", """
|
1392
|
+
<configuration>
|
1393
|
+
<packageSources>
|
1394
|
+
<clear />
|
1395
|
+
<add key="test-feed" value="http://localhost/test-feed" />
|
1396
|
+
</packageSources>
|
1397
|
+
</configuration>
|
1398
|
+
""")]);
|
1399
|
+
var actualError = JobErrorBase.ErrorFromException(exception, "TEST-JOB-ID", tempDir.DirectoryPath);
|
1400
|
+
if (actualError is DependencyFileNotFound notFound)
|
1401
|
+
{
|
1402
|
+
// normalize default message for the test
|
1403
|
+
actualError = new DependencyFileNotFound(notFound.Details["file-path"].ToString()!, "test message");
|
1404
|
+
}
|
1405
|
+
|
1406
|
+
var actualErrorJson = JsonSerializer.Serialize(actualError, RunWorker.SerializerOptions);
|
1407
|
+
var expectedErrorJson = JsonSerializer.Serialize(expectedError, RunWorker.SerializerOptions);
|
1408
|
+
Assert.Equal(expectedErrorJson, actualErrorJson);
|
1409
|
+
}
|
1410
|
+
}
|
1411
|
+
|
1412
|
+
public static IEnumerable<object?[]> GenerateErrorFromToolOutputTestData()
|
1413
|
+
{
|
1414
|
+
yield return
|
1415
|
+
[
|
1416
|
+
// output
|
1417
|
+
"Everything was good.",
|
1418
|
+
// expectedError
|
1419
|
+
null,
|
1420
|
+
];
|
1421
|
+
|
1422
|
+
yield return
|
1423
|
+
[
|
1424
|
+
// output
|
1425
|
+
"Response status code does not indicate success: 403",
|
1426
|
+
// expectedError
|
1427
|
+
new PrivateSourceAuthenticationFailure(["http://localhost/test-feed"]),
|
1428
|
+
];
|
1429
|
+
|
1430
|
+
yield return
|
1431
|
+
[
|
1432
|
+
// output
|
1433
|
+
"The imported file \"some.file\" does not exist",
|
1434
|
+
// expectedError
|
1435
|
+
new DependencyFileNotFound("some.file", "test message"),
|
1436
|
+
];
|
1437
|
+
|
1438
|
+
yield return
|
1439
|
+
[
|
1440
|
+
// output
|
1441
|
+
"Package 'Some.Package' is not found on source",
|
1442
|
+
// expectedError
|
1443
|
+
new UpdateNotPossible(["Some.Package"]),
|
1444
|
+
];
|
1445
|
+
|
1446
|
+
yield return
|
1447
|
+
[
|
1448
|
+
// output
|
1449
|
+
"Unable to resolve dependencies. 'Some.Package 1.2.3' is not compatible with",
|
1450
|
+
// expectedError
|
1451
|
+
new UpdateNotPossible(["Some.Package.1.2.3"]),
|
1452
|
+
];
|
1453
|
+
}
|
1454
|
+
|
1369
1455
|
public static IEnumerable<object[]> GetTopLevelPackageDependencyInfosTestData()
|
1370
1456
|
{
|
1371
1457
|
// simple case
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: dependabot-nuget
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.
|
4
|
+
version: 0.295.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Dependabot
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2025-01-
|
11
|
+
date: 2025-01-30 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: dependabot-common
|
@@ -16,14 +16,14 @@ dependencies:
|
|
16
16
|
requirements:
|
17
17
|
- - '='
|
18
18
|
- !ruby/object:Gem::Version
|
19
|
-
version: 0.
|
19
|
+
version: 0.295.0
|
20
20
|
type: :runtime
|
21
21
|
prerelease: false
|
22
22
|
version_requirements: !ruby/object:Gem::Requirement
|
23
23
|
requirements:
|
24
24
|
- - '='
|
25
25
|
- !ruby/object:Gem::Version
|
26
|
-
version: 0.
|
26
|
+
version: 0.295.0
|
27
27
|
- !ruby/object:Gem::Dependency
|
28
28
|
name: rubyzip
|
29
29
|
requirement: !ruby/object:Gem::Requirement
|
@@ -351,6 +351,7 @@ files:
|
|
351
351
|
- helpers/lib/NuGetUpdater/NuGetUpdater.Core.Test/Run/RunWorkerTests.cs
|
352
352
|
- helpers/lib/NuGetUpdater/NuGetUpdater.Core.Test/Run/SerializationTests.cs
|
353
353
|
- helpers/lib/NuGetUpdater/NuGetUpdater.Core.Test/Run/TestApiHandler.cs
|
354
|
+
- helpers/lib/NuGetUpdater/NuGetUpdater.Core.Test/Run/UpdateAllowedTests.cs
|
354
355
|
- helpers/lib/NuGetUpdater/NuGetUpdater.Core.Test/Run/UpdatedDependencyListTests.cs
|
355
356
|
- helpers/lib/NuGetUpdater/NuGetUpdater.Core.Test/TemporaryDirectory.cs
|
356
357
|
- helpers/lib/NuGetUpdater/NuGetUpdater.Core.Test/TemporaryEnvironment.cs
|
@@ -528,7 +529,7 @@ licenses:
|
|
528
529
|
- MIT
|
529
530
|
metadata:
|
530
531
|
bug_tracker_uri: https://github.com/dependabot/dependabot-core/issues
|
531
|
-
changelog_uri: https://github.com/dependabot/dependabot-core/releases/tag/v0.
|
532
|
+
changelog_uri: https://github.com/dependabot/dependabot-core/releases/tag/v0.295.0
|
532
533
|
post_install_message:
|
533
534
|
rdoc_options: []
|
534
535
|
require_paths:
|