using Omika.Core.Observability.Identity; namespace Omika.Diagnostics.Tests; /// /// Tests unitaires de l'ActorKey (§19) : HMAC-SHA256 serveur, stable par /// (tenant, collaborateur), non réversible, fail-open. Aucune clé de production /// utilisée ici (clés de test déterministes uniquement). /// public class DiagnosticActorKeyTests { private static string KeyA() => Convert.ToBase64String(new byte[32]); // 32 octets 0x00 private static string KeyB() => Convert.ToBase64String(Enumerable.Repeat((byte)7, 32).ToArray()); [Fact] public void SameTenant_SameCollaborateur_ProducesSameKey() { var provider = new DiagnosticActorKeyProvider(KeyA()); DiagnosticActorKey a = provider.Compute("BATIK.01", "42"); DiagnosticActorKey b = provider.Compute("BATIK.01", "42"); Assert.True(a.HasValue); Assert.Equal(a.Value, b.Value); Assert.Equal(a.Version, b.Version); } [Fact] public void DifferentTenant_SameCollaborateur_ProducesDifferentKey() { var provider = new DiagnosticActorKeyProvider(KeyA()); DiagnosticActorKey a = provider.Compute("BATIK.01", "42"); DiagnosticActorKey b = provider.Compute("BATIK.02", "42"); Assert.NotEqual(a.Value, b.Value); } [Fact] public void SameTenant_DifferentCollaborateur_ProducesDifferentKey() { var provider = new DiagnosticActorKeyProvider(KeyA()); DiagnosticActorKey a = provider.Compute("BATIK.01", "42"); DiagnosticActorKey b = provider.Compute("BATIK.01", "43"); Assert.NotEqual(a.Value, b.Value); } [Fact] public void DifferentKey_ProducesDifferentActorKey() { DiagnosticActorKey a = new DiagnosticActorKeyProvider(KeyA()).Compute("BATIK.01", "42"); DiagnosticActorKey b = new DiagnosticActorKeyProvider(KeyB()).Compute("BATIK.01", "42"); Assert.True(a.HasValue); Assert.True(b.HasValue); Assert.NotEqual(a.Value, b.Value); } [Fact] public void ActorKey_IsHexOnly_AndContainsNoNominativeData() { DiagnosticActorKey key = new DiagnosticActorKeyProvider(KeyA()).Compute("BATIK.01", "42"); Assert.NotNull(key.Value); Assert.Equal(64, key.Value!.Length); Assert.Matches("^[0-9a-f]{64}$", key.Value); Assert.DoesNotContain("42", key.Value, StringComparison.Ordinal); Assert.DoesNotContain("BATIK", key.Value, StringComparison.OrdinalIgnoreCase); Assert.DoesNotContain("collaborateur", key.Value, StringComparison.OrdinalIgnoreCase); } [Fact] public void ActorKey_HasStableFormat_Versioned() { DiagnosticActorKey key = new DiagnosticActorKeyProvider(KeyA()).Compute("BATIK.01", "7"); Assert.Equal(1, key.Version); Assert.NotNull(key.Value); Assert.Equal(64, key.Value!.Length); Assert.Equal(key.Value.ToLowerInvariant(), key.Value); // hex minuscule } [Fact] public void MissingKey_IsFailOpen_ReturnsEmpty() { var cases = new[] { new DiagnosticActorKeyProvider(null), new DiagnosticActorKeyProvider(string.Empty), new DiagnosticActorKeyProvider(" "), new DiagnosticActorKeyProvider("not-base64!!!"), new DiagnosticActorKeyProvider(Convert.ToBase64String(new byte[16])) // 128 bits < 256 }; foreach (DiagnosticActorKeyProvider provider in cases) { DiagnosticActorKey key = provider.Compute("BATIK.01", "42"); Assert.False(key.HasValue); Assert.Null(key.Value); Assert.Null(key.Version); } } [Fact] public void MissingIdentity_IsFailOpen_ReturnsEmpty() { var provider = new DiagnosticActorKeyProvider(KeyA()); Assert.False(provider.Compute("", "42").HasValue); Assert.False(provider.Compute(" ", "42").HasValue); Assert.False(provider.Compute("BATIK.01", null).HasValue); Assert.False(provider.Compute("BATIK.01", "").HasValue); Assert.False(provider.Compute("BATIK.01", " ").HasValue); } [Fact] public void CanonicalString_IsVersionedAndDeterministic() { string a = DiagnosticActorKeyProvider.BuildCanonicalString(1, "BATIK.01", "42"); string b = DiagnosticActorKeyProvider.BuildCanonicalString(1, "BATIK.01", "42"); Assert.Equal(a, b); Assert.Equal("v1|tenant:BATIK.01|collaborateur:42", a); } [Fact] public void ActorKey_DoesNotRevealIdentity_WhenComparedAcrossTenants() { // Deux collaborateurs différents ne peuvent pas être confondus, et la clé // seule ne permet pas de retrouver le CollaborateurId (§2 : non réversible). var provider = new DiagnosticActorKeyProvider(KeyA()); DiagnosticActorKey key = provider.Compute("BATIK.01", "12345678"); Assert.NotNull(key.Value); Assert.DoesNotContain("12345678", key.Value, StringComparison.Ordinal); } }