// Copyright © 2025 StellaOps Contributors // Licensed under AGPL-3.0-or-later using FluentAssertions; using Microsoft.Extensions.Configuration; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Logging; using Microsoft.Extensions.Logging.Abstractions; using StellaOps.Policy.Determinization; using StellaOps.Policy.Determinization.Models; using StellaOps.Policy.Determinization.Scoring; using Xunit; namespace StellaOps.Policy.Determinization.Tests.Integration; [Trait("Category", "Unit")] public sealed class ServiceRegistrationIntegrationTests { [Fact] public void AddDeterminization_WithConfiguration_RegistersAllServices() { // Arrange var configuration = new ConfigurationBuilder() .AddInMemoryCollection(new Dictionary { ["Determinization:ConfidenceHalfLifeDays"] = "21", ["Determinization:ConfidenceFloor"] = "0.15", ["Determinization:ManualReviewEntropyThreshold"] = "0.65", ["Determinization:SignalWeights:VexWeight"] = "0.30", ["Determinization:SignalWeights:EpssWeight"] = "0.15" }) .Build(); var services = new ServiceCollection(); services.AddSingleton(TimeProvider.System); services.AddLogging(); // Act services.AddDeterminization(configuration); var provider = services.BuildServiceProvider(); // Assert - verify all services are registered provider.GetService().Should().NotBeNull(); provider.GetService().Should().NotBeNull(); provider.GetService().Should().NotBeNull(); } [Fact] public void AddDeterminization_WithConfigureAction_RegistersAllServices() { // Arrange var services = new ServiceCollection(); services.AddSingleton(TimeProvider.System); services.AddLogging(); // Act services.AddDeterminization(options => { // Options are immutable records, so can't mutate // This tests that the configure action is called }); var provider = services.BuildServiceProvider(); // Assert provider.GetService().Should().NotBeNull(); provider.GetService().Should().NotBeNull(); provider.GetService().Should().NotBeNull(); } [Fact] public void RegisteredServices_AreResolvableAndFunctional() { // Arrange var configuration = new ConfigurationBuilder() .AddInMemoryCollection(new Dictionary()) .Build(); var services = new ServiceCollection(); services.AddSingleton(TimeProvider.System); services.AddLogging(b => b.AddProvider(NullLoggerProvider.Instance)); services.AddDeterminization(configuration); var provider = services.BuildServiceProvider(); // Act - resolve and use services var uncertaintyCalc = provider.GetRequiredService(); var decayCalc = provider.GetRequiredService(); var trustAgg = provider.GetRequiredService(); // Test uncertainty calculator var snapshot = SignalSnapshot.Empty("CVE-2024-1234", "pkg:maven/test@1.0", DateTimeOffset.UtcNow); // Assert - verify they work var score = uncertaintyCalc.Calculate(snapshot); score.Entropy.Should().Be(1.0); // All signals missing = maximum entropy var decayed = decayCalc.Calculate(baseConfidence: 0.9, ageDays: 14.0, halfLifeDays: 14.0); decayed.Should().BeApproximately(0.45, 0.01); // Half-life decay // Trust aggregator requires an uncertainty score var trust = trustAgg.Aggregate(snapshot, score); trust.Should().BeInRange(0.0, 1.0); } [Fact] public void RegisteredServices_AreSingletons() { // Arrange var configuration = new ConfigurationBuilder().AddInMemoryCollection(new Dictionary()).Build(); var services = new ServiceCollection(); services.AddSingleton(TimeProvider.System); services.AddLogging(); services.AddDeterminization(configuration); var provider = services.BuildServiceProvider(); // Act - resolve same service multiple times var calc1 = provider.GetService(); var calc2 = provider.GetService(); // Assert - should be same instance (singleton) calc1.Should().BeSameAs(calc2); } }