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.Clock(), fixture.Logger()); } 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); } }