mirror of
https://github.com/azaion/satellite-provider.git
synced 2026-06-21 15:21:15 +00:00
5a28f67d33
Replace 9 nearly-identical catch blocks in RegionService.ProcessRegionAsync with a single catch (Exception ex) that delegates to RegionFailureClassifier.Classify, returning a typed (category, errorMessage) pair. Preserves all original error messages stored in region summary files; failure-path call into HandleProcessingFailureAsync is unchanged. Net source delta: -38 lines in RegionService, +71 lines in new RegionFailureClassifier (pure static), +10 unit tests covering each category, precedence, status-code propagation, null guard. Tests: 68 unit (was 58) + 5 smoke + 3 stub-contract integration tests pass. Co-authored-by: Cursor <cursoragent@cursor.com>
169 lines
5.5 KiB
C#
169 lines
5.5 KiB
C#
using System.Net;
|
|
using FluentAssertions;
|
|
using SatelliteProvider.Common.Exceptions;
|
|
using SatelliteProvider.Services.RegionProcessing;
|
|
|
|
namespace SatelliteProvider.Tests;
|
|
|
|
public class RegionFailureClassifierTests
|
|
{
|
|
[Fact]
|
|
public void Classify_TaskCanceled_WhenTimeoutFired_ReturnsTimeout()
|
|
{
|
|
// Arrange
|
|
var timeoutCts = new CancellationTokenSource();
|
|
timeoutCts.Cancel();
|
|
var external = new CancellationToken();
|
|
var ex = new TaskCanceledException("timed out");
|
|
|
|
// Act
|
|
var result = RegionFailureClassifier.Classify(ex, timeoutCts, external);
|
|
|
|
// Assert
|
|
result.Category.Should().Be(RegionFailureCategory.Timeout);
|
|
result.ErrorMessage.Should().Be(RegionFailureClassifier.TimeoutMessage);
|
|
}
|
|
|
|
[Fact]
|
|
public void Classify_TaskCanceled_WhenExternalTokenCancelled_ReturnsExternalCancellation()
|
|
{
|
|
// Arrange
|
|
var timeoutCts = new CancellationTokenSource();
|
|
var externalCts = new CancellationTokenSource();
|
|
externalCts.Cancel();
|
|
var ex = new TaskCanceledException("shutdown");
|
|
|
|
// Act
|
|
var result = RegionFailureClassifier.Classify(ex, timeoutCts, externalCts.Token);
|
|
|
|
// Assert
|
|
result.Category.Should().Be(RegionFailureCategory.ExternalCancellation);
|
|
result.ErrorMessage.Should().Be(RegionFailureClassifier.ExternalCancellationMessage);
|
|
}
|
|
|
|
[Fact]
|
|
public void Classify_TaskCanceled_WithNoTokenCancelled_ReturnsTaskCanceledOther()
|
|
{
|
|
// Arrange
|
|
var timeoutCts = new CancellationTokenSource();
|
|
var ex = new TaskCanceledException("inner http timeout");
|
|
|
|
// Act
|
|
var result = RegionFailureClassifier.Classify(ex, timeoutCts, CancellationToken.None);
|
|
|
|
// Assert
|
|
result.Category.Should().Be(RegionFailureCategory.TaskCanceledOther);
|
|
result.ErrorMessage.Should().Contain("inner http timeout");
|
|
result.ErrorMessage.Should().Contain("HttpClient timeout");
|
|
}
|
|
|
|
[Fact]
|
|
public void Classify_OperationCanceled_WhenTimeoutFired_ReturnsTimeout()
|
|
{
|
|
// Arrange
|
|
var timeoutCts = new CancellationTokenSource();
|
|
timeoutCts.Cancel();
|
|
var ex = new OperationCanceledException("op cancelled");
|
|
|
|
// Act
|
|
var result = RegionFailureClassifier.Classify(ex, timeoutCts, CancellationToken.None);
|
|
|
|
// Assert
|
|
result.Category.Should().Be(RegionFailureCategory.Timeout);
|
|
result.ErrorMessage.Should().Be(RegionFailureClassifier.TimeoutMessage);
|
|
}
|
|
|
|
[Fact]
|
|
public void Classify_OperationCanceled_WithoutTimeout_ReturnsOperationCanceledOther()
|
|
{
|
|
// Arrange
|
|
var timeoutCts = new CancellationTokenSource();
|
|
var ex = new OperationCanceledException("manual abort");
|
|
|
|
// Act
|
|
var result = RegionFailureClassifier.Classify(ex, timeoutCts, CancellationToken.None);
|
|
|
|
// Assert
|
|
result.Category.Should().Be(RegionFailureCategory.OperationCanceledOther);
|
|
result.ErrorMessage.Should().Contain("manual abort");
|
|
}
|
|
|
|
[Fact]
|
|
public void Classify_RateLimitException_ReturnsRateLimit()
|
|
{
|
|
// Arrange
|
|
var timeoutCts = new CancellationTokenSource();
|
|
var ex = new RateLimitException("retries exhausted");
|
|
|
|
// Act
|
|
var result = RegionFailureClassifier.Classify(ex, timeoutCts, CancellationToken.None);
|
|
|
|
// Assert
|
|
result.Category.Should().Be(RegionFailureCategory.RateLimit);
|
|
result.ErrorMessage.Should().Contain("retries exhausted");
|
|
result.ErrorMessage.Should().Contain("Rate limit exceeded");
|
|
}
|
|
|
|
[Fact]
|
|
public void Classify_HttpRequestException_ReturnsNetwork_WithStatusCode()
|
|
{
|
|
// Arrange
|
|
var timeoutCts = new CancellationTokenSource();
|
|
var ex = new HttpRequestException("server down", inner: null, statusCode: HttpStatusCode.BadGateway);
|
|
|
|
// Act
|
|
var result = RegionFailureClassifier.Classify(ex, timeoutCts, CancellationToken.None);
|
|
|
|
// Assert
|
|
result.Category.Should().Be(RegionFailureCategory.Network);
|
|
result.ErrorMessage.Should().Contain("BadGateway");
|
|
result.ErrorMessage.Should().Contain("server down");
|
|
}
|
|
|
|
[Fact]
|
|
public void Classify_UnknownException_ReturnsUnexpected()
|
|
{
|
|
// Arrange
|
|
var timeoutCts = new CancellationTokenSource();
|
|
var ex = new InvalidOperationException("boom");
|
|
|
|
// Act
|
|
var result = RegionFailureClassifier.Classify(ex, timeoutCts, CancellationToken.None);
|
|
|
|
// Assert
|
|
result.Category.Should().Be(RegionFailureCategory.Unexpected);
|
|
result.ErrorMessage.Should().Contain("InvalidOperationException");
|
|
result.ErrorMessage.Should().Contain("boom");
|
|
}
|
|
|
|
[Fact]
|
|
public void Classify_TimeoutTakesPrecedenceOverExternalCancellation()
|
|
{
|
|
// Arrange
|
|
var timeoutCts = new CancellationTokenSource();
|
|
timeoutCts.Cancel();
|
|
var externalCts = new CancellationTokenSource();
|
|
externalCts.Cancel();
|
|
var ex = new TaskCanceledException();
|
|
|
|
// Act
|
|
var result = RegionFailureClassifier.Classify(ex, timeoutCts, externalCts.Token);
|
|
|
|
// Assert
|
|
result.Category.Should().Be(RegionFailureCategory.Timeout);
|
|
}
|
|
|
|
[Fact]
|
|
public void Classify_NullException_Throws()
|
|
{
|
|
// Arrange
|
|
var timeoutCts = new CancellationTokenSource();
|
|
|
|
// Act
|
|
Action act = () => RegionFailureClassifier.Classify(null!, timeoutCts, CancellationToken.None);
|
|
|
|
// Assert
|
|
act.Should().Throw<ArgumentNullException>();
|
|
}
|
|
}
|