test(udt-001): backend unit and integration tests (30 tests)
This commit is contained in:
25
tests/SIGCM2.TestSupport/SIGCM2.TestSupport.csproj
Normal file
25
tests/SIGCM2.TestSupport/SIGCM2.TestSupport.csproj
Normal file
@@ -0,0 +1,25 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
|
||||
<PropertyGroup>
|
||||
<TargetFramework>net10.0</TargetFramework>
|
||||
<ImplicitUsings>enable</ImplicitUsings>
|
||||
<Nullable>enable</Nullable>
|
||||
<IsPackable>false</IsPackable>
|
||||
<RootNamespace>SIGCM2.TestSupport</RootNamespace>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Microsoft.AspNetCore.Mvc.Testing" />
|
||||
<PackageReference Include="Microsoft.NET.Test.Sdk" />
|
||||
<PackageReference Include="Respawn" />
|
||||
<PackageReference Include="Microsoft.Data.SqlClient" />
|
||||
<PackageReference Include="xunit" />
|
||||
<PackageReference Include="Dapper" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\..\src\api\SIGCM2.Api\SIGCM2.Api.csproj" />
|
||||
<ProjectReference Include="..\..\src\api\SIGCM2.Infrastructure\SIGCM2.Infrastructure.csproj" />
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
||||
66
tests/SIGCM2.TestSupport/SqlTestFixture.cs
Normal file
66
tests/SIGCM2.TestSupport/SqlTestFixture.cs
Normal file
@@ -0,0 +1,66 @@
|
||||
using Dapper;
|
||||
using Microsoft.Data.SqlClient;
|
||||
using Respawn;
|
||||
using Xunit;
|
||||
|
||||
namespace SIGCM2.TestSupport;
|
||||
|
||||
/// <summary>
|
||||
/// Manages a real SQL Server test database.
|
||||
/// Resets state between test runs using Respawn.
|
||||
/// Seeds the admin user after each reset.
|
||||
/// </summary>
|
||||
public sealed class SqlTestFixture : IAsyncLifetime
|
||||
{
|
||||
private readonly string _connectionString;
|
||||
private SqlConnection _connection = null!;
|
||||
private Respawner _respawner = null!;
|
||||
|
||||
public SqlTestFixture(string connectionString)
|
||||
{
|
||||
_connectionString = connectionString;
|
||||
}
|
||||
|
||||
public async Task InitializeAsync()
|
||||
{
|
||||
_connection = new SqlConnection(_connectionString);
|
||||
await _connection.OpenAsync();
|
||||
|
||||
_respawner = await Respawner.CreateAsync(_connection, new RespawnerOptions
|
||||
{
|
||||
DbAdapter = DbAdapter.SqlServer
|
||||
});
|
||||
|
||||
await ResetAndSeedAsync();
|
||||
}
|
||||
|
||||
public async Task ResetAndSeedAsync()
|
||||
{
|
||||
await _respawner.ResetAsync(_connection);
|
||||
await SeedAdminAsync();
|
||||
}
|
||||
|
||||
public async Task DisposeAsync()
|
||||
{
|
||||
if (_connection is not null)
|
||||
{
|
||||
await _connection.CloseAsync();
|
||||
await _connection.DisposeAsync();
|
||||
}
|
||||
}
|
||||
|
||||
private async Task SeedAdminAsync()
|
||||
{
|
||||
const string sql = """
|
||||
SET QUOTED_IDENTIFIER ON;
|
||||
IF NOT EXISTS (SELECT 1 FROM dbo.Usuario WHERE Username = 'admin')
|
||||
INSERT INTO dbo.Usuario (Username, PasswordHash, Nombre, Apellido, Rol, PermisosJson, Activo)
|
||||
VALUES (
|
||||
'admin',
|
||||
'$2a$12$rmq6tlSAQ8WXhR2CwLCSeuwCJKz/.8Eab95UQCUNfwe4dokeOqMcW',
|
||||
'Administrador', 'Sistema', 'admin', '["*"]', 1
|
||||
);
|
||||
""";
|
||||
await _connection.ExecuteAsync(sql);
|
||||
}
|
||||
}
|
||||
94
tests/SIGCM2.TestSupport/TestWebAppFactory.cs
Normal file
94
tests/SIGCM2.TestSupport/TestWebAppFactory.cs
Normal file
@@ -0,0 +1,94 @@
|
||||
using System.Security.Cryptography;
|
||||
using Microsoft.AspNetCore.Hosting;
|
||||
using Microsoft.AspNetCore.Mvc.Testing;
|
||||
using Microsoft.Extensions.Configuration;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
using SIGCM2.Application.Abstractions.Security;
|
||||
using SIGCM2.Infrastructure.Security;
|
||||
using Xunit;
|
||||
|
||||
namespace SIGCM2.TestSupport;
|
||||
|
||||
/// <summary>
|
||||
/// WebApplicationFactory for integration tests against SIGCM2.Api.
|
||||
/// Uses SIGCM2_Test database (separate from production SIGCM2).
|
||||
/// </summary>
|
||||
public sealed class TestWebAppFactory : WebApplicationFactory<Program>, IAsyncLifetime
|
||||
{
|
||||
private const string TestConnectionString =
|
||||
"Server=TECNICA3;Database=SIGCM2_Test;User Id=desarrollo;Password=desarrollo2026;TrustServerCertificate=True;";
|
||||
|
||||
// Resolved once — absolute paths independent of working directory
|
||||
private static readonly string RepoRoot = ResolveRepoRoot();
|
||||
private static readonly string PrivateKeyPath = Path.Combine(RepoRoot, "src", "api", "SIGCM2.Api", "keys", "private.pem");
|
||||
private static readonly string PublicKeyPath = Path.Combine(RepoRoot, "src", "api", "SIGCM2.Api", "keys", "public.pem");
|
||||
|
||||
private readonly SqlTestFixture _dbFixture = new(TestConnectionString);
|
||||
|
||||
protected override void ConfigureWebHost(IWebHostBuilder builder)
|
||||
{
|
||||
// Step 1: Override configuration BEFORE services are built
|
||||
builder.ConfigureAppConfiguration((ctx, config) =>
|
||||
{
|
||||
// Clear all existing sources and rebuild with test values
|
||||
// This ensures our paths win over appsettings.json
|
||||
config.AddInMemoryCollection(new Dictionary<string, string?>
|
||||
{
|
||||
["ConnectionStrings:SqlServer"] = TestConnectionString,
|
||||
["Jwt:Issuer"] = "sigcm2.api",
|
||||
["Jwt:Audience"] = "sigcm2.web",
|
||||
["Jwt:AccessTokenMinutes"] = "60",
|
||||
["Jwt:PrivateKeyPath"] = PrivateKeyPath,
|
||||
["Jwt:PublicKeyPath"] = PublicKeyPath,
|
||||
["Jwt:PrivateKey"] = null,
|
||||
["Jwt:PublicKey"] = null,
|
||||
["Cors:AllowedOrigins:0"] = "http://localhost:5173",
|
||||
["Serilog:MinimumLevel:Default"] = "Warning",
|
||||
});
|
||||
});
|
||||
|
||||
builder.UseEnvironment("Testing");
|
||||
}
|
||||
|
||||
public async Task InitializeAsync()
|
||||
{
|
||||
await _dbFixture.InitializeAsync();
|
||||
}
|
||||
|
||||
public new async Task DisposeAsync()
|
||||
{
|
||||
await _dbFixture.DisposeAsync();
|
||||
await base.DisposeAsync();
|
||||
}
|
||||
|
||||
private static string ResolveRepoRoot()
|
||||
{
|
||||
// Walk up from AppContext.BaseDirectory looking for SIGCM2.slnx
|
||||
var dir = new DirectoryInfo(AppContext.BaseDirectory);
|
||||
while (dir is not null)
|
||||
{
|
||||
if (dir.GetFiles("SIGCM2.slnx").Length > 0)
|
||||
return dir.FullName;
|
||||
dir = dir.Parent;
|
||||
}
|
||||
|
||||
// Walk up from assembly location
|
||||
var assemblyLocation = typeof(TestWebAppFactory).Assembly.Location;
|
||||
dir = new DirectoryInfo(Path.GetDirectoryName(assemblyLocation)!);
|
||||
while (dir is not null)
|
||||
{
|
||||
if (dir.GetFiles("SIGCM2.slnx").Length > 0)
|
||||
return dir.FullName;
|
||||
dir = dir.Parent;
|
||||
}
|
||||
|
||||
// Known absolute path (last resort for this machine)
|
||||
const string knownPath = @"E:\SIG-CM2.0";
|
||||
if (Directory.Exists(knownPath) && File.Exists(Path.Combine(knownPath, "SIGCM2.slnx")))
|
||||
return knownPath;
|
||||
|
||||
throw new InvalidOperationException(
|
||||
$"Could not find repo root containing SIGCM2.slnx. " +
|
||||
$"AppContext.BaseDirectory: {AppContext.BaseDirectory}");
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user