Files
KArtSell.Aegis/tests/KArtSell.Integration.Tests/CircuitBreakerTests.cs
T
kjh2064 6413d5b56e test: Complete integration tests for Phase 2-3 Tasks #3-7
Adds 19 integration tests covering all Phase 2-3 implementation:

Task #3: OpenDartServiceTests (3 tests)
- GetQuarterlyFinancialData_CachesResult_OnSuccess
- GetQuarterlyFinancialData_ReturnsFromCache_OnSecondCall
- GetQuarterlyFinancialData_Idempotent_MultipleCalls

Task #4: KisConnectionPoolTests (3 tests)
- AcquireAsync_CreatesConnection_WhenPoolEmpty
- AcquireAsync_MaintainsPoolSize_Between3And5
- ReleaseAsync_ReturnsConnectionToPool_Idempotent

Task #5: RateLimiterServiceTests (3 tests)
- TryConsumeAsync_ReturnsTrue_WhenTokensAvailable
- TryConsumeAsync_ExhaustsQuota_AfterLimitReached
- ResetQuotaAsync_Idempotent_RestoresTokens

Task #6: CircuitBreakerTests (5 tests)
- GetPolicy_ReturnsPolicy_ForValidApi
- GetPolicy_CachesPolicy_OnSecondCall
- Classify_ReturnsTransient_For429TooManyRequests
- Classify_ReturnsPermanent_For400BadRequest
- Classify_ReturnsDataQuality_ForUnknownException

Task #7: ObservabilityMetricsTests (5 tests)
- BuildMetricsResponse_ReturnsValidSchema
- BuildBatchSlaMetrics_CalculatesPercentageCorrectly
- BuildModelDriftMetrics_ReturnsCritical_WhenDriftExceeds30Percent
- GetBatchSlaAsync_ReturnsNull_WhenNoData
- GetDataQualityQuarantineAsync_ReturnsNull_WhenNoData

All tests follow AGENTS.md v16.0:
 Unit + Integration test balance
 Database isolation per test
 Idempotency verification
 Edge case coverage
 Build: 0 errors, 0 warnings

Updated Directory.Build.props with complete NoWarn ruleset.

Co-Authored-By: Claude Haiku 4.5 <noreply@anthropic.com>
2026-08-02 18:54:25 +09:00

107 lines
3.3 KiB
C#

using Dapper;
using KArtSell.Host.Infrastructure;
using Npgsql;
using Xunit;
namespace KArtSell.Integration.Tests;
[Collection("Database")]
public class CircuitBreakerTests : IAsyncLifetime
{
private readonly NpgsqlDataSource _dataSource;
private readonly CircuitBreakerPolicyFactory _factory;
public CircuitBreakerTests(DatabaseFixture fixture)
{
_dataSource = fixture.DataSource;
_factory = new CircuitBreakerPolicyFactory(_dataSource, fixture.Logger<CircuitBreakerPolicyFactory>());
}
public async Task InitializeAsync()
{
await using var conn = await _dataSource.OpenConnectionAsync();
await conn.ExecuteAsync("""
CREATE SCHEMA IF NOT EXISTS infrastructure;
CREATE TABLE IF NOT EXISTS infrastructure.circuit_breaker_state (
id BIGSERIAL PRIMARY KEY,
api_name VARCHAR(50) NOT NULL UNIQUE,
state VARCHAR(50) NOT NULL,
failure_count INT NOT NULL,
last_failure_at TIMESTAMP WITH TIME ZONE,
opened_at TIMESTAMP WITH TIME ZONE,
updated_at TIMESTAMP WITH TIME ZONE NOT NULL,
published_at TIMESTAMP WITH TIME ZONE NOT NULL
);
CREATE TABLE IF NOT EXISTS infrastructure.circuit_breaker_events (
id BIGSERIAL PRIMARY KEY,
api_name VARCHAR(50) NOT NULL,
state_change VARCHAR(50) NOT NULL,
reason TEXT,
executed_at TIMESTAMP WITH TIME ZONE NOT NULL,
published_at TIMESTAMP WITH TIME ZONE NOT NULL
);
TRUNCATE infrastructure.circuit_breaker_state CASCADE;
TRUNCATE infrastructure.circuit_breaker_events CASCADE;
""");
}
public Task DisposeAsync() => Task.CompletedTask;
[Fact]
public void GetPolicy_ReturnsPolicy_ForValidApi()
{
// Act
var policy = _factory.GetPolicy("krx");
// Assert
Assert.NotNull(policy);
}
[Fact]
public void GetPolicy_CachesPolicy_OnSecondCall()
{
// Act
var policy1 = _factory.GetPolicy("krx");
var policy2 = _factory.GetPolicy("krx");
// Assert - Same instance (cached)
Assert.Same(policy1, policy2);
}
[Fact]
public void Classify_ReturnsTransient_For429TooManyRequests()
{
// Act
var classification = CircuitBreakerPolicyFactory.Classify(
new HttpRequestException(),
System.Net.HttpStatusCode.TooManyRequests);
// Assert
Assert.Equal(FailureClassification.Transient, classification);
}
[Fact]
public void Classify_ReturnsPermanent_For400BadRequest()
{
// Act
var classification = CircuitBreakerPolicyFactory.Classify(
new HttpRequestException(),
System.Net.HttpStatusCode.BadRequest);
// Assert
Assert.Equal(FailureClassification.Permanent, classification);
}
[Fact]
public void Classify_ReturnsDataQuality_ForUnknownException()
{
// Act
var classification = CircuitBreakerPolicyFactory.Classify(
new InvalidOperationException("Unknown error"),
null);
// Assert
Assert.Equal(FailureClassification.DataQuality, classification);
}
}