using Dapper; using FluentAssertions; using Microsoft.Data.SqlClient; using SIGCM2.Infrastructure.Audit; using SIGCM2.Infrastructure.Persistence; using Xunit; namespace SIGCM2.Application.Tests.Infrastructure.Audit; /// UDT-010 Batch 5 — SecurityEventRepository integration tests against SIGCM2_Test. [Collection("Database")] public sealed class SecurityEventRepositoryTests : IAsyncLifetime { private const string ConnectionString = "Server=TECNICA3;Database=SIGCM2_Test;User Id=desarrollo;Password=desarrollo2026;TrustServerCertificate=True;"; private SqlConnection _connection = null!; private SecurityEventRepository _repo = null!; public async Task InitializeAsync() { _connection = new SqlConnection(ConnectionString); await _connection.OpenAsync(); await _connection.ExecuteAsync("DELETE FROM dbo.SecurityEvent;"); var factory = new SqlConnectionFactory(ConnectionString); _repo = new SecurityEventRepository(factory); } public async Task DisposeAsync() { await _connection.ExecuteAsync("DELETE FROM dbo.SecurityEvent;"); await _connection.CloseAsync(); await _connection.DisposeAsync(); } [Fact] public async Task InsertAsync_LoginSuccess_PersistsAllFields() { var sessionId = Guid.NewGuid(); var occurredAt = DateTime.UtcNow; var id = await _repo.InsertAsync( occurredAt: occurredAt, actorUserId: 42, attemptedUsername: null, sessionId: sessionId, action: "login", result: "success", failureReason: null, ipAddress: "1.2.3.4", userAgent: "ua/1.0", metadata: """{"route":"/login"}"""); id.Should().BeGreaterThan(0); var row = await _connection.QuerySingleAsync<(int? ActorUserId, Guid? SessionId, string Action, string Result, string? FailureReason)>( "SELECT ActorUserId, SessionId, Action, Result, FailureReason FROM dbo.SecurityEvent WHERE Id = @Id", new { Id = id }); row.ActorUserId.Should().Be(42); row.SessionId.Should().Be(sessionId); row.Action.Should().Be("login"); row.Result.Should().Be("success"); row.FailureReason.Should().BeNull(); } [Fact] public async Task InsertAsync_LoginFailure_SupportsNullActorAndAttemptedUsername() { var id = await _repo.InsertAsync( occurredAt: DateTime.UtcNow, actorUserId: null, attemptedUsername: "juan", sessionId: null, action: "login", result: "failure", failureReason: "invalid_password", ipAddress: "1.2.3.4", userAgent: null, metadata: null); id.Should().BeGreaterThan(0); var row = await _connection.QuerySingleAsync<(int? ActorUserId, string? AttemptedUsername, string Result, string? FailureReason)>( "SELECT ActorUserId, AttemptedUsername, Result, FailureReason FROM dbo.SecurityEvent WHERE Id = @Id", new { Id = id }); row.ActorUserId.Should().BeNull(); row.AttemptedUsername.Should().Be("juan"); row.Result.Should().Be("failure"); row.FailureReason.Should().Be("invalid_password"); } [Fact] public async Task InsertAsync_InvalidResult_FailsCheckConstraint() { var act = async () => await _repo.InsertAsync( DateTime.UtcNow, null, null, null, "login", "neutral", null, null, null, null); await act.Should().ThrowAsync() .Where(e => e.Message.Contains("CK_SecurityEvent_Result")); } }