using FluentAssertions; using IBKRTrader.Core.Security; namespace IBKRTrader.Tests.Security; /// /// Testet die at-rest-Verschlüsselung. Nutzt den statischen SecretProtection-Zustand → nicht /// parallelisierbar mit anderen Tests dieser Collection; Reset() im Finally hält es isoliert. /// [Trait("cat", "unit")] [Collection("SecretProtection")] public class SecretProtectionTests { // 32-Byte-Key als Base64. private const string Key = "MDEyMzQ1Njc4OWFiY2RlZjAxMjM0NTY3ODlhYmNkZWY="; [Fact] public void RoundTrip_EncryptsAndDecrypts() { try { SecretProtection.Configure(Key); var enc = SecretProtection.Protect("geheim123"); enc.Should().StartWith("enc:v1:"); enc.Should().NotContain("geheim123"); SecretProtection.Unprotect(enc).Should().Be("geheim123"); } finally { SecretProtection.Reset(); } } [Fact] public void Protect_IsIdempotent_ForAlreadyEncrypted() { try { SecretProtection.Configure(Key); var enc = SecretProtection.Protect("x"); var enc2 = SecretProtection.Protect(enc); // nicht doppelt verschlüsseln enc2.Should().Be(enc); } finally { SecretProtection.Reset(); } } [Fact] public void NoKey_IsPassthrough_Plaintext() { SecretProtection.Reset(); SecretProtection.Protect("klartext").Should().Be("klartext"); SecretProtection.Unprotect("klartext").Should().Be("klartext"); // Alt-Klartext unverändert } [Fact] public void Unprotect_Encrypted_WithoutKey_Throws() { string enc; try { SecretProtection.Configure(Key); enc = SecretProtection.Protect("secret"); } finally { SecretProtection.Reset(); } var act = () => SecretProtection.Unprotect(enc); act.Should().Throw(); } [Fact] public void Unprotect_TamperedData_Throws() { try { SecretProtection.Configure(Key); var enc = SecretProtection.Protect("secret"); // Erstes Base64-Zeichen des Payloads kippen → Nonce/Ciphertext korrupt, Base64 bleibt gültig. var payload = enc[SecretProtection.Prefix.Length..].ToCharArray(); payload[0] = payload[0] == 'A' ? 'B' : 'A'; var tampered = SecretProtection.Prefix + new string(payload); var act = () => SecretProtection.Unprotect(tampered); act.Should().Throw(); } finally { SecretProtection.Reset(); } } }