namespace HrynCo.Common.Tests; using System.Security.Cryptography; using HrynCo.Common.Security; using FluentAssertions; using Xunit; public sealed class SecretProtectorTests { [Fact] public void Constructor_ShouldRejectMissingKey() { Action act = () => _ = new SecretProtector(string.Empty); act.Should().Throw() .WithMessage("Secret encryption key is not configured."); } [Fact] public void Constructor_ShouldRejectInvalidKeyLength() { string key = Convert.ToBase64String(RandomNumberGenerator.GetBytes(31)); Action act = () => _ = new SecretProtector(key); act.Should().Throw() .WithMessage("Secret encryption key must be 32 bytes encoded as Base64."); } [Fact] public void ProtectAndUnprotect_ShouldRoundTripPlaintext() { string key = Convert.ToBase64String(RandomNumberGenerator.GetBytes(32)); var protector = new SecretProtector(key); string protectedValue = protector.Protect("hello world"); string plaintext = protector.Unprotect(protectedValue); protectedValue.Should().StartWith("v1:"); plaintext.Should().Be("hello world"); } [Fact] public void Unprotect_ShouldRejectUnsupportedFormat() { string key = Convert.ToBase64String(RandomNumberGenerator.GetBytes(32)); var protector = new SecretProtector(key); Action act = () => protector.Unprotect("v2:payload"); act.Should().Throw() .WithMessage("Unsupported protected value format."); } [Fact] public void Unprotect_ShouldFailWhenProtectedValueIsTamperedWith() { string key = Convert.ToBase64String(RandomNumberGenerator.GetBytes(32)); var protector = new SecretProtector(key); string protectedValue = protector.Protect("hello world"); string tamperedValue = protectedValue[..^1] + (protectedValue.EndsWith('A') ? 'B' : 'A'); Action act = () => protector.Unprotect(tamperedValue); act.Should().Throw(); } [Fact] public void Unprotect_ShouldFailWhenUsingTheWrongKey() { string originalKey = Convert.ToBase64String(RandomNumberGenerator.GetBytes(32)); string otherKey = Convert.ToBase64String(RandomNumberGenerator.GetBytes(32)); var protector = new SecretProtector(originalKey); var wrongProtector = new SecretProtector(otherKey); string protectedValue = protector.Protect("hello world"); Action act = () => wrongProtector.Unprotect(protectedValue); act.Should().Throw(); } }