zcbot/windows-node/Zcbot.WindowsNode.Tests/TransferRetryPolicyTests.cs

112 lines
4.1 KiB
C#

using System.Net;
namespace Zcbot.WindowsNode.Tests;
public sealed class TransferRetryPolicyTests
{
[Theory]
[InlineData(HttpStatusCode.Unauthorized)]
[InlineData(HttpStatusCode.Forbidden)]
public void AuthenticationFailuresAreClassifiedSeparately(HttpStatusCode statusCode)
{
Assert.Equal(
TransferFailureKind.Authentication,
TransferRetryPolicy.Classify(statusCode));
Assert.Null(TransferRetryPolicy.RetryDelay(HttpMethod.Get, 0, statusCode));
}
[Theory]
[InlineData(HttpStatusCode.RequestTimeout)]
[InlineData(HttpStatusCode.TooManyRequests)]
[InlineData(HttpStatusCode.InternalServerError)]
[InlineData(HttpStatusCode.ServiceUnavailable)]
public void SafeDownloadsRetryTransientResponses(HttpStatusCode statusCode)
{
Assert.Equal(TransferFailureKind.Transient, TransferRetryPolicy.Classify(statusCode));
Assert.NotNull(TransferRetryPolicy.RetryDelay(HttpMethod.Get, 0, statusCode));
Assert.Null(TransferRetryPolicy.RetryDelay(HttpMethod.Post, 0, statusCode));
}
[Fact]
public void CancellationIsNeverClassifiedAsANetworkFailure()
{
Assert.Equal(
TransferFailureKind.Cancelled,
TransferRetryPolicy.Classify(new OperationCanceledException()));
}
[Fact]
public async Task HttpClientRetriesGetAndInjectsAuthenticationWithoutSecretsInTheUrl()
{
var handler = new SequenceHandler(
HttpStatusCode.ServiceUnavailable,
HttpStatusCode.OK);
using var client = new NodeHttpClient(CreateConfig(), handler);
using var response = await client.SendWithRetryAsync(
() => client.CreateRequest(HttpMethod.Get, "/v1/software-jobs/example/input"),
HttpCompletionOption.ResponseHeadersRead,
CancellationToken.None);
Assert.Equal(HttpStatusCode.OK, response.StatusCode);
Assert.Equal(2, handler.RequestCount);
Assert.Equal("Bearer node-secret", handler.Authorization);
Assert.Equal("node-secret", CreateConfig().NodeToken);
Assert.DoesNotContain("node-secret", handler.RequestUri!.ToString());
Assert.StartsWith("zcbot-windows-node/", handler.UserAgent);
}
[Fact]
public async Task HttpClientDoesNotRetryAuthenticationFailure()
{
var handler = new SequenceHandler(HttpStatusCode.Unauthorized, HttpStatusCode.OK);
using var client = new NodeHttpClient(CreateConfig(), handler);
using var response = await client.SendWithRetryAsync(
() => client.CreateRequest(HttpMethod.Get, "/v1/software-jobs/example/input"),
HttpCompletionOption.ResponseHeadersRead,
CancellationToken.None);
Assert.Equal(HttpStatusCode.Unauthorized, response.StatusCode);
Assert.Equal(1, handler.RequestCount);
}
private static NodeConfig CreateConfig() => new(
new Uri("https://node.example/"),
Guid.Parse("11111111-1111-1111-1111-111111111111"),
Guid.Parse("22222222-2222-2222-2222-222222222222"),
"test-node",
"node-secret",
30,
["origin.plot@v2"]);
private sealed class SequenceHandler(params HttpStatusCode[] statuses) : HttpMessageHandler
{
private int index;
internal int RequestCount { get; private set; }
internal string? Authorization { get; private set; }
internal Uri? RequestUri { get; private set; }
internal string UserAgent { get; private set; } = "";
protected override Task<HttpResponseMessage> SendAsync(
HttpRequestMessage request,
CancellationToken cancellationToken)
{
RequestCount++;
Authorization = request.Headers.Authorization?.ToString();
RequestUri = request.RequestUri;
UserAgent = request.Headers.UserAgent.ToString();
var status = statuses[Math.Min(index++, statuses.Length - 1)];
return Task.FromResult(new HttpResponseMessage(status)
{
Content = new ByteArrayContent([]),
RequestMessage = request,
});
}
}
}