// // Copyright (c) StellaOps. Licensed under BUSL-1.1. // // Sprint: SPRINT_20260105_002_002_TEST_trace_replay_evidence // Task: TREP-013, TREP-014 using System.Collections.Immutable; namespace StellaOps.Testing.Evidence; /// /// Links test executions to EvidenceLocker for audit-grade storage. /// public interface ITestEvidenceService { /// /// Begin a test evidence session. /// /// Session metadata. /// Cancellation token. /// The created session. Task BeginSessionAsync( TestSessionMetadata metadata, CancellationToken ct = default); /// /// Record a test result within a session. /// /// The active session. /// The test result to record. /// Cancellation token. Task RecordTestResultAsync( TestEvidenceSession session, TestResultRecord result, CancellationToken ct = default); /// /// Finalize session and store in EvidenceLocker. /// /// The session to finalize. /// Cancellation token. /// The evidence bundle. Task FinalizeSessionAsync( TestEvidenceSession session, CancellationToken ct = default); /// /// Retrieve test evidence bundle for audit. /// /// The bundle identifier. /// Cancellation token. /// The evidence bundle, or null if not found. Task GetBundleAsync( string bundleId, CancellationToken ct = default); } /// /// Metadata about a test session. /// /// Unique session identifier. /// Identifier for the test suite. /// Git commit hash. /// Git branch name. /// Description of the runner environment. /// When the session started. /// Additional labels. public sealed record TestSessionMetadata( string SessionId, string TestSuiteId, string GitCommit, string GitBranch, string RunnerEnvironment, DateTimeOffset StartedAt, ImmutableDictionary Labels); /// /// A recorded test result. /// /// Unique test identifier. /// Test method name. /// Test class name. /// Test outcome. /// Test duration. /// Failure message, if failed. /// Stack trace, if failed. /// Test categories. /// Blast radius annotations. /// Attached file references. public sealed record TestResultRecord( string TestId, string TestName, string TestClass, TestOutcome Outcome, TimeSpan Duration, string? FailureMessage, string? StackTrace, ImmutableArray Categories, ImmutableArray BlastRadiusAnnotations, ImmutableDictionary Attachments); /// /// Test outcome. /// public enum TestOutcome { Passed, Failed, Skipped, Inconclusive } /// /// A finalized test evidence bundle. /// /// Unique bundle identifier. /// Merkle root for integrity verification. /// Session metadata. /// Test summary. /// All test results. /// When the bundle was finalized. /// Reference to EvidenceLocker storage. public sealed record TestEvidenceBundle( string BundleId, string MerkleRoot, TestSessionMetadata Metadata, TestSummary Summary, ImmutableArray Results, DateTimeOffset FinalizedAt, string EvidenceLockerRef); /// /// Summary of test results. /// /// Total number of tests. /// Number of passed tests. /// Number of failed tests. /// Number of skipped tests. /// Total test duration. /// Results grouped by category. /// Results grouped by blast radius. public sealed record TestSummary( int TotalTests, int Passed, int Failed, int Skipped, TimeSpan TotalDuration, ImmutableDictionary ResultsByCategory, ImmutableDictionary ResultsByBlastRadius); /// /// An active test evidence session. /// public sealed class TestEvidenceSession { private readonly List _results = []; private readonly object _lock = new(); /// /// Gets the session metadata. /// public TestSessionMetadata Metadata { get; } /// /// Gets whether the session is finalized. /// public bool IsFinalized { get; private set; } /// /// Initializes a new instance of the class. /// /// Session metadata. public TestEvidenceSession(TestSessionMetadata metadata) { Metadata = metadata; } /// /// Add a test result to the session. /// /// The result to add. public void AddResult(TestResultRecord result) { if (IsFinalized) { throw new InvalidOperationException("Cannot add results to a finalized session."); } lock (_lock) { _results.Add(result); } } /// /// Get all results recorded in this session. /// /// Immutable array of results. public ImmutableArray GetResults() { lock (_lock) { return [.. _results]; } } /// /// Mark the session as finalized. /// internal void MarkAsFinalized() { IsFinalized = true; } }