-
Notifications
You must be signed in to change notification settings - Fork 49
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Cargo project manager retry 500 errors. (#471)
* Cargo project manager should retry 500 errors. * Switch to polly retry
- Loading branch information
1 parent
7d9fada
commit ed0e2cf
Showing
2 changed files
with
194 additions
and
43 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -2,6 +2,7 @@ | |
|
||
namespace Microsoft.CST.OpenSource.Tests.ProjectManagerTests | ||
{ | ||
using Microsoft.CodeAnalysis.Sarif; | ||
using Microsoft.CST.OpenSource.Extensions; | ||
using Microsoft.CST.OpenSource.Model; | ||
using Microsoft.CST.OpenSource.PackageActions; | ||
|
@@ -15,6 +16,9 @@ namespace Microsoft.CST.OpenSource.Tests.ProjectManagerTests | |
using System.Linq; | ||
using System.Net; | ||
using System.Net.Http; | ||
using System.Text; | ||
using System.Text.Json; | ||
using System.Threading; | ||
using System.Threading.Tasks; | ||
using VisualStudio.TestTools.UnitTesting; | ||
|
||
|
@@ -27,15 +31,28 @@ public class CargoProjectManagerTests | |
{ "https://crates.io/api/v1/crates/rand", Resources.cargo_rand_json }, | ||
}.ToImmutableDictionary(); | ||
|
||
private readonly IDictionary<string, bool> _retryTestsPackages = new Dictionary<string, bool>() | ||
{ | ||
{ "https://crates.io/api/v1/crates/a-mazed*", true }, | ||
{ "https://raw.githubusercontent.com/rust-lang/crates.io-index/master/a-/ma/a-mazed", true}, | ||
{ "https://crates.io/api/v1/crates/A2VConverter*", false}, | ||
{ "https://raw.githubusercontent.com/rust-lang/crates.io-index/master/A2/VC/A2VConverter", false}, | ||
}.ToImmutableDictionary(); | ||
|
||
private readonly CargoProjectManager _projectManager; | ||
private readonly IHttpClientFactory _httpFactory; | ||
|
||
public CargoProjectManagerTests() | ||
{ | ||
Mock<IHttpClientFactory> mockFactory = new(); | ||
|
||
MockHttpMessageHandler mockHttp = new(); | ||
|
||
foreach ((string Url, bool ShouldSucceedAfterRetry) in _retryTestsPackages) | ||
{ | ||
ConfigureMockHttpForRetryMechanismTests(mockHttp, Url, ShouldSucceedAfterRetry); | ||
} | ||
|
||
foreach ((string url, string json) in _packages) | ||
{ | ||
MockHttpFetchResponse(HttpStatusCode.OK, url, json, mockHttp); | ||
|
@@ -47,6 +64,77 @@ public CargoProjectManagerTests() | |
_projectManager = new CargoProjectManager(".", new NoOpPackageActions(), _httpFactory); | ||
} | ||
|
||
[DataTestMethod] | ||
[DataRow("pkg:cargo/[email protected]")] | ||
public async Task GetMetadataAsyncRetries500InternalServerError(string purlString) | ||
{ | ||
PackageURL purl = new(purlString); | ||
string? sampleResult = await new StringContent(JsonSerializer.Serialize(new { Name = "sampleName", Content = "sampleContent" }), Encoding.UTF8, "application/json").ReadAsStringAsync(); | ||
|
||
string? result = await _projectManager.GetMetadataAsync(purl, useCache: false); | ||
|
||
Assert.IsNotNull(result); | ||
Assert.AreEqual(sampleResult, result); | ||
} | ||
|
||
[DataTestMethod] | ||
[DataRow("pkg:cargo/[email protected]")] | ||
public async Task DownloadVersionAsyncRetries500InternalServerError(string purlString) | ||
{ | ||
PackageURL purl = new(purlString); | ||
|
||
IEnumerable<string> downloadedPath = await _projectManager.DownloadVersionAsync(purl, doExtract: true, cached: false); | ||
|
||
Assert.IsFalse(downloadedPath.IsEmptyEnumerable()); | ||
} | ||
|
||
[DataTestMethod] | ||
[DataRow("pkg:cargo/[email protected]")] | ||
public async Task EnumerateVersionsAsyncRetries500InternalServerError(string purlString) | ||
{ | ||
PackageURL purl = new(purlString); | ||
|
||
IEnumerable<string> versionsList = await _projectManager.EnumerateVersionsAsync(purl); | ||
|
||
Assert.IsNotNull(versionsList); | ||
} | ||
|
||
[DataTestMethod] | ||
[DataRow("pkg:cargo/[email protected]")] | ||
public async Task GetMetadataAsyncThrowsExceptionAfterMaxRetries(string purlString) | ||
{ | ||
PackageURL purl = new(purlString); | ||
|
||
HttpRequestException exception = await Assert.ThrowsExceptionAsync<HttpRequestException>(() => _projectManager.GetPackageMetadataAsync(purl, useCache: false)); | ||
|
||
Assert.IsNotNull(exception); | ||
Assert.AreEqual(exception.StatusCode, HttpStatusCode.InternalServerError); | ||
} | ||
|
||
[DataTestMethod] | ||
[DataRow("pkg:cargo/[email protected]")] | ||
public async Task DownloadVersionAsyncThrowsExceptionAfterMaxRetries(string purlString) | ||
{ | ||
PackageURL purl = new(purlString); | ||
|
||
IEnumerable<string> downloadedPath = await _projectManager.DownloadVersionAsync(purl, doExtract: true, cached: false); | ||
|
||
Assert.IsTrue(downloadedPath.IsEmptyEnumerable()); | ||
} | ||
|
||
[DataTestMethod] | ||
[DataRow("pkg:cargo/[email protected]")] | ||
public async Task EnumerateVersionsAsyncThrowsExceptionAfterMaxRetries(string purlString) | ||
{ | ||
PackageURL purl = new(purlString); | ||
|
||
HttpRequestException exception = await Assert.ThrowsExceptionAsync<HttpRequestException>(() => _projectManager.EnumerateVersionsAsync(purl)); | ||
|
||
Assert.IsNotNull(exception); | ||
Assert.AreEqual(exception.StatusCode, HttpStatusCode.InternalServerError); | ||
} | ||
|
||
|
||
[DataTestMethod] | ||
[DataRow("pkg:cargo/[email protected]", "https://crates.io/api/v1/crates/rand/0.8.5/download")] | ||
[DataRow("pkg:cargo/[email protected]", "https://crates.io/api/v1/crates/quote/1.0.21/download")] | ||
|
@@ -80,7 +168,7 @@ public async Task PackageVersionExistsAsyncSucceeds(string purlString) | |
|
||
Assert.IsTrue(await _projectManager.PackageVersionExistsAsync(purl, useCache: false)); | ||
} | ||
|
||
[DataTestMethod] | ||
[DataRow("pkg:cargo/[email protected]")] | ||
public async Task PackageVersionDoesntExistsAsyncSucceeds(string purlString) | ||
|
@@ -122,7 +210,35 @@ private static void MockHttpFetchResponse( | |
httpMock | ||
.When(HttpMethod.Get, url) | ||
.Respond(statusCode, "application/json", content); | ||
} | ||
|
||
private static void ConfigureMockHttpForRetryMechanismTests(MockHttpMessageHandler mockHttp, string url, bool shouldSucceedAfterRetry = true) | ||
{ | ||
if (shouldSucceedAfterRetry) | ||
{ | ||
int callCount = 0; | ||
mockHttp | ||
.When(HttpMethod.Get, url) | ||
.Respond(_ => | ||
{ | ||
callCount++; | ||
if (callCount == 1) // Fail and return 500 on 1st attempt | ||
{ | ||
return new HttpResponseMessage(HttpStatusCode.InternalServerError); | ||
} | ||
return new HttpResponseMessage(HttpStatusCode.OK) // Succeed on subsequent attempts | ||
{ | ||
Content = new StringContent(JsonSerializer.Serialize(new { Name = "sampleName", Content = "sampleContent" }), Encoding.UTF8, "application/json") | ||
|
||
}; | ||
}); | ||
} | ||
else | ||
{ | ||
mockHttp | ||
.When(HttpMethod.Get, url) | ||
.Respond(HttpStatusCode.InternalServerError); | ||
} | ||
} | ||
} | ||
} |