diff --git a/tests/SIGCM2.Application.Tests/Domain/TokenHasherTests.cs b/tests/SIGCM2.Application.Tests/Domain/TokenHasherTests.cs new file mode 100644 index 0000000..b91e9d4 --- /dev/null +++ b/tests/SIGCM2.Application.Tests/Domain/TokenHasherTests.cs @@ -0,0 +1,50 @@ +using SIGCM2.Domain.Security; + +namespace SIGCM2.Application.Tests.Domain; + +public class TokenHasherTests +{ + [Fact] + public void Sha256Base64Url_IsDeterministic() + { + const string raw = "my_test_raw_token_value_abc123"; + + var hash1 = TokenHasher.Sha256Base64Url(raw); + var hash2 = TokenHasher.Sha256Base64Url(raw); + + Assert.Equal(hash1, hash2); + Assert.False(string.IsNullOrWhiteSpace(hash1)); + } + + [Fact] + public void Sha256Base64Url_ProducesUrlSafeString() + { + // Use many values to increase chance of hitting + / = in base64 + for (var i = 0; i < 50; i++) + { + var raw = $"token_value_{i}_padding_test_xyz"; + var hash = TokenHasher.Sha256Base64Url(raw); + + Assert.DoesNotContain('+', hash); + Assert.DoesNotContain('/', hash); + Assert.DoesNotContain('=', hash); + } + } + + [Fact] + public void Sha256Base64Url_DifferentInputsDifferentOutputs() + { + var hash1 = TokenHasher.Sha256Base64Url("token_a"); + var hash2 = TokenHasher.Sha256Base64Url("token_b"); + + Assert.NotEqual(hash1, hash2); + } + + [Fact] + public void Sha256Base64Url_ProducesExpectedLength() + { + // SHA-256 = 32 bytes. Base64url without padding: ceil(32 * 4/3) = 43 chars + var hash = TokenHasher.Sha256Base64Url("any_token_value"); + Assert.Equal(43, hash.Length); + } +}