test(udt-001): backend unit and integration tests (30 tests)

This commit is contained in:
2026-04-13 21:36:09 -03:00
parent 9891f96618
commit b657dc0d2a
12 changed files with 851 additions and 0 deletions

View File

@@ -0,0 +1,127 @@
using System.IdentityModel.Tokens.Jwt;
using System.Security.Cryptography;
using Microsoft.IdentityModel.Tokens;
using SIGCM2.Domain.Entities;
using SIGCM2.Infrastructure.Security;
namespace SIGCM2.Application.Tests.Infrastructure;
public class JwtServiceTests : IDisposable
{
private readonly RSA _rsa;
private readonly JwtOptions _options;
private readonly JwtService _jwtService;
public JwtServiceTests()
{
// Generate a test RSA key pair inline (no files needed for unit tests)
_rsa = RSA.Create(2048);
_options = new JwtOptions
{
Issuer = "sigcm2.api",
Audience = "sigcm2.web",
AccessTokenMinutes = 60
};
_jwtService = new JwtService(_rsa, _options);
}
public void Dispose() => _rsa.Dispose();
// Scenario: generated token uses RS256 algorithm
[Fact]
public void GenerateAccessToken_UsesRS256Algorithm()
{
var usuario = MakeUsuario();
var token = _jwtService.GenerateAccessToken(usuario);
var handler = new JwtSecurityTokenHandler();
var parsed = handler.ReadJwtToken(token);
Assert.Equal("RS256", parsed.Header.Alg);
}
// Scenario: claims contain expected values
[Fact]
public void GenerateAccessToken_ContainsExpectedClaims()
{
var usuario = MakeUsuario();
var token = _jwtService.GenerateAccessToken(usuario);
var handler = new JwtSecurityTokenHandler();
var parsed = handler.ReadJwtToken(token);
Assert.Equal("1", parsed.Subject); // sub = user ID
Assert.Equal("sigcm2.api", parsed.Issuer); // iss
Assert.Contains("sigcm2.web", parsed.Audiences); // aud
Assert.Contains(parsed.Claims, c => c.Type == "name" && c.Value == "admin");
Assert.Contains(parsed.Claims, c => c.Type == "rol" && c.Value == "admin");
}
// Scenario: token is verifiable with the public key
[Fact]
public void GenerateAccessToken_IsVerifiableWithPublicKey()
{
var usuario = MakeUsuario();
var token = _jwtService.GenerateAccessToken(usuario);
var publicKey = RSA.Create();
publicKey.ImportRSAPublicKey(_rsa.ExportRSAPublicKey(), out _);
var validationParams = new TokenValidationParameters
{
ValidateIssuerSigningKey = true,
IssuerSigningKey = new RsaSecurityKey(publicKey),
ValidIssuer = "sigcm2.api",
ValidAudience = "sigcm2.web",
ValidateLifetime = true,
ClockSkew = TimeSpan.Zero
};
var handler = new JwtSecurityTokenHandler();
var principal = handler.ValidateToken(token, validationParams, out var validatedToken);
Assert.NotNull(principal);
Assert.IsType<JwtSecurityToken>(validatedToken);
}
// Scenario: expiry is 60 minutes from now
[Fact]
public void GenerateAccessToken_ExpiryIs60MinutesFromNow()
{
var usuario = MakeUsuario();
var before = DateTime.UtcNow;
var token = _jwtService.GenerateAccessToken(usuario);
var after = DateTime.UtcNow;
var handler = new JwtSecurityTokenHandler();
var parsed = handler.ReadJwtToken(token);
var expectedMinExpiry = before.AddMinutes(59).AddSeconds(55);
var expectedMaxExpiry = after.AddMinutes(60).AddSeconds(5);
Assert.True(parsed.ValidTo >= expectedMinExpiry, $"exp {parsed.ValidTo} < expected min {expectedMinExpiry}");
Assert.True(parsed.ValidTo <= expectedMaxExpiry, $"exp {parsed.ValidTo} > expected max {expectedMaxExpiry}");
}
// Triangulation: different user produces token with different sub claim
[Fact]
public void GenerateAccessToken_DifferentUser_DifferentSubClaim()
{
var user1 = MakeUsuario(id: 1, username: "admin");
var user2 = MakeUsuario(id: 2, username: "vendedor");
var token1 = _jwtService.GenerateAccessToken(user1);
var token2 = _jwtService.GenerateAccessToken(user2);
var handler = new JwtSecurityTokenHandler();
var parsed1 = handler.ReadJwtToken(token1);
var parsed2 = handler.ReadJwtToken(token2);
Assert.NotEqual(parsed1.Subject, parsed2.Subject);
Assert.Equal("1", parsed1.Subject);
Assert.Equal("2", parsed2.Subject);
}
private static Usuario MakeUsuario(int id = 1, string username = "admin")
=> new(id, username, "$2a$12$hash", "Administrador", "Sistema", null, "admin", "[\"*\"]", true);
}