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>
This commit is contained in:
@@ -0,0 +1,106 @@
|
||||
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);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user