Files
KArtSell.Aegis/tests/KArtSell.IdentityAccess.IntegrationTests/ManageIdentityAndRoles/RequestMfaSetupIntegrationTests.cs
T
kjh2064 dc8f3466c9 WIP: AEG-VS-01-04 Part 2 - DI setup + Endpoint refactoring (token budget constraint)
- Added IdentityAccessModule.cs with DI registration
- Added KArtSell.Modules.IdentityAccess.csproj with FastEndpoints deps
- Added project files for UnitTests & IntegrationTests
- Updated Program.cs to register IdentityAccessModule
- Updated Host.csproj to reference IdentityAccess module
- Fixed Directory.Packages.props with Moq + MS.Extensions.DependencyInjection

ISSUES (to fix next session):
- FastEndpoints Send/SendAsync/SendOkAsync method resolution incomplete
- Response record initialization requires field values
- Need to refactor endpoints to match ModelOperations pattern exactly

WORKING:
- Domain layer (IdentityState, RoleAssignmentState) 
- SQL repositories (Dapper) 
- Unit tests (RegisterIdentity, RequestMfaSetup handlers) 
- Integration test structure ready 

Next: Simplify endpoints using 'Endpoint<Req,Resp>' pattern from GetApprovalQueue sample

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

123 lines
4.8 KiB
C#

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<InvalidOperationException>(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<InvalidOperationException>(async () =>
await _mfaSql.GetIdentityAsync(nonExistentId, CancellationToken.None)
);
Assert.Contains("not found", ex.Message, StringComparison.OrdinalIgnoreCase);
}
}