Files
satellite-provider/SatelliteProvider.Tests/Authentication/JwtTokenFactoryTests.cs
T
Oleksandr Bezdieniezhnykh f64d0d760a [AZ-487] fix: JWT factory + tests now pass on net8.0
- JwtTokenFactory.Create: negative `lifetime` produced Expires < NotBefore
  which `JwtSecurityToken` rejects at construction time. Shift NotBefore
  behind Expires whenever the requested lifetime is non-positive so the
  expired-token fixture round-trips and lifetime validation can fire.
- JwtTokenFactoryTests: validate against a handler with
  `MapInboundClaims = false` so assertions read the factory's own claim
  names ("sub", "email", "permissions") rather than the .NET-default
  remapped ClaimTypes.* aliases.

These were latent — masked by the CS0104 build break fixed in 753be43.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-05-11 23:45:12 +03:00

96 lines
3.3 KiB
C#

using System.IdentityModel.Tokens.Jwt;
using System.Security.Claims;
using System.Text;
using FluentAssertions;
using Microsoft.IdentityModel.Tokens;
using SatelliteProvider.Tests.TestUtilities;
namespace SatelliteProvider.Tests.Authentication;
public class JwtTokenFactoryTests
{
private const string Secret = "factory-secret-that-is-longer-than-thirty-two-bytes-bytes";
[Fact]
public void Create_ProducesTokenValidatedByMatchingParameters()
{
// Arrange — disable inbound claim remapping so the test asserts
// the factory's actual output ("sub", "email", ...) rather than
// the framework's ClaimTypes.* aliases.
var token = JwtTokenFactory.Create(Secret, subject: "alice");
var parameters = BuildParameters(Secret);
var handler = new JwtSecurityTokenHandler { MapInboundClaims = false };
// Act
var principal = handler.ValidateToken(token, parameters, out var validatedToken);
// Assert
principal.Identity!.IsAuthenticated.Should().BeTrue();
principal.FindFirst(JwtRegisteredClaimNames.Sub)!.Value.Should().Be("alice");
validatedToken.Should().BeOfType<JwtSecurityToken>();
}
[Fact]
public void Create_WithExtraClaims_PropagatesClaimsThroughValidation()
{
// Arrange
var claims = new[]
{
new Claim("email", "alice@example.com"),
new Claim("role", "operator"),
new Claim("permissions", "GPS"),
new Claim("permissions", "FL")
};
var token = JwtTokenFactory.Create(Secret, extraClaims: claims);
var handler = new JwtSecurityTokenHandler { MapInboundClaims = false };
// Act
var principal = handler.ValidateToken(token, BuildParameters(Secret), out _);
// Assert
principal.FindAll("permissions").Select(c => c.Value).Should().BeEquivalentTo(new[] { "GPS", "FL" });
principal.FindFirst("email")!.Value.Should().Be("alice@example.com");
}
[Fact]
public void CreateExpired_TokenFailsValidationWithLifetimeException()
{
// Arrange
var token = JwtTokenFactory.CreateExpired(Secret);
var handler = new JwtSecurityTokenHandler();
// Act
var act = () => handler.ValidateToken(token, BuildParameters(Secret), out _);
// Assert
act.Should().Throw<SecurityTokenExpiredException>();
}
[Fact]
public void TamperSignature_TokenFailsValidationWithSignatureException()
{
// Arrange
var token = JwtTokenFactory.Create(Secret);
var tampered = JwtTokenFactory.TamperSignature(token);
var handler = new JwtSecurityTokenHandler();
// Act
var act = () => handler.ValidateToken(tampered, BuildParameters(Secret), out _);
// Assert
act.Should().Throw<SecurityTokenInvalidSignatureException>();
}
private static TokenValidationParameters BuildParameters(string secret) => new()
{
ValidateIssuerSigningKey = true,
IssuerSigningKey = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(secret)),
ValidateLifetime = true,
ClockSkew = TimeSpan.FromSeconds(30),
ValidateIssuer = false,
ValidateAudience = false,
RequireSignedTokens = true,
RequireExpirationTime = true
};
}