using System.Security.Claims; using Microsoft.Extensions.Logging.Abstractions; using Microsoft.Extensions.Options; using Shouldly; using Xunit; using ZB.MOM.WW.OtOpcUa.Security.Jwt; namespace ZB.MOM.WW.OtOpcUa.Security.Tests; public sealed class JwtTokenServiceTests { private const string TestKey = "this-is-a-32-byte-test-signing-key!!"; private static JwtTokenService NewService(string key = TestKey, int expiryMinutes = 15) => new(Options.Create(new JwtOptions { SigningKey = key, Issuer = "otopcua-test", Audience = "otopcua-test", ExpiryMinutes = expiryMinutes, }), NullLogger.Instance); [Fact] public void Short_signing_key_throws() { Should.Throw(() => NewService("too-short")); } [Fact] public void Issue_then_validate_roundtrips_claims() { var jwt = NewService(); var token = jwt.Issue("Joe User", "joe", new[] { "ReadOnly", "AlarmAck" }); jwt.TryValidate(token, out var principal).ShouldBeTrue(); principal.ShouldNotBeNull(); principal!.FindFirst(JwtTokenService.UsernameClaimType)!.Value.ShouldBe("joe"); principal.FindFirst(JwtTokenService.DisplayNameClaimType)!.Value.ShouldBe("Joe User"); principal.FindAll(JwtTokenService.RoleClaimType) .Select(c => c.Value).ShouldBe(new[] { "ReadOnly", "AlarmAck" }, ignoreOrder: true); } [Fact] public void Tampered_token_is_rejected() { var jwt = NewService(); var token = jwt.Issue("Joe", "joe", Array.Empty()); // Corrupt the payload — flip a char in the middle segment. var parts = token.Split('.'); parts[1] = parts[1][..^2] + "AA"; var tampered = string.Join('.', parts); jwt.TryValidate(tampered, out var principal).ShouldBeFalse(); principal.ShouldBeNull(); } [Fact] public void Expired_token_is_rejected() { // Issue with -1 min expiry so it's already past at validate time. var jwt = NewService(expiryMinutes: -1); var token = jwt.Issue("Joe", "joe", Array.Empty()); jwt.TryValidate(token, out var principal).ShouldBeFalse(); principal.ShouldBeNull(); } [Fact] public void Cross_key_token_is_rejected() { var issuer = NewService(); var token = issuer.Issue("Joe", "joe", Array.Empty()); var different = NewService("a-different-32-byte-signing-key!!!"); different.TryValidate(token, out var principal).ShouldBeFalse(); principal.ShouldBeNull(); } }