using System.Collections.Immutable; using FluentAssertions; using Microsoft.Extensions.Logging.Abstractions; using StellaOps.Evidence.Models; using StellaOps.Replay.Engine; using StellaOps.Replay.Models; using StellaOps.Testing.Manifests.Models; using Xunit; using StellaOps.TestKit; namespace StellaOps.Replay.Tests; public class ReplayEngineTests { [Trait("Category", TestCategories.Unit)] [Fact] public async Task Replay_SameManifest_ProducesIdenticalVerdict() { var manifest = CreateManifest(); var engine = CreateEngine(); var result1 = await engine.ReplayAsync(manifest, new ReplayOptions()); var result2 = await engine.ReplayAsync(manifest, new ReplayOptions()); result1.VerdictDigest.Should().Be(result2.VerdictDigest); } [Trait("Category", TestCategories.Unit)] [Fact] public async Task Replay_DifferentManifest_ProducesDifferentVerdict() { var manifest1 = CreateManifest(); var manifest2 = manifest1 with { PolicySnapshot = manifest1.PolicySnapshot with { LatticeRulesDigest = new string('f', 64) } }; var engine = CreateEngine(); var result1 = await engine.ReplayAsync(manifest1, new ReplayOptions()); var result2 = await engine.ReplayAsync(manifest2, new ReplayOptions()); result1.VerdictDigest.Should().NotBe(result2.VerdictDigest); } [Trait("Category", TestCategories.Unit)] [Fact] public void CheckDeterminism_IdenticalResults_ReturnsTrue() { var engine = CreateEngine(); var result1 = new ReplayResult { RunId = "1", VerdictDigest = "abc123", Success = true, ExecutedAt = DateTimeOffset.UtcNow }; var result2 = new ReplayResult { RunId = "1", VerdictDigest = "abc123", Success = true, ExecutedAt = DateTimeOffset.UtcNow }; var check = engine.CheckDeterminism(result1, result2); check.IsDeterministic.Should().BeTrue(); } [Trait("Category", TestCategories.Unit)] [Fact] public void CheckDeterminism_DifferentResults_ReturnsDifferences() { var engine = CreateEngine(); var result1 = new ReplayResult { RunId = "1", VerdictJson = "{\"score\":100}", VerdictDigest = "abc123", Success = true, ExecutedAt = DateTimeOffset.UtcNow }; var result2 = new ReplayResult { RunId = "1", VerdictJson = "{\"score\":99}", VerdictDigest = "def456", Success = true, ExecutedAt = DateTimeOffset.UtcNow }; var check = engine.CheckDeterminism(result1, result2); check.IsDeterministic.Should().BeFalse(); check.Differences.Should().NotBeEmpty(); } private static ReplayEngine CreateEngine() { return new ReplayEngine( new FakeFeedLoader(), new FakePolicyLoader(), new FakeScannerFactory(), NullLogger.Instance); } private static RunManifest CreateManifest() { return new RunManifest { RunId = Guid.NewGuid().ToString(), SchemaVersion = "1.0.0", ArtifactDigests = ImmutableArray.Create(new ArtifactDigest("sha256", new string('a', 64), null, null)), FeedSnapshot = new FeedSnapshot("nvd", "v1", new string('b', 64), DateTimeOffset.UtcNow.AddHours(-1)), PolicySnapshot = new PolicySnapshot("1.0.0", new string('c', 64), ImmutableArray.Empty), ToolVersions = new ToolVersions("1.0.0", "1.0.0", "1.0.0", "1.0.0", ImmutableDictionary.Empty), CryptoProfile = new CryptoProfile("default", ImmutableArray.Empty, ImmutableArray.Empty), EnvironmentProfile = new EnvironmentProfile("postgres-only", false, null, null), CanonicalizationVersion = "1.0.0", InitiatedAt = DateTimeOffset.UtcNow }; } private sealed class FakeFeedLoader : IFeedLoader { public Task LoadByDigestAsync(string digest, CancellationToken ct = default) => Task.FromResult(new FeedSnapshot("nvd", "v1", digest, DateTimeOffset.UtcNow.AddHours(-1))); } private sealed class FakePolicyLoader : IPolicyLoader { public Task LoadByDigestAsync(string digest, CancellationToken ct = default) => Task.FromResult(new PolicySnapshot("1.0.0", digest, ImmutableArray.Empty)); } private sealed class FakeScannerFactory : IScannerFactory { public IScanner Create(ScannerOptions options) => new FakeScanner(options); } private sealed class FakeScanner : IScanner { private readonly ScannerOptions _options; public FakeScanner(ScannerOptions options) => _options = options; public Task ScanAsync(ImmutableArray artifacts, CancellationToken ct = default) { var verdict = new { feedVersion = _options.FeedSnapshot.Version, policyDigest = _options.PolicySnapshot.LatticeRulesDigest }; var evidence = new EvidenceIndex { IndexId = Guid.NewGuid().ToString(), SchemaVersion = "1.0.0", Verdict = new VerdictReference("v1", new string('d', 64), VerdictOutcome.Pass, null), Sboms = ImmutableArray.Empty, Attestations = ImmutableArray.Empty, ToolChain = new ToolChainEvidence("1", "1", "1", "1", "1", ImmutableDictionary.Empty), RunManifestDigest = new string('e', 64), CreatedAt = DateTimeOffset.UtcNow }; return Task.FromResult(new ScanResult(verdict, evidence, 10)); } } }