dependabot-nuget 0.312.0 → 0.313.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: ac365725d12dca90647d4d71f59d1038f9d678bc811f20ffcb2f48ec0fb5da7a
4
+ data.tar.gz: 7acd524ff51f1a2f2eba42db8711331410e740761deecb78e97365d6d5ada225
5
5
  SHA512:
6
- metadata.gz: d1d117e611b42497834c3276f08e35e3a70a3b2d0f960ac3d666dde3c12eb32697f911e0af3409e76500977f4c1db835c4979a85879e86bbd00e0ec0df4cc34c
7
- data.tar.gz: b3f8f76bc8be51fc23493c1660cb41e5f6b9c10721941bd98c72c49652c807bb320def722cdc3c08d822f2cd2ed0dbe993b75cd6d43d0f1593de6890a753af32
6
+ metadata.gz: 27bb68098a00a6e05e88e07c5adfe99c9a74fe9fe8cf43d367e77fee46e29046832f8cf7993ac02fe767709da11a570ef3900a0a8c0941bcde5f2bc19d0a6e07
7
+ data.tar.gz: 84654a79e5cfb88dbde80c8218ed8737d5211ba5f65cc974d3aca4ab5aac8feae869a5718cebb1ed9724e46891c11287854762d60d89f535a63716ea8fcca9ee
@@ -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)
@@ -1067,6 +1068,19 @@ internal static partial class MSBuildHelper
1067
1068
  }
1068
1069
  }
1069
1070
 
1071
+ private static void ThrowOnUnparseableFile(string output)
1072
+ {
1073
+ var patterns = new[]
1074
+ {
1075
+ new Regex(@"\nAn error occurred while reading file '(?<FilePath>[^']+)': (?<Message>[^\n]*)\n"),
1076
+ };
1077
+ var match = patterns.Select(p => p.Match(output)).Where(m => m.Success).FirstOrDefault();
1078
+ if (match is not null)
1079
+ {
1080
+ throw new UnparseableFileException(match.Groups["Message"].Value, match.Groups["FilePath"].Value);
1081
+ }
1082
+ }
1083
+
1070
1084
  internal static bool TryGetGlobalJsonPath(string repoRootPath, string workspacePath, [NotNullWhen(returnValue: true)] out string? globalJsonPath)
1071
1085
  {
1072
1086
  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
+ }
@@ -1860,6 +1860,14 @@ public class MSBuildHelperTests : TestBase
1860
1860
  // expectedError
1861
1861
  new DependencyNotFound("Some.Package"),
1862
1862
  ];
1863
+
1864
+ yield return
1865
+ [
1866
+ // output
1867
+ "This part is not reported.\nAn error occurred while reading file '/path/to/packages.config': Some error message.\nThis part is not reported.",
1868
+ // expectedError
1869
+ new DependencyFileNotParseable("/path/to/packages.config", "Some error message."),
1870
+ ];
1863
1871
  }
1864
1872
 
1865
1873
  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.313.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-15 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.313.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.313.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.313.0
561
562
  rdoc_options: []
562
563
  require_paths:
563
564
  - lib