dependabot-nuget 0.311.0 → 0.312.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (26) hide show
  1. checksums.yaml +4 -4
  2. data/helpers/lib/NuGetUpdater/NuGetUpdater.Cli.Test/EntryPointTests.Run.cs +2 -2
  3. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core/Analyze/VersionFinder.cs +7 -0
  4. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core/BadResponseException.cs +12 -0
  5. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core/Discover/SdkProjectDiscovery.cs +121 -9
  6. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core/Run/ApiModel/JobErrorBase.cs +7 -0
  7. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core/Run/ApiModel/UnknownError.cs +12 -2
  8. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core/Run/HttpApiHandler.cs +19 -50
  9. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core/Run/IApiHandler.cs +33 -7
  10. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core/Run/RunWorker.cs +10 -5
  11. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core/Updater/PackageReferenceUpdater.cs +13 -1
  12. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core/Utilities/MSBuildHelper.cs +1 -0
  13. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core.Test/Analyze/VersionFinderTests.cs +74 -1
  14. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core.Test/Clone/CloneWorkerTests.cs +1 -1
  15. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core.Test/Discover/DiscoveryWorkerTests.Project.cs +3 -9
  16. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core.Test/Discover/DiscoveryWorkerTests.cs +1 -2
  17. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core.Test/Discover/SdkProjectDiscoveryTests.cs +58 -2
  18. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core.Test/Run/HttpApiHandlerTests.cs +116 -0
  19. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core.Test/Run/MessageReportTests.cs +1 -2
  20. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core.Test/Run/RunWorkerTests.cs +227 -6
  21. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core.Test/Run/SerializationTests.cs +40 -11
  22. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core.Test/Run/TestApiHandler.cs +2 -39
  23. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core.Test/TestHttpServer.cs +9 -5
  24. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core.Test/Update/PackageReferenceUpdaterTests.cs +99 -1
  25. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core.Test/Utilities/MSBuildHelperTests.cs +8 -0
  26. metadata +25 -23
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: a32c81e06aafe3606a77d89ae351b246b6e80c9fd272bfe0001e6486eebf989b
4
- data.tar.gz: 7796df4d53b7b86d9ea7472ff82fd4801dd7144d93b087fe32423dfb535c50e5
3
+ metadata.gz: acb9d775c29330f4cae905aa23b7ad9c36f0ed161d8cc1c2fbfc7391fb52646b
4
+ data.tar.gz: 97b60783a35914969fe10efc5235a91d29a470faf40e4520cf331bf6bafe21f1
5
5
  SHA512:
6
- metadata.gz: b487988ea2bc084a70c73216d9ab773be814a442c3a9e160374ae5bdefd03e9067ca9b7cc12d55725da0396e3e14af7bb1cb037742ac7f2aba7c9b65130cfb8a
7
- data.tar.gz: 281290949ba2b0dccaff75e68aaeab2e886721cc19c79cf379ce5302cbe719f2fa7e1ec491720a30f3bda4617136c03e7b8e8a4dc20fef9c6632f79ed3c0a9ae
6
+ metadata.gz: d1d117e611b42497834c3276f08e35e3a70a3b2d0f960ac3d666dde3c12eb32697f911e0af3409e76500977f4c1db835c4979a85879e86bbd00e0ec0df4cc34c
7
+ data.tar.gz: b3f8f76bc8be51fc23493c1660cb41e5f6b9c10721941bd98c72c49652c807bb320def722cdc3c08d822f2cd2ed0dbe993b75cd6d43d0f1593de6890a753af32
@@ -58,7 +58,7 @@ public partial class EntryPointTests
58
58
  );
59
59
  }
60
60
 
61
- private static async Task RunAsync(TestFile[] files, Job job, string[] expectedUrls, MockNuGetPackage[]? packages = null)
61
+ private static async Task RunAsync(TestFile[] files, Job job, string[] expectedUrls, MockNuGetPackage[]? packages = null, string? repoContentsPath = null)
62
62
  {
63
63
  using var tempDirectory = new TemporaryDirectory();
64
64
 
@@ -90,7 +90,7 @@ public partial class EntryPointTests
90
90
  "--job-path",
91
91
  jobPath,
92
92
  "--repo-contents-path",
93
- tempDirectory.DirectoryPath,
93
+ repoContentsPath ?? tempDirectory.DirectoryPath,
94
94
  "--api-url",
95
95
  http.BaseUrl,
96
96
  "--job-id",
@@ -1,5 +1,7 @@
1
1
  using System.Collections.Immutable;
2
2
 
3
+ using Newtonsoft.Json;
4
+
3
5
  using NuGet.Common;
4
6
  using NuGet.Configuration;
5
7
  using NuGet.Frameworks;
@@ -98,6 +100,11 @@ internal static class VersionFinder
98
100
  // if anything goes wrong here, the package source obviously doesn't contain the requested package
99
101
  continue;
100
102
  }
