Files
SIG-CM2.0/tests/SIGCM2.Api.Tests/Audit/AuditControllerTests.cs
dmolinari e5b6c06f64 refactor(tests): Api.Tests apunta a SIGCM2_Test_Api via TestConnectionStrings
Todos los archivos de Api.Tests reemplazan la connection string hardcodeada
por TestConnectionStrings.ApiTestDb. Cada proyecto de tests ahora tiene su
propia base de datos aislada, eliminando la contención entre Application.Tests
y Api.Tests que causaba flakiness.
2026-04-18 21:44:40 -03:00

126 lines
4.6 KiB
C#

using System.Net;
using System.Net.Http.Headers;
using System.Net.Http.Json;
using Dapper;
using FluentAssertions;
using Microsoft.Data.SqlClient;
using Microsoft.IdentityModel.Tokens;
using Microsoft.Extensions.DependencyInjection;
using SIGCM2.Api.Controllers;
using SIGCM2.Application.Abstractions.Security;
using SIGCM2.Domain.Entities;
using SIGCM2.TestSupport;
using Xunit;
namespace SIGCM2.Api.Tests.Audit;
/// UDT-010 Batch 10 — AuditController integration tests.
[Collection("ApiIntegration")]
public sealed class AuditControllerTests : IClassFixture<TestWebAppFactory>
{
private const string ConnectionString = TestConnectionStrings.ApiTestDb;
private readonly TestWebAppFactory _factory;
public AuditControllerTests(TestWebAppFactory factory)
{
_factory = factory;
}
private async Task<(HttpClient client, int adminId)> AuthedAdminClientAsync()
{
await using var conn = new SqlConnection(ConnectionString);
await conn.OpenAsync();
await conn.ExecuteAsync("DELETE FROM dbo.AuditEvent;");
var adminId = await conn.QuerySingleAsync<int>("SELECT Id FROM dbo.Usuario WHERE Username = 'admin'");
var client = _factory.CreateClient();
var jwt = _factory.Services.GetRequiredService<IJwtService>();
var token = jwt.GenerateAccessToken(new Usuario(
id: adminId, username: "admin", passwordHash: "x",
nombre: "Admin", apellido: "Sys", email: null,
rol: "admin", permisosJson: """{"grant":[],"deny":[]}""", activo: true));
client.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", token);
return (client, adminId);
}
[Fact]
public async Task GetEvents_WithoutPermission_Returns403()
{
var client = _factory.CreateClient();
var jwt = _factory.Services.GetRequiredService<IJwtService>();
// Use a role without administracion:auditoria:ver (cajero only has ventas:contado:*)
var operadorToken = jwt.GenerateAccessToken(new Usuario(
id: 9999, username: "opx", passwordHash: "x",
nombre: "X", apellido: "Y", email: null,
rol: "cajero", permisosJson: """{"grant":[],"deny":[]}""", activo: true));
client.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", operadorToken);
var response = await client.GetAsync("/api/v1/audit/events");
response.StatusCode.Should().Be(HttpStatusCode.Forbidden);
}
[Fact]
public async Task GetEvents_WithoutAuth_Returns401()
{
var client = _factory.CreateClient();
var response = await client.GetAsync("/api/v1/audit/events");
response.StatusCode.Should().Be(HttpStatusCode.Unauthorized);
}
[Fact]
public async Task GetEvents_AuthenticatedAdmin_ReturnsAuditEvents()
{
var (client, adminId) = await AuthedAdminClientAsync();
// Seed 3 events directly
await using var conn = new SqlConnection(ConnectionString);
await conn.OpenAsync();
for (var i = 0; i < 3; i++)
{
await conn.ExecuteAsync("""
INSERT INTO dbo.AuditEvent (OccurredAt, ActorUserId, Action, TargetType, TargetId)
VALUES (@O, @A, @Ac, 'Usuario', @T);
""", new
{
O = DateTime.UtcNow.AddSeconds(-i),
A = adminId,
Ac = $"test.seed{i}",
T = i.ToString(),
});
}
var response = await client.GetAsync("/api/v1/audit/events?targetType=Usuario");
response.StatusCode.Should().Be(HttpStatusCode.OK);
var body = await response.Content.ReadFromJsonAsync<AuditEventPageResponse>();
body.Should().NotBeNull();
body!.Items.Should().HaveCount(3);
body.Items.Should().OnlyContain(e => e.TargetType == "Usuario");
}
[Fact]
public async Task GetEvents_InvalidLimit_Returns400()
{
var (client, _) = await AuthedAdminClientAsync();
var response = await client.GetAsync("/api/v1/audit/events?limit=0");
response.StatusCode.Should().Be(HttpStatusCode.BadRequest);
var response2 = await client.GetAsync("/api/v1/audit/events?limit=101");
response2.StatusCode.Should().Be(HttpStatusCode.BadRequest);
}
[Fact]
public async Task GetEvents_FromGreaterThanTo_Returns400()
{
var (client, _) = await AuthedAdminClientAsync();
var response = await client.GetAsync(
"/api/v1/audit/events?from=2026-05-01T00:00:00Z&to=2026-04-01T00:00:00Z");
response.StatusCode.Should().Be(HttpStatusCode.BadRequest);
}
}