dependabot-nuget 0.312.0 → 0.314.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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: acb9d775c29330f4cae905aa23b7ad9c36f0ed161d8cc1c2fbfc7391fb52646b
4
- data.tar.gz: 97b60783a35914969fe10efc5235a91d29a470faf40e4520cf331bf6bafe21f1
3
+ metadata.gz: 63addcf7753f130114a786641a1c8f3b82b2d8a3cd4c46e4f3d072c3d1b189f7
4
+ data.tar.gz: 36ad00f20c6beb2fd1146ddba62f9cffe9c42246e358f70fc4ec115372c93540
5
5
  SHA512:
6
- metadata.gz: d1d117e611b42497834c3276f08e35e3a70a3b2d0f960ac3d666dde3c12eb32697f911e0af3409e76500977f4c1db835c4979a85879e86bbd00e0ec0df4cc34c
7
- data.tar.gz: b3f8f76bc8be51fc23493c1660cb41e5f6b9c10721941bd98c72c49652c807bb320def722cdc3c08d822f2cd2ed0dbe993b75cd6d43d0f1593de6890a753af32
6
+ metadata.gz: 967973760977d8dce179417fe4a786c134db3a0b14fce254b303ca52c930a25b43fa990447b5fbddc36dfd6223129ba727764b628fd817598cab3f7f77feddec
7
+ data.tar.gz: 1616f671a195834522840194d936d3a301f1664f00d66201f31b27276436bdcbe5c1ed604b4d0ec06bae55276e183dfa6f464d1178f2fe0536edc421d3756f49
@@ -47,24 +47,64 @@ public abstract record JobErrorBase : MessageBase
47
47
 
48
48
  public static JobErrorBase ErrorFromException(Exception ex, string jobId, string currentDirectory)
49
49
  {
50
- return ex switch
50
+ switch (ex)
51
51
  {
52
- BadRequirementException badRequirement => new BadRequirement(badRequirement.Message),
53
- BadResponseException badResponse => new PrivateSourceBadResponse([badResponse.Uri]),
54
- DependencyNotFoundException dependencyNotFound => new DependencyNotFound(string.Join(", ", dependencyNotFound.Dependencies)),
55
- HttpRequestException httpRequest => httpRequest.StatusCode switch
56
- {
57
- HttpStatusCode.Unauthorized or
58
- HttpStatusCode.Forbidden => new PrivateSourceAuthenticationFailure(NuGetContext.GetPackageSourceUrls(currentDirectory)),
59
- HttpStatusCode.TooManyRequests => new PrivateSourceBadResponse(NuGetContext.GetPackageSourceUrls(currentDirectory)),
60
- HttpStatusCode.ServiceUnavailable => new PrivateSourceBadResponse(NuGetContext.GetPackageSourceUrls(currentDirectory)),
61
- _ => new UnknownError(ex, jobId),
62
- },
63
- InvalidProjectFileException invalidProjectFile => new DependencyFileNotParseable(invalidProjectFile.ProjectFile),
64
- MissingFileException missingFile => new DependencyFileNotFound(missingFile.FilePath, missingFile.Message),
65
- UnparseableFileException unparseableFile => new DependencyFileNotParseable(unparseableFile.FilePath, unparseableFile.Message),
66
- UpdateNotPossibleException updateNotPossible => new UpdateNotPossible(updateNotPossible.Dependencies),
67
- _ => new UnknownError(ex, jobId),
68
- };
52
+ case BadRequirementException badRequirement:
53
+ return new BadRequirement(badRequirement.Message);
54
+ case BadResponseException badResponse:
55
+ return new PrivateSourceBadResponse([badResponse.Uri]);
56
+ case DependencyNotFoundException dependencyNotFound:
57
+ return new DependencyNotFound(string.Join(", ", dependencyNotFound.Dependencies));
58
+ case HttpRequestException httpRequest:
59
+ if (httpRequest.StatusCode is null)
60
+ {
61
+ if (httpRequest.InnerException is HttpIOException ioException &&
62
+ ioException.HttpRequestError == HttpRequestError.ResponseEnded)
63
+ {
64
+ // server hung up on us
65
+ return new PrivateSourceBadResponse(NuGetContext.GetPackageSourceUrls(currentDirectory));
66
+ }
67
+
68
+ return new UnknownError(ex, jobId);
69
+ }
70
+
71
+ switch (httpRequest.StatusCode)
72
+ {
73
+ case HttpStatusCode.Unauthorized:
74
+ case HttpStatusCode.Forbidden:
75
+ return new PrivateSourceAuthenticationFailure(NuGetContext.GetPackageSourceUrls(currentDirectory));
76
+ case HttpStatusCode.TooManyRequests:
77
+ case HttpStatusCode.ServiceUnavailable:
78
+ return new PrivateSourceBadResponse(NuGetContext.GetPackageSourceUrls(currentDirectory));
79
+ default:
80
+ if ((int)httpRequest.StatusCode / 100 == 5)
81
+ {
82
+ return new PrivateSourceBadResponse(NuGetContext.GetPackageSourceUrls(currentDirectory));
83
+ }
84
+
85
+ return new UnknownError(ex, jobId);
86
+ }
87
+ case InvalidProjectFileException invalidProjectFile:
88
+ return new DependencyFileNotParseable(invalidProjectFile.ProjectFile);
89
+ case MissingFileException missingFile:
90
+ return new DependencyFileNotFound(missingFile.FilePath, missingFile.Message);
91
+ case UnparseableFileException unparseableFile:
92
+ return new DependencyFileNotParseable(unparseableFile.FilePath, unparseableFile.Message);
93
+ case UpdateNotPossibleException updateNotPossible:
94
+ return new UpdateNotPossible(updateNotPossible.Dependencies);
95
+ default:
96
+ // if a more specific inner exception was encountered, use that, otherwise...
97
+ if (ex.InnerException is not null)
98
+ {
99
+ var innerError = ErrorFromException(ex.InnerException, jobId, currentDirectory);
100
+ if (innerError is not UnknownError)
101
+ {
102
+ return innerError;
103
+ }
104
+ }
105
+
106
+ // ...return the whole thing
107
+ return new UnknownError(ex, jobId);
108
+ }
69
109
  }