103
+ catch (JsonReaderException ex)
104
+ {
105
+ // unable to parse server response
106
+ throw new BadResponseException(ex.Message, source.Source);
107
+ }
101
108
 
102
109
  var feedVersions = (await feed.GetVersions(
103
110
  packageId,
@@ -0,0 +1,12 @@
1
+ namespace NuGetUpdater.Core;
2
+
3
+ internal class BadResponseException : Exception
4
+ {
5
+ public string Uri { get; }
6
+
7
+ public BadResponseException(string message, string uri)
8
+ : base(message)
9
+ {
10
+ Uri = uri;
11
+ }
12
+ }
@@ -1,9 +1,11 @@
1
1
  using System.Collections.Immutable;
2
+ using System.Text.Json;
2
3
  using System.Xml.Linq;
3
4
  using System.Xml.XPath;
4
5
 
5
6
  using Microsoft.Build.Logging.StructuredLogger;
6
7
 
8
+ using NuGet.Frameworks;
7
9
  using NuGet.Versioning;
8
10
 
9
11
  using NuGetUpdater.Core.Utilities;
@@ -39,6 +41,8 @@ internal static class SdkProjectDiscovery
39
41
  // these packages are resolved during restore, but aren't really updatable and shouldn't be reported as dependencies
40
42
  private static readonly HashSet<string> NonReportedPackgeNames = new HashSet<string>(StringComparer.OrdinalIgnoreCase)
41
43
  {
44
+ "Microsoft.NETCore.Platforms",
45
+ "Microsoft.NETCore.Targets",
42
46
  "NETStandard.Library"
43
47
  };
44
48
 
@@ -84,6 +88,9 @@ internal static class SdkProjectDiscovery
84
88
  Dictionary<string, Dictionary<string, Dictionary<string, string>>> packagesReplacedBySdkPerProject = new(PathComparer.Instance);
85
89
  // projectPath tfm packageName packageVersion
86
90
 
91
+ Dictionary<string, Dictionary<string, HashSet<string>>> packageDependencies = new(PathComparer.Instance);
92
+ // projectPath tfm packageNames
93
+
87
94
  Dictionary<string, Dictionary<string, string>> resolvedProperties = new(PathComparer.Instance);
88
95
  // projectPath propertyName propertyValue
89
96
 
@@ -228,6 +235,28 @@ internal static class SdkProjectDiscovery
228
235
  }
229
236
  }
230
237
  }
238
+
239
+ // track all referenced projects in case they have no assemblies and can't be otherwise reported
240
+ if (addItem.Name.Equals("PackageDependencies", StringComparison.OrdinalIgnoreCase))
241
+ {
242
+ var projectEvaluation = GetNearestProjectEvaluation(node);
243
+ if (projectEvaluation is not null)
244
+ {
245
+ var specificPackageDeps = packageDependencies.GetOrAdd(projectEvaluation.ProjectFile, () => new(StringComparer.OrdinalIgnoreCase));
246
+ var tfm = GetPropertyValueFromProjectEvaluation(projectEvaluation, "TargetFramework");
247
+ if (tfm is not null)
248
+ {
249
+ var packagesByTfm = specificPackageDeps.GetOrAdd(tfm, () => new(StringComparer.OrdinalIgnoreCase));
250
+ foreach (var package in addItem.Children.OfType<Item>())
251
+ {
252
+ if (!NonReportedPackgeNames.Contains(package.Name))
253
+ {
254
+ packagesByTfm.Add(package.Name);
255
+ }
256
+ }
257
+ }
258
+ }
259
+ }
231
260
  }
232
261
  break;
233
262
  case Target target when target.Name == "_HandlePackageFileConflicts":
@@ -330,7 +359,7 @@ internal static class SdkProjectDiscovery
330
359
  }
331
360
 
332
361
  // and done
333
- var projectDiscoveryResults = BuildResults(
362
+ var projectDiscoveryResults = await BuildResults(
334
363
  repoRootPath,
335
364
  workspacePath,
336
365
  packagesPerProject,
@@ -338,6 +367,7 @@ internal static class SdkProjectDiscovery
338
367
  packagesReplacedBySdkPerProject,
339
368
  topLevelPackagesPerProject,
340
369
  resolvedProperties,
370
+ packageDependencies,
341
371
  referencedProjects,
342
372
  importedFiles,
343
373
  additionalFiles
@@ -345,7 +375,7 @@ internal static class SdkProjectDiscovery
345
375
  return projectDiscoveryResults;
346
376
  }
347
377
 
348
- private static ImmutableArray<ProjectDiscoveryResult> BuildResults(
378
+ private static async Task<ImmutableArray<ProjectDiscoveryResult>> BuildResults(
349
379
  string repoRootPath,
350
380
  string workspacePath,
351
381
  Dictionary<string, Dictionary<string, Dictionary<string, string>>> packagesPerProject,
@@ -353,13 +383,14 @@ internal static class SdkProjectDiscovery
353
383
  Dictionary<string, Dictionary<string, Dictionary<string, string>>> packagesReplacedBySdkPerProject,
354
384
  Dictionary<string, Dictionary<string, HashSet<string>>> topLevelPackagesPerProject,
355
385
  Dictionary<string, Dictionary<string, string>> resolvedProperties,
386
+ Dictionary<string, Dictionary<string, HashSet<string>>> packageDependencies,
356
387
  Dictionary<string, HashSet<string>> referencedProjects,
357
388
  Dictionary<string, HashSet<string>> importedFiles,
358
389
  Dictionary<string, HashSet<string>> additionalFiles
359
390
  )
360
391
  {
361
392
  var projectDiscoveryResults = new List<ProjectDiscoveryResult>();
362
- foreach (var projectPath in packagesPerProject.Keys.OrderBy(p => p)) //packagesPerProject.Keys.OrderBy(p => p).Select(projectPath =>
393
+ foreach (var projectPath in packagesPerProject.Keys.OrderBy(p => p))
363
394
  {
364
395
  // gather some project-level information
365
396
  var packagesByTfm = packagesPerProject[projectPath];
@@ -400,18 +431,99 @@ internal static class SdkProjectDiscovery
400
431
  .SelectMany(kvp => kvp.Value)
401
432
  .ToHashSet(StringComparer.OrdinalIgnoreCase);
402
433
 
434
+ var propertiesForProject = resolvedProperties.GetOrAdd(projectPath, () => new(StringComparer.OrdinalIgnoreCase));
435
+ var assetsJson = new Lazy<JsonElement?>(() =>
436
+ {
437
+ if (propertiesForProject.TryGetValue("ProjectAssetsFile", out var assetsFilePath))
438
+ {
439
+ var assetsContent = File.ReadAllText(assetsFilePath);
440
+ var assets = JsonDocument.Parse(assetsContent).RootElement;
441
+ return assets;
442
+ }
443
+
444
+ return null;
445
+ });
446
+
403
447
  // create dependencies
404
448
  var tfms = packagesByTfm.Keys.OrderBy(tfm => tfm).ToImmutableArray();
405
- var dependencies = tfms.SelectMany(tfm =>
449
+ var groupedDependencies = new Dictionary<string, Dependency>(StringComparer.OrdinalIgnoreCase);
450
+ foreach (var tfm in tfms)
406
451
  {
407
- return packagesByTfm[tfm].Keys.OrderBy(p => p).Select(packageName =>
452
+ var parsedTfm = NuGetFramework.Parse(tfm);
453
+ var packages = packagesByTfm[tfm];
454
+
455
+ // augment with any packages that might not have reported assemblies
456
+ var assetsPackageVersions = new Lazy<Dictionary<string, string>>(() =>
457
+ {
458
+ var result = new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase);
459
+ if (assetsJson.Value is { } assets &&
460
+ assets.TryGetProperty("targets", out var tfmObjects))
461
+ {
462
+ foreach (var tfmObject in tfmObjects.EnumerateObject())
463
+ {
464
+ // TFM might have a RID suffix after a slash that we can't parse
465
+ var tfmParts = tfmObject.Name.Split('/');
466
+ var reportedTargetFramework = NuGetFramework.Parse(tfmParts[0]);
467
+ if (reportedTargetFramework == parsedTfm)
468
+ {
469
+ foreach (var packageObject in tfmObject.Value.EnumerateObject())
470
+ {
471
+ var parts = packageObject.Name.Split('/');
472
+ if (parts.Length == 2)
473
+ {
474
+ var packageName = parts[0];
475
+ var packageVersion = parts[1];
476
+ result[packageName] = packageVersion;
477
+ }
478
+ }
479
+ }
480
+ }
481
+ }
482
+
483
+ return result;
484
+ });
485
+ var packageDepsForProject = packageDependencies.GetOrAdd(projectPath, () => new(StringComparer.OrdinalIgnoreCase));
486
+ var packageDepsForTfm = packageDepsForProject.GetOrAdd(tfm, () => new(StringComparer.OrdinalIgnoreCase));
487
+ foreach (var packageDepName in packageDepsForTfm)
488
+ {
489
+ if (packages.ContainsKey(packageDepName))
490
+ {
491
+ // we already know about this
492
+ continue;
493
+ }
494
+
495
+ // otherwise find the corresponding version through project.assets.json
496
+ if (assetsPackageVersions.Value.TryGetValue(packageDepName, out var packageDepVersion))
497
+ {
498
+ packages[packageDepName] = packageDepVersion;
499
+ }
500
+ }
501
+
502
+ foreach (var package in packages)
408
503
  {
409
- var packageVersion = packagesByTfm[tfm][packageName]!;
504
+ var packageName = package.Key;
505
+ var packageVersion = package.Value;
410
506
  var isTopLevel = topLevelPackageNames.Contains(packageName);
411
507
  var dependencyType = isTopLevel ? DependencyType.PackageReference : DependencyType.Unknown;
412
- return new Dependency(packageName, packageVersion, dependencyType, TargetFrameworks: [tfm], IsDirect: isTopLevel, IsTransitive: !isTopLevel);
413
- });
414
- }).ToImmutableArray();
508
+ var combinedTfms = new HashSet<string>([tfm], StringComparer.OrdinalIgnoreCase);
509
+ if (groupedDependencies.TryGetValue(packageName, out var existingDependency) &&
510
+ existingDependency.Version == packageVersion &&
511
+ existingDependency.Type == dependencyType &&
512
+ existingDependency.TargetFrameworks is not null)
513
+ {
514
+ // same dependency, combine tfms
515
+ combinedTfms.AddRange(existingDependency.TargetFrameworks);
516
+ }
517
+
518
+ var normalizedTfms = combinedTfms.OrderBy(t => t).ToImmutableArray();
519
+ groupedDependencies[package.Key] = new Dependency(packageName, packageVersion, dependencyType, TargetFrameworks: normalizedTfms, IsDirect: isTopLevel, IsTransitive: !isTopLevel);
520
+ }
521
+ }
522
+
523
+ var dependencies = groupedDependencies.Values
524
+ .OrderBy(d => d.Name)
525
+ .ThenBy(d => d.Version)
526
+ .ToImmutableArray();
415
527
 
416
528
  // others
417
529
  var properties = resolvedProperties[projectPath]
@@ -27,6 +27,12 @@ public abstract record JobErrorBase : MessageBase
27
27
  report.AppendLine($"Error type: {Type}");
28
28
  foreach (var (key, value) in Details)
29
29
  {
30
+ if (this is UnknownError && key == "error-backtrace")
31
+ {
32
+ // there's nothing meaningful in this field
33
+ continue;
34
+ }
35
+
30
36
  var valueString = value.ToString();
31
37
  if (value is IEnumerable<string> strings)
32
38
  {
@@ -44,6 +50,7 @@ public abstract record JobErrorBase : MessageBase
44
50
  return ex switch
45
51
  {
46
52
  BadRequirementException badRequirement => new BadRequirement(badRequirement.Message),
53
+ BadResponseException badResponse => new PrivateSourceBadResponse([badResponse.Uri]),
47
54
  DependencyNotFoundException dependencyNotFound => new DependencyNotFound(string.Join(", ", dependencyNotFound.Dependencies)),
48
55
  HttpRequestException httpRequest => httpRequest.StatusCode switch
49
56
  {
@@ -1,13 +1,23 @@
1
+ using System.Text.Json.Serialization;
2
+
1
3
  namespace NuGetUpdater.Core.Run.ApiModel;
2
4
 
3
5
  public record UnknownError : JobErrorBase
4
6
  {
7
+ [JsonIgnore]
8
+ public Exception Exception { get; init; }
9
+
5
10
  public UnknownError(Exception ex, string jobId)
6
11
  : base("unknown_error")
7
12
  {
13
+ Exception = ex;
14
+
15
+ // The following object is parsed by the server and the `error-backtrace` property is expected to be a Ruby
16
+ // stacktrace. Since we're not in Ruby we can set an empty string there and append the .NET stacktrace to
17
+ // the message.
8
18
  Details["error-class"] = ex.GetType().Name;
9
- Details["error-message"] = ex.Message;
10
- Details["error-backtrace"] = ex.StackTrace ?? "<unknown>";
19
+ Details["error-message"] = ex.ToString();
20
+ Details["error-backtrace"] = "";
11
21
  Details["package-manager"] = "nuget";
12
22
  Details["job-id"] = jobId;
13
23
  }
@@ -2,8 +2,6 @@ using System.Text;
2
2
  using System.Text.Json;
3
3
  using System.Text.Json.Serialization;
4
4
 
5
- using NuGetUpdater.Core.Run.ApiModel;
6
-
7
5
  namespace NuGetUpdater.Core.Run;
8
6
 
9
7
  public class HttpApiHandler : IApiHandler
@@ -25,39 +23,27 @@ public class HttpApiHandler : IApiHandler
25
23
  _jobId = jobId;
26
24
  }
27
25
 
28
- public async Task RecordUpdateJobError(JobErrorBase error)
29
- {
30
- await PostAsJson("record_update_job_error", error);
31
- }
32
-
33
- public async Task UpdateDependencyList(UpdatedDependencyList updatedDependencyList)
26
+ public async Task SendAsync(string endpoint, object body, string method)
34
27
  {
35
- await PostAsJson("update_dependency_list", updatedDependencyList);
36
- }
37
-
38
- public async Task IncrementMetric(IncrementMetric incrementMetric)
39
- {
40
- await PostAsJson("increment_metric", incrementMetric);
41
- }
42
-
43
- public async Task CreatePullRequest(CreatePullRequest createPullRequest)
44
- {
45
- await PostAsJson("create_pull_request", createPullRequest);
46
- }
47
-
48
- public async Task ClosePullRequest(ClosePullRequest closePullRequest)
49
- {
50
- await PostAsJson("close_pull_request", closePullRequest);
51
- }
52
-
53
- public async Task UpdatePullRequest(UpdatePullRequest updatePullRequest)
54
- {
55
- await PostAsJson("update_pull_request", updatePullRequest);
56
- }
28
+ var uri = $"{_apiUrl}/update_jobs/{_jobId}/{endpoint}";
29
+ var payload = Serialize(body);
30
+ var content = new StringContent(payload, Encoding.UTF8, "application/json");
31
+ var httpMethod = new HttpMethod(method);
32
+ var message = new HttpRequestMessage(httpMethod, uri)
33
+ {
34
+ Content = content
35
+ };
36
+ var response = await HttpClient.SendAsync(message);
37
+ if (!response.IsSuccessStatusCode)
38
+ {
39
+ var responseContent = await response.Content.ReadAsStringAsync();
40
+ if (!string.IsNullOrEmpty(responseContent))
41
+ {
42
+ responseContent = string.Concat(": ", responseContent);
43
+ }
57
44
 
58
- public async Task MarkAsProcessed(MarkAsProcessed markAsProcessed)
59
- {
60
- await PatchAsJson("mark_as_processed", markAsProcessed);
45
+ throw new HttpRequestException(message: $"{(int)response.StatusCode} ({response.StatusCode}){responseContent}", inner: null, statusCode: response.StatusCode);
46
+ }
61
47
  }
62
48
 
63
49
  internal static string Serialize(object body)
@@ -69,21 +55,4 @@ public class HttpApiHandler : IApiHandler
69
55
  var payload = JsonSerializer.Serialize(wrappedBody, SerializerOptions);
70
56
  return payload;
71
57
  }
72
-
73
- private Task PostAsJson(string endpoint, object body) => SendAsJson(endpoint, body, "POST");
74
- private Task PatchAsJson(string endpoint, object body) => SendAsJson(endpoint, body, "PATCH");
75
-
76
- private async Task SendAsJson(string endpoint, object body, string method)
77
- {
78
- var uri = $"{_apiUrl}/update_jobs/{_jobId}/{endpoint}";
79
- var payload = Serialize(body);
80
- var content = new StringContent(payload, Encoding.UTF8, "application/json");
81
- var httpMethod = new HttpMethod(method);
82
- var message = new HttpRequestMessage(httpMethod, uri)
83
- {
84
- Content = content
85
- };
86
- var response = await HttpClient.SendAsync(message);
87
- var _ = response.EnsureSuccessStatusCode();
88
- }
89
58
  }
@@ -4,11 +4,37 @@ namespace NuGetUpdater.Core.Run;
4
4
 
5
5
  public interface IApiHandler
6
6
  {
7
- Task RecordUpdateJobError(JobErrorBase error);
8
- Task UpdateDependencyList(UpdatedDependencyList updatedDependencyList);
9
- Task IncrementMetric(IncrementMetric incrementMetric);
10
- Task CreatePullRequest(CreatePullRequest createPullRequest);
11
- Task ClosePullRequest(ClosePullRequest closePullRequest);
12
- Task UpdatePullRequest(UpdatePullRequest updatePullRequest);
13
- Task MarkAsProcessed(MarkAsProcessed markAsProcessed);
7
+ Task SendAsync(string endpoint, object body, string method);
8
+ }
9
+
10
+ public static class IApiHandlerExtensions
11
+ {
12
+ public static async Task RecordUpdateJobError(this IApiHandler handler, JobErrorBase error)
13
+ {
14
+ await handler.PostAsJson("record_update_job_error", error);
15
+ if (error is UnknownError unknown)
16
+ {
17
+ await handler.PostAsJson("record_update_job_unknown_error", error);
18
+ var increment = new IncrementMetric()
19
+ {
20
+ Metric = "updater.update_job_unknown_error",
21
+ Tags =
22
+ {
23
+ ["package_manager"] = "nuget",
24
+ ["class_name"] = unknown.Exception.GetType().Name
25
+ },
26
+ };
27
+ await handler.IncrementMetric(increment);
28
+ }
29
+ }
30
+
31
+ public static Task UpdateDependencyList(this IApiHandler handler, UpdatedDependencyList updatedDependencyList) => handler.PostAsJson("update_dependency_list", updatedDependencyList);
32
+ public static Task IncrementMetric(this IApiHandler handler, IncrementMetric incrementMetric) => handler.PostAsJson("increment_metric", incrementMetric);
33
+ public static Task CreatePullRequest(this IApiHandler handler, CreatePullRequest createPullRequest) => handler.PostAsJson("create_pull_request", createPullRequest);
34
+ public static Task ClosePullRequest(this IApiHandler handler, ClosePullRequest closePullRequest) => handler.PostAsJson("close_pull_request", closePullRequest);
35
+ public static Task UpdatePullRequest(this IApiHandler handler, UpdatePullRequest updatePullRequest) => handler.PostAsJson("update_pull_request", updatePullRequest);
36
+ public static Task MarkAsProcessed(this IApiHandler handler, MarkAsProcessed markAsProcessed) => handler.PatchAsJson("mark_as_processed", markAsProcessed);
37
+
38
+ private static Task PostAsJson(this IApiHandler handler, string endpoint, object body) => handler.SendAsync(endpoint, body, "POST");
39
+ private static Task PatchAsJson(this IApiHandler handler, string endpoint, object body) => handler.SendAsync(endpoint, body, "PATCH");
14
40
  }
@@ -223,12 +223,17 @@ public class RunWorker
223
223
  {
224
224
  if (!job.UpdatingAPullRequest)
225
225
  {
226
- var existingPullRequest = job.GetExistingPullRequestForDependency(analysisResult.UpdatedDependencies.First(d => d.Name.Equals(dependency.Name, StringComparison.OrdinalIgnoreCase)));
227
- if (existingPullRequest is not null)
226
+ var updatedDependencyFromAnalysis = analysisResult.UpdatedDependencies
227
+ .FirstOrDefault(d => d.Name.Equals(dependency.Name, StringComparison.OrdinalIgnoreCase));
228
+ if (updatedDependencyFromAnalysis is not null)
228
229
  {
229
- await SendApiMessage(new PullRequestExistsForLatestVersion(dependency.Name, analysisResult.UpdatedVersion));
230
- unhandledPullRequestDependenciesSet.RemoveWhere(handled => handled.Count == 1 && handled.Contains(dependency.Name));
231
- continue;
230
+ var existingPullRequest = job.GetExistingPullRequestForDependency(updatedDependencyFromAnalysis);
231
+ if (existingPullRequest is not null)
232
+ {
233
+ await SendApiMessage(new PullRequestExistsForLatestVersion(dependency.Name, analysisResult.UpdatedVersion));
234
+ unhandledPullRequestDependenciesSet.RemoveWhere(handled => handled.Count == 1 && handled.Contains(dependency.Name));
235
+ continue;
236
+ }
232
237
  }
233
238
  }
234
239
 
@@ -1,5 +1,6 @@
1
1
  using System.Collections.Immutable;
2
2
  using System.Text.Json;
3
+ using System.Text.RegularExpressions;
3
4
 
4
5
  using Microsoft.Language.Xml;
5
6
 
@@ -526,7 +527,7 @@ internal static class PackageReferenceUpdater
526
527
  /// <returns>The updated files.</returns>
527
528
  private static async Task<IEnumerable<string>> AddTransitiveDependencyAsync(string repoRootPath, string projectPath, string dependencyName, string newDependencyVersion, ExperimentsManager experimentsManager, ILogger logger)
528
529
  {
529
- var updatedFiles = new[] { projectPath }; // assume this worked unless...
530
+ var updatedFiles = new List<string>() { projectPath }; // assume this worked unless...
530
531
  var projectDirectory = Path.GetDirectoryName(projectPath)!;
531
532
  await MSBuildHelper.HandleGlobalJsonAsync(projectDirectory, repoRootPath, experimentsManager, async () =>
532
533
  {
@@ -545,6 +546,17 @@ internal static class PackageReferenceUpdater
545
546
  updatedFiles = [];
546
547
  }
547
548
 
549
+ // output might contain a line like this:
550
+ // info : PackageReference for package 'Some.Package' added to 'C:\project.csproj' and PackageVersion added to central package management file 'C:\Directory.Packages.props'.
551
+ // we explicitly want to pull out this: ^^^^^^^^^^^^^^^^^^^^^^^^^^^
552
+ // so we can report all files updated on disk
553
+ var match = Regex.Match(stdout, @"added to central package management file '(?<CentralPackageManagementFilePath>[^']+)'");
554
+ if (match.Success)
555
+ {
556
+ var cpmFilePath = match.Groups["CentralPackageManagementFilePath"].Value;
557
+ updatedFiles.Add(cpmFilePath);
558
+ }
559
+
548
560
  return exitCode;
549
561
  }, logger, retainMSBuildSdks: true);
550
562
 
@@ -1029,6 +1029,7 @@ internal static partial class MSBuildHelper
1029
1029
  new Regex(@"Unable to find package (?<PackageName>[^ ]+)\. No packages exist with this id in source\(s\): (?<PackageSource>.*)$", RegexOptions.Multiline),
1030
1030
  new Regex(@"Unable to find package (?<PackageName>[^ ]+) with version \((?<PackageVersion>[^)]+)\)"),
1031
1031
  new Regex(@"Could not resolve SDK ""(?<PackageName>[^ ]+)""\."),
1032
+ new Regex(@"Failed to fetch results from V2 feed at '.*FindPackagesById\(\)\?id='(?<PackageName>[^']+)'&semVerLevel=2\.0\.0' with following message : Response status code does not indicate success: 404\."),
1032
1033
  };
1033
1034
  var matches = patterns.Select(p => p.Match(output)).Where(m => m.Success);
1034
1035
  if (matches.Any())
@@ -1,9 +1,12 @@
1
1
  using System.Collections.Immutable;
2
+ using System.Text.Json;
2
3
 
3
4
  using NuGet.Frameworks;
4
5
  using NuGet.Versioning;
5
6
 
6
7
  using NuGetUpdater.Core.Analyze;
8
+ using NuGetUpdater.Core.Run;
9
+ using NuGetUpdater.Core.Run.ApiModel;
7
10
  using NuGetUpdater.Core.Test.Update;
8
11
  using NuGetUpdater.Core.Test.Utilities;
9
12
 
@@ -11,7 +14,7 @@ using Xunit;
11
14
 
12
15
  namespace NuGetUpdater.Core.Test.Analyze;
13
16
 
14
- public class VersionFinderTests
17
+ public class VersionFinderTests : TestBase
15
18
  {
16
19
  [Fact]
17
20
  public void VersionFilter_VersionInIgnoredVersions_ReturnsFalse()
@@ -223,4 +226,74 @@ public class VersionFinderTests
223
226
  var expected = new[] { "2.0.0" };
224
227
  AssertEx.Equal(expected, actual);
225
228
  }
229
+
230
+ [Fact]
231
+ public async Task FeedReturnsBadJson()
232
+ {
233
+ // arrange
234
+ using var http = TestHttpServer.CreateTestStringServer(url =>
235
+ {
236
+ var uri = new Uri(url, UriKind.Absolute);
237
+ var baseUrl = $"{uri.Scheme}://{uri.Host}:{uri.Port}";
238
+ return uri.PathAndQuery switch
239
+ {
240
+ // initial and search query are good, update should be possible...
241
+ "/index.json" => (200, $$"""
242
+ {
243
+ "version": "3.0.0",
244
+ "resources": [
245
+ {
246
+ "@id": "{{baseUrl}}/download",
247
+ "@type": "PackageBaseAddress/3.0.0"
248
+ },
249
+ {
250
+ "@id": "{{baseUrl}}/query",
251
+ "@type": "SearchQueryService"
252
+ },
253
+ {
254
+ "@id": "{{baseUrl}}/registrations",
255
+ "@type": "RegistrationsBaseUrl"
256
+ }
257
+ ]
258
+ }
259
+ """),
260
+ _ => (200, "") // empty string instead of expected JSON object
261
+ };
262
+ });
263
+ var feedUrl = $"{http.BaseUrl.TrimEnd('/')}/index.json";
264
+ using var tempDir = await TemporaryDirectory.CreateWithContentsAsync(
265
+ ("NuGet.Config", $"""
266
+ <configuration>
267
+ <packageSources>
268
+ <clear />
269
+ <add key="private_feed" value="{feedUrl}" allowInsecureConnections="true" />
270
+ </packageSources>
271
+ </configuration>
272
+ """)
273
+ );
274
+
275
+ // act
276
+ var tfm = NuGetFramework.Parse("net9.0");
277
+ var dependencyInfo = new DependencyInfo
278
+ {
279
+ Name = "Some.Dependency",
280
+ Version = "1.0.0",
281
+ IsVulnerable = false,
282
+ IgnoredVersions = [],
283
+ Vulnerabilities = [],
284
+ };
285
+ var logger = new TestLogger();
286
+ var nugetContext = new NuGetContext(tempDir.DirectoryPath);
287
+ var exception = await Assert.ThrowsAsync<BadResponseException>(async () =>
288
+ {
289
+ await VersionFinder.GetVersionsAsync([tfm], dependencyInfo, nugetContext, logger, CancellationToken.None);
290
+ });
291
+ var error = JobErrorBase.ErrorFromException(exception, "TEST-JOB-ID", tempDir.DirectoryPath);
292
+
293
+ // assert
294
+ var expected = new PrivateSourceBadResponse([feedUrl]);
295
+ var expectedJson = JsonSerializer.Serialize(expected, RunWorker.SerializerOptions);
296
+ var actualJson = JsonSerializer.Serialize(error, RunWorker.SerializerOptions);
297
+ Assert.Equal(expectedJson, actualJson);
298
+ }
226
299
  }
@@ -111,7 +111,7 @@ public class CloneWorkerTests
111
111
 
112
112
  // assert
113
113
  Assert.Equal(1, result);
114
- var expectedParseErrorObject = testApiHandler.ReceivedMessages.Single(m => m.Type == typeof(UnknownError));
114
+ var expectedParseErrorObject = testApiHandler.ReceivedMessages.First(m => m.Type == typeof(UnknownError));
115
115
  var unknownError = (UnknownError)expectedParseErrorObject.Object;
116
116
  Assert.Equal("JsonException", unknownError.Details["error-class"]);
117
117
  }