//
// 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;
}
}