70
110
  }
@@ -35,6 +35,30 @@ public static class IApiHandlerExtensions
35
35
  public static Task UpdatePullRequest(this IApiHandler handler, UpdatePullRequest updatePullRequest) => handler.PostAsJson("update_pull_request", updatePullRequest);
36
36
  public static Task MarkAsProcessed(this IApiHandler handler, MarkAsProcessed markAsProcessed) => handler.PatchAsJson("mark_as_processed", markAsProcessed);
37
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");
38
+ private static Task PostAsJson(this IApiHandler handler, string endpoint, object body) => handler.WithRetries(() => handler.SendAsync(endpoint, body, "POST"));
39
+ private static Task PatchAsJson(this IApiHandler handler, string endpoint, object body) => handler.WithRetries(() => handler.SendAsync(endpoint, body, "PATCH"));
40
+
41
+ private const int MaxRetries = 3;
42
+ private const int MinRetryDelay = 3;
43
+ private const int MaxRetryDelay = 10;
44
+
45
+ private static async Task WithRetries(this IApiHandler handler, Func<Task> action)
46
+ {
47
+ var retryCount = 0;
48
+ while (true)
49
+ {
50
+ try
51
+ {
52
+ await action();
53
+ return;
54
+ }
55
+ catch (HttpRequestException ex)
56
+ when (retryCount < MaxRetries &&
57
+ (ex.StatusCode is null || ((int)ex.StatusCode) / 100 == 5))
58
+ {
59
+ retryCount++;
60
+ await Task.Delay(TimeSpan.FromSeconds(Random.Shared.Next(MinRetryDelay, MaxRetryDelay)));
61
+ }
62
+ }
63
+ }
40
64
  }
@@ -969,6 +969,7 @@ internal static partial class MSBuildHelper
969
969
  ThrowOnUpdateNotPossible(output);
970
970
  ThrowOnRateLimitExceeded(output);
971
971
  ThrowOnServiceUnavailable(output);
972
+ ThrowOnUnparseableFile(output);
972
973
  }
973
974
 
974
975
  private static void ThrowOnUnauthenticatedFeed(string stdout)
@@ -978,6 +979,7 @@ internal static partial class MSBuildHelper
978
979
  "The plugin credential provider could not acquire credentials",
979
980
  "401 (Unauthorized)",
980
981
  "error NU1301: Unable to load the service index for source",
982
+ "Response status code does not indicate success: 401",
981
983
  "Response status code does not indicate success: 403",
982
984
  };
983
985
  if (unauthorizedMessageSnippets.Any(stdout.Contains))
@@ -1058,6 +1060,7 @@ internal static partial class MSBuildHelper
1058
1060
  new Regex(@"Could not install package '(?<PackageName>[^ ]+) (?<PackageVersion>[^']+)'. You are trying to install this package"),
1059
1061
  new Regex(@"Unable to find a version of '[^']+' that is compatible with '[^ ]+ [^ ]+ constraint: (?<PackageName>[^ ]+) \([^ ]+ (?<PackageVersion>[^)]+)\)'"),
1060
1062
  new Regex(@"the following error\(s\) may be blocking the current package operation: '(?<PackageName>[^ ]+) (?<PackageVersion>[^ ]+) constraint:"),
1063
+ new Regex(@"Unable to resolve '(?<PackageName>[^']+)'. An additional constraint '\((?<PackageVersion>[^)]+)\)' defined in packages.config prevents this operation."),
1061
1064
  };
1062
1065
  var matches = patterns.Select(p => p.Match(output)).Where(m => m.Success);
1063
1066
  if (matches.Any())
@@ -1067,6 +1070,19 @@ internal static partial class MSBuildHelper
1067
1070
  }
1068
1071
  }
1069
1072
 
1073
+ private static void ThrowOnUnparseableFile(string output)
1074
+ {
1075
+ var patterns = new[]
1076
+ {
1077
+ new Regex(@"\nAn error occurred while reading file '(?<FilePath>[^']+)': (?<Message>[^\n]*)\n"),
1078
+ };
1079
+ var match = patterns.Select(p => p.Match(output)).Where(m => m.Success).FirstOrDefault();
1080
+ if (match is not null)
1081
+ {
1082
+ throw new UnparseableFileException(match.Groups["Message"].Value, match.Groups["FilePath"].Value);
1083
+ }
1084
+ }
1085
+
1070
1086
  internal static bool TryGetGlobalJsonPath(string repoRootPath, string workspacePath, [NotNullWhen(returnValue: true)] out string? globalJsonPath)
1071
1087
  {
1072
1088
  globalJsonPath = PathHelper.GetFileInDirectoryOrParent(workspacePath, repoRootPath, "global.json", caseSensitive: false);
@@ -39,7 +39,7 @@ public class HttpApiHandlerTests
39
39
  using var http = TestHttpServer.CreateTestServer((method, url) =>
40
40
  {
41
41
  // no error content returned
42
- return (500, null);
42
+ return (400, null);
43
43
  });
44
44
  var handler = new HttpApiHandler(http.BaseUrl, "TEST-ID");
45
45
 
@@ -51,10 +51,51 @@ public class HttpApiHandlerTests
51
51
  }));
52
52
 
53
53
  // assert
54
- var expectedMessage = $"500 (InternalServerError)";
54
+ var expectedMessage = $"400 (BadRequest)";
55
55
  Assert.Equal(expectedMessage, exception.Message);
56
56
  }
57
57
 
58
+ [Fact]
59
+ public async Task ApiCallsAreAutomaticallyRetriedWhenTheServerThrowsAnError()
60
+ {
61
+ // arrange
62
+ var requestCount = 0;
63
+ using var http = TestHttpServer.CreateTestServer((method, url) =>
64
+ {
65
+ if (requestCount < 2)
66
+ {
67
+ requestCount++;
68
+ return (500, Array.Empty<byte>());
69
+ }
70
+
71
+ return (200, Array.Empty<byte>());
72
+ });
73
+ var handler = new HttpApiHandler(http.BaseUrl, "TEST-ID");
74
+
75
+ // act
76
+ await handler.IncrementMetric(new()
77
+ {
78
+ Metric = "test",
79
+ });
80
+ }
81
+
82
+ [Fact]
83
+ public async Task ApiCallsAreNotRetriedOnABadRequest()
84
+ {
85
+ // arrange
86
+ var requestCount = 0;
87
+ using var http = TestHttpServer.CreateTestServer((method, url) =>
88
+ {
89
+ requestCount++;
90
+ return (400, Array.Empty<byte>());
91
+ });
92
+ var handler = new HttpApiHandler(http.BaseUrl, "TEST-ID");
93
+
94
+ // act
95
+ await Assert.ThrowsAsync<HttpRequestException>(() => handler.IncrementMetric(new() { Metric = "test" }));
96
+ Assert.True(requestCount == 1, $"Expected only 1 request, but received {requestCount}.");
97
+ }
98
+
58
99
  [Theory]
59
100
  [MemberData(nameof(ErrorsAreSentToTheCorrectEndpointTestData))]
60
101
  public async Task ErrorsAreSentToTheCorrectEndpoint(JobErrorBase error, params string[] expectedEndpoints)
@@ -0,0 +1,86 @@
1
+ using System.Net;
2
+ using System.Text.Json;
3
+
4
+ using NuGet.Protocol.Core.Types;
5
+
6
+ using NuGetUpdater.Core.Run;
7
+ using NuGetUpdater.Core.Run.ApiModel;
8
+
9
+ using Xunit;
10
+
11
+ namespace NuGetUpdater.Core.Test.Run;
12
+
13
+ public class JobErrorBaseTests : TestBase
14
+ {
15
+ [Theory]
16
+ [MemberData(nameof(GenerateErrorFromExceptionTestData))]
17
+ public async Task GenerateErrorFromException(Exception exception, JobErrorBase expectedError)
18
+ {
19
+ // arrange
20
+ // some error types require a NuGet.Config file to be present
21
+ using var tempDir = await TemporaryDirectory.CreateWithContentsAsync(
22
+ ("NuGet.Config", """
23
+ <configuration>
24
+ <packageSources>
25
+ <clear />
26
+ <add key="some_package_feed" value="http://nuget.example.com/v3/index.json" allowInsecureConnections="true" />
27
+ </packageSources>
28
+ </configuration>
29
+ """)
30
+ );
31
+
32
+ // act
33
+ var actualError = JobErrorBase.ErrorFromException(exception, "TEST-JOB-ID", tempDir.DirectoryPath);
34
+
35
+ // assert
36
+ var actualErrorJson = JsonSerializer.Serialize(actualError, RunWorker.SerializerOptions);
37
+ var expectedErrorJson = JsonSerializer.Serialize(expectedError, RunWorker.SerializerOptions);
38
+ Assert.Equal(expectedErrorJson, actualErrorJson);
39
+ }
40
+
41
+ public static IEnumerable<object[]> GenerateErrorFromExceptionTestData()
42
+ {
43
+ // internal error from package feed
44
+ yield return
45
+ [
46
+ new HttpRequestException("nope", null, HttpStatusCode.InternalServerError),
47
+ new PrivateSourceBadResponse(["http://nuget.example.com/v3/index.json"]),
48
+ ];
49
+
50
+ // inner exception turns into private_source_bad_response; 500
51
+ yield return
52
+ [
53
+ new FatalProtocolException("nope", new HttpRequestException("nope", null, HttpStatusCode.InternalServerError)),
54
+ new PrivateSourceBadResponse(["http://nuget.example.com/v3/index.json"]),
55
+ ];
56
+
57
+ // inner exception turns into private_source_bad_response; ResponseEnded
58
+ yield return
59
+ [
60
+ new FatalProtocolException("nope", new HttpRequestException("nope", new HttpIOException(HttpRequestError.ResponseEnded))),
61
+ new PrivateSourceBadResponse(["http://nuget.example.com/v3/index.json"]),
62
+ ];
63
+
64
+ // top-level exception turns into private_source_authentication_failure
65
+ yield return
66
+ [
67
+ new HttpRequestException("nope", null, HttpStatusCode.Unauthorized),
68
+ new PrivateSourceAuthenticationFailure(["http://nuget.example.com/v3/index.json"]),
69
+ ];
70
+
71
+ // inner exception turns into private_source_authentication_failure
72
+ yield return
73
+ [
74
+ // the NuGet libraries commonly do this
75
+ new FatalProtocolException("nope", new HttpRequestException("nope", null, HttpStatusCode.Unauthorized)),
76
+ new PrivateSourceAuthenticationFailure(["http://nuget.example.com/v3/index.json"]),
77
+ ];
78
+
79
+ // unknown errors all the way down; report the initial top-level error
80
+ yield return
81
+ [
82
+ new Exception("outer", new Exception("inner")),
83
+ new UnknownError(new Exception("outer", new Exception("inner")), "TEST-JOB-ID"),
84
+ ];
85
+ }
86
+ }
@@ -1773,6 +1773,14 @@ public class MSBuildHelperTests : TestBase
1773
1773
  null,
1774
1774
  ];
1775
1775
 
1776
+ yield return
1777
+ [
1778
+ // output
1779
+ "Response status code does not indicate success: 401",
1780
+ // expectedError
1781
+ new PrivateSourceAuthenticationFailure(["http://localhost/test-feed"]),
1782
+ ];
1783
+
1776
1784
  yield return
1777
1785
  [
1778
1786
  // output
@@ -1853,6 +1861,14 @@ public class MSBuildHelperTests : TestBase
1853
1861
  new UpdateNotPossible(["Some.Package.1.2.3"]),
1854
1862
  ];
1855
1863
 
1864
+ yield return
1865
+ [
1866
+ // output
1867
+ "Unable to resolve 'Some.Package'. An additional constraint '(= 1.2.3)' defined in packages.config prevents this operation.",
1868
+ // expectedError
1869
+ new UpdateNotPossible(["Some.Package.= 1.2.3"]),
1870
+ ];
1871
+
1856
1872
  yield return
1857
1873
  [
1858
1874
  // output
@@ -1860,6 +1876,14 @@ public class MSBuildHelperTests : TestBase
1860
1876
  // expectedError
1861
1877
  new DependencyNotFound("Some.Package"),
1862
1878
  ];
1879
+
1880
+ yield return
1881
+ [
1882
+ // output
1883
+ "This part is not reported.\nAn error occurred while reading file '/path/to/packages.config': Some error message.\nThis part is not reported.",
1884
+ // expectedError
1885
+ new DependencyFileNotParseable("/path/to/packages.config", "Some error message."),
1886
+ ];
1863
1887
  }
1864
1888
 
1865
1889
  public static IEnumerable<object[]> GetTopLevelPackageDependencyInfosTestData()
metadata CHANGED
@@ -1,13 +1,13 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: dependabot-nuget
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.312.0
4
+ version: 0.314.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Dependabot
8
8
  bindir: bin
9
9
  cert_chain: []
10
- date: 2025-05-09 00:00:00.000000000 Z
10
+ date: 2025-05-22 00:00:00.000000000 Z
11
11
  dependencies:
12
12
  - !ruby/object:Gem::Dependency
13
13
  name: dependabot-common
@@ -15,14 +15,14 @@ dependencies:
15
15
  requirements:
16
16
  - - '='
17
17
  - !ruby/object:Gem::Version
18
- version: 0.312.0
18
+ version: 0.314.0
19
19
  type: :runtime
20
20
  prerelease: false
21
21
  version_requirements: !ruby/object:Gem::Requirement
22
22
  requirements:
23
23
  - - '='
24
24
  - !ruby/object:Gem::Version
25
- version: 0.312.0
25
+ version: 0.314.0
26
26
  - !ruby/object:Gem::Dependency
27
27
  name: rubyzip
28
28
  requirement: !ruby/object:Gem::Requirement
@@ -348,6 +348,7 @@ files:
348
348
  - helpers/lib/NuGetUpdater/NuGetUpdater.Core.Test/NuGetUpdater.Core.Test.csproj
349
349
  - helpers/lib/NuGetUpdater/NuGetUpdater.Core.Test/Run/EndToEndTests.cs
350
350
  - helpers/lib/NuGetUpdater/NuGetUpdater.Core.Test/Run/HttpApiHandlerTests.cs
351
+ - helpers/lib/NuGetUpdater/NuGetUpdater.Core.Test/Run/JobErrorBaseTests.cs
351
352
  - helpers/lib/NuGetUpdater/NuGetUpdater.Core.Test/Run/MessageReportTests.cs
352
353
  - helpers/lib/NuGetUpdater/NuGetUpdater.Core.Test/Run/MiscellaneousTests.cs
353
354
  - helpers/lib/NuGetUpdater/NuGetUpdater.Core.Test/Run/PullRequestMessageTests.cs
@@ -557,7 +558,7 @@ licenses:
557
558
  - MIT
558
559
  metadata:
559
560
  bug_tracker_uri: https://github.com/dependabot/dependabot-core/issues
560
- changelog_uri: https://github.com/dependabot/dependabot-core/releases/tag/v0.312.0
561
+ changelog_uri: https://github.com/dependabot/dependabot-core/releases/tag/v0.314.0
561
562
  rdoc_options: []
562
563
  require_paths:
563
564
  - lib