using System.Reflection; using Omika.Core.Observability.External; namespace Omika.Tests.Observability.External; /// Validation de la configuration du pont externe (§74/§75). public class ExternalObservabilityOptionsTests { [Fact] public void Default_IsDisabled_AndNotUsable() { var options = new ExternalObservabilityOptions(); Assert.False(options.Enabled); Assert.False(options.IsUsable); Assert.Empty(options.Validate()); } [Fact] public void Disabled_IsAlwaysValid() { var options = new ExternalObservabilityOptions { Enabled = false, OtlpEndpoint = "", ExportTimeoutSeconds = 0, MaxQueueSize = 0 }; Assert.Empty(options.Validate()); } [Fact] public void Enabled_WithEmptyEndpoint_IsInvalid() { var options = new ExternalObservabilityOptions { Enabled = true, OtlpEndpoint = "" }; Assert.False(options.IsUsable); Assert.NotEmpty(options.Validate()); } [Fact] public void Enabled_WithValidHttpEndpoint_IsUsable() { var options = new ExternalObservabilityOptions { Enabled = true, OtlpEndpoint = "http://10.0.0.10:4318" }; Assert.True(options.IsUsable); Assert.Empty(options.Validate()); } [Fact] public void Enabled_WithNonHttpEndpoint_IsInvalid() { var options = new ExternalObservabilityOptions { Enabled = true, OtlpEndpoint = "ftp://collector:4318" }; Assert.False(options.IsUsable); Assert.Contains(options.Validate(), e => e.Contains("http", StringComparison.OrdinalIgnoreCase)); } [Theory] [InlineData(0)] [InlineData(61)] public void ExportTimeoutSeconds_MustBeBetween1And60(int value) { var options = new ExternalObservabilityOptions { Enabled = true, OtlpEndpoint = "http://localhost:4318", ExportTimeoutSeconds = value }; Assert.False(options.IsUsable); } [Theory] [InlineData(0)] [InlineData(-1)] public void NonPositiveBounds_AreInvalid(int value) { var options = new ExternalObservabilityOptions { Enabled = true, OtlpEndpoint = "http://localhost:4318", MaxQueueSize = value }; Assert.False(options.IsUsable); } [Fact] public void TwoInstances_AreIndependent_NoSharedStaticState() { var a = new ExternalObservabilityOptions { Enabled = true, OtlpEndpoint = "http://a:4318", MaxQueueSize = 111 }; var b = new ExternalObservabilityOptions { Enabled = false, OtlpEndpoint = "http://b:4318", MaxQueueSize = 222 }; // Chaque instance conserve ses propres valeurs (aucune contamination croisée, // aucun état statique mutable partagé). Assert.True(a.Enabled); Assert.Equal("http://a:4318", a.OtlpEndpoint); Assert.Equal(111, a.MaxQueueSize); Assert.False(b.Enabled); Assert.Equal("http://b:4318", b.OtlpEndpoint); Assert.Equal(222, b.MaxQueueSize); } [Fact] public void Options_HasNoStaticMutableState() { // Garde-fou anti-régression (§13/§28) : la config du pont externe ne doit // être pilotée par AUCUN champ static mutable (DI-friendly, isolable, testable). string[] staticMutable = typeof(ExternalObservabilityOptions) .GetFields(BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Static) .Where(f => !f.IsInitOnly && !f.IsLiteral) .Select(f => f.Name) .ToArray(); Assert.Empty(staticMutable); } }