Premiere commit - Omika
This commit is contained in:
@@ -0,0 +1,147 @@
|
||||
using Omika.Core.Observability.Identity;
|
||||
|
||||
namespace Omika.Diagnostics.Tests;
|
||||
|
||||
/// <summary>
|
||||
/// Tests unitaires de la DeviceKey (T17-B) : HMAC-SHA256 serveur, stable par
|
||||
/// (tenant, device), non réversible, tenant-scoped, séparation de domaine avec
|
||||
/// l'ActorKey, fail-open. Aucune clé de production utilisée ici (clés de test
|
||||
/// déterministes uniquement).
|
||||
/// </summary>
|
||||
public class DiagnosticDeviceKeyTests
|
||||
{
|
||||
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_SameDevice_ProducesSameKey()
|
||||
{
|
||||
var provider = new DiagnosticDeviceKeyProvider(KeyA());
|
||||
|
||||
DiagnosticDeviceKey a = provider.Compute("BATIK.01", "device-1");
|
||||
DiagnosticDeviceKey b = provider.Compute("BATIK.01", "device-1");
|
||||
|
||||
Assert.True(a.HasValue);
|
||||
Assert.Equal(a.Value, b.Value);
|
||||
Assert.Equal(a.Version, b.Version);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void SameTenant_DifferentDevice_ProducesDifferentKey()
|
||||
{
|
||||
var provider = new DiagnosticDeviceKeyProvider(KeyA());
|
||||
|
||||
DiagnosticDeviceKey a = provider.Compute("BATIK.01", "device-1");
|
||||
DiagnosticDeviceKey b = provider.Compute("BATIK.01", "device-2");
|
||||
|
||||
Assert.NotEqual(a.Value, b.Value);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void DifferentTenant_SameDevice_ProducesDifferentKey()
|
||||
{
|
||||
var provider = new DiagnosticDeviceKeyProvider(KeyA());
|
||||
|
||||
DiagnosticDeviceKey a = provider.Compute("BATIK.01", "device-1");
|
||||
DiagnosticDeviceKey b = provider.Compute("BATIK.02", "device-1");
|
||||
|
||||
Assert.NotEqual(a.Value, b.Value);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void DifferentKey_ProducesDifferentDeviceKey()
|
||||
{
|
||||
DiagnosticDeviceKey a = new DiagnosticDeviceKeyProvider(KeyA()).Compute("BATIK.01", "device-1");
|
||||
DiagnosticDeviceKey b = new DiagnosticDeviceKeyProvider(KeyB()).Compute("BATIK.01", "device-1");
|
||||
|
||||
Assert.True(a.HasValue);
|
||||
Assert.True(b.HasValue);
|
||||
Assert.NotEqual(a.Value, b.Value);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void DeviceKey_IsHexOnly_AndContainsNoIdentity()
|
||||
{
|
||||
DiagnosticDeviceKey key = new DiagnosticDeviceKeyProvider(KeyA()).Compute("BATIK.01", "device-12345");
|
||||
|
||||
Assert.NotNull(key.Value);
|
||||
Assert.Equal(64, key.Value!.Length);
|
||||
Assert.Matches("^[0-9a-f]{64}$", key.Value);
|
||||
Assert.DoesNotContain("device", key.Value, StringComparison.OrdinalIgnoreCase);
|
||||
Assert.DoesNotContain("12345", key.Value, StringComparison.Ordinal);
|
||||
Assert.DoesNotContain("BATIK", key.Value, StringComparison.OrdinalIgnoreCase);
|
||||
// La clé HMAC elle-même ne doit jamais fuiter dans le résultat.
|
||||
Assert.DoesNotContain(KeyA(), key.Value, StringComparison.OrdinalIgnoreCase);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void DeviceKey_HasStableFormat_Versioned()
|
||||
{
|
||||
DiagnosticDeviceKey key = new DiagnosticDeviceKeyProvider(KeyA()).Compute("BATIK.01", "device-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 DiagnosticDeviceKeyProvider(null),
|
||||
new DiagnosticDeviceKeyProvider(string.Empty),
|
||||
new DiagnosticDeviceKeyProvider(" "),
|
||||
new DiagnosticDeviceKeyProvider("not-base64!!!"),
|
||||
new DiagnosticDeviceKeyProvider(Convert.ToBase64String(new byte[16])) // 128 bits < 256
|
||||
};
|
||||
|
||||
foreach (DiagnosticDeviceKeyProvider provider in cases)
|
||||
{
|
||||
DiagnosticDeviceKey key = provider.Compute("BATIK.01", "device-1");
|
||||
Assert.False(key.HasValue);
|
||||
Assert.Null(key.Value);
|
||||
Assert.Null(key.Version);
|
||||
}
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void MissingIdentity_IsFailOpen_ReturnsEmpty()
|
||||
{
|
||||
var provider = new DiagnosticDeviceKeyProvider(KeyA());
|
||||
|
||||
Assert.False(provider.Compute("", "device-1").HasValue);
|
||||
Assert.False(provider.Compute(" ", "device-1").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 = DiagnosticDeviceKeyProvider.BuildCanonicalString(1, "BATIK.01", "device-1");
|
||||
string b = DiagnosticDeviceKeyProvider.BuildCanonicalString(1, "BATIK.01", "device-1");
|
||||
|
||||
Assert.Equal(a, b);
|
||||
Assert.Equal("v1|tenant:BATIK.01|device:device-1", a);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void DeviceKey_IsDomainSeparated_FromActorKey()
|
||||
{
|
||||
// Même clé HMAC, même tenant, même valeur d'identifiant : les deux pseudonymes
|
||||
// doivent rester distincts grâce au libellé de champ (device: vs collaborateur:).
|
||||
var actor = new DiagnosticActorKeyProvider(KeyA());
|
||||
var device = new DiagnosticDeviceKeyProvider(KeyA());
|
||||
|
||||
DiagnosticActorKey actorKey = actor.Compute("BATIK.01", "42");
|
||||
DiagnosticDeviceKey deviceKey = device.Compute("BATIK.01", "42");
|
||||
|
||||
Assert.True(actorKey.HasValue);
|
||||
Assert.True(deviceKey.HasValue);
|
||||
Assert.NotEqual(actorKey.Value, deviceKey.Value);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user