using Xunit; using Npgsql; using Dapper; using KArtSell.Modules.IdentityAccess.ManageIdentityAndRoles.Features.RegisterIdentity; using KArtSell.Modules.IdentityAccess.ManageIdentityAndRoles.Features.RequestMfaSetup; using KArtSell.Modules.IdentityAccess.ManageIdentityAndRoles.Domain; namespace KArtSell.IdentityAccess.IntegrationTests.ManageIdentityAndRoles; [Collection("Database")] public class RequestMfaSetupIntegrationTests : IAsyncLifetime { private readonly string _connectionString; private NpgsqlDataSource _dataSource = null!; private RegisterIdentitySql _registerSql = null!; private RequestMfaSetupSql _mfaSql = null!; public RequestMfaSetupIntegrationTests() { _connectionString = Environment.GetEnvironmentVariable("KARTSELL_POSTGRES") ?? "Host=127.0.0.1;Port=5432;Database=kartselldb;Username=postgres;Password=postgres"; } public async Task InitializeAsync() { _dataSource = new NpgsqlDataSourceBuilder(_connectionString).Build(); _registerSql = new RegisterIdentitySql(async () => await _dataSource.OpenConnectionAsync()); _mfaSql = new RequestMfaSetupSql(async () => await _dataSource.OpenConnectionAsync()); await CleanupAsync(); } public async Task DisposeAsync() { await CleanupAsync(); await _dataSource.DisposeAsync(); } private async Task CleanupAsync() { using var conn = await _dataSource.OpenConnectionAsync(); await conn.ExecuteAsync("DELETE FROM identity.identity WHERE email LIKE 'test-mfa-integration-%'"); } [Fact] public async Task UpdateIdentityState_ActiveToMfaSetup_Success() { var email = "test-mfa-integration-001@example.com"; var displayName = "Test MFA 001"; var correlationId = Guid.NewGuid().ToString(); var identityId = Guid.NewGuid(); await _registerSql.CreateIdentityAsync(identityId, email, displayName, correlationId, CancellationToken.None); var (_, _, revision) = await _mfaSql.GetIdentityAsync(identityId, CancellationToken.None); await _mfaSql.UpdateIdentityStateAsync(identityId, IdentityState.RequiresMfaSetup, revision, CancellationToken.None); var (_, newState, _) = await _mfaSql.GetIdentityAsync(identityId, CancellationToken.None); Assert.Equal(IdentityState.RequiresMfaSetup, newState); } [Fact] public async Task UpdateIdentityState_OptimisticConcurrency_FailsOnRevisionMismatch() { var email = "test-mfa-integration-002@example.com"; var displayName = "Test MFA 002"; var correlationId = Guid.NewGuid().ToString(); var identityId = Guid.NewGuid(); await _registerSql.CreateIdentityAsync(identityId, email, displayName, correlationId, CancellationToken.None); var ex = await Assert.ThrowsAsync(async () => await _mfaSql.UpdateIdentityStateAsync(identityId, IdentityState.RequiresMfaSetup, 999, CancellationToken.None) ); Assert.Contains("concurrency", ex.Message, StringComparison.OrdinalIgnoreCase); } [Fact] public async Task GetIdentity_AfterCreate_ReturnsCorrectRevision() { var email = "test-mfa-integration-003@example.com"; var displayName = "Test MFA 003"; var correlationId = Guid.NewGuid().ToString(); var identityId = Guid.NewGuid(); await _registerSql.CreateIdentityAsync(identityId, email, displayName, correlationId, CancellationToken.None); var (_, state, revision) = await _mfaSql.GetIdentityAsync(identityId, CancellationToken.None); Assert.Equal(IdentityState.Active, state); Assert.Equal(1, revision); } [Fact] public async Task UpdateIdentityState_IncreasesRevision() { var email = "test-mfa-integration-004@example.com"; var displayName = "Test MFA 004"; var correlationId = Guid.NewGuid().ToString(); var identityId = Guid.NewGuid(); await _registerSql.CreateIdentityAsync(identityId, email, displayName, correlationId, CancellationToken.None); var (_, _, revision1) = await _mfaSql.GetIdentityAsync(identityId, CancellationToken.None); await _mfaSql.UpdateIdentityStateAsync(identityId, IdentityState.RequiresMfaSetup, revision1, CancellationToken.None); var (_, _, revision2) = await _mfaSql.GetIdentityAsync(identityId, CancellationToken.None); Assert.Equal(revision1 + 1, revision2); } [Fact] public async Task GetIdentity_NotFound_ThrowsException() { var nonExistentId = Guid.NewGuid(); var ex = await Assert.ThrowsAsync(async () => await _mfaSql.GetIdentityAsync(nonExistentId, CancellationToken.None) ); Assert.Contains("not found", ex.Message, StringComparison.OrdinalIgnoreCase); } }