using System.Text.Json; using System.Text.Json.Nodes; using StellaOps.TaskRunner.Core.Execution; using StellaOps.TaskRunner.Core.Execution.Simulation; using StellaOps.TaskRunner.Core.Planning; using StellaOps.TaskRunner.Core.TaskPacks; using StellaOps.TaskRunner.Infrastructure.Execution; using Xunit; using StellaOps.TestKit; namespace StellaOps.TaskRunner.Tests; public sealed class PackRunProvenanceWriterTests { [Trait("Category", TestCategories.Unit)] [Fact] public async Task Filesystem_writer_emits_manifest() { var (context, state) = CreateRunState(); var completedAt = new DateTimeOffset(2025, 11, 30, 12, 30, 0, TimeSpan.Zero); var temp = Directory.CreateTempSubdirectory(); try { var ct = CancellationToken.None; var writer = new FilesystemPackRunProvenanceWriter(temp.FullName, new FixedTimeProvider(completedAt)); await writer.WriteAsync(context, state, ct); var path = Path.Combine(temp.FullName, "provenance", "run-test.json"); Assert.True(File.Exists(path)); using var document = JsonDocument.Parse(await File.ReadAllTextAsync(path, ct)); var root = document.RootElement; Assert.Equal("run-test", root.GetProperty("runId").GetString()); Assert.Equal("tenant-alpha", root.GetProperty("tenantId").GetString()); Assert.Equal(context.Plan.Hash, root.GetProperty("planHash").GetString()); Assert.Equal(completedAt, root.GetProperty("completedAt").GetDateTimeOffset()); } finally { temp.Delete(recursive: true); } } private static (PackRunExecutionContext Context, PackRunState State) CreateRunState() { var loader = new TaskPackManifestLoader(); var planner = new TaskPackPlanner(); var manifest = loader.Deserialize(TestManifests.Sample); var plan = planner.Plan(manifest, new Dictionary()).Plan ?? throw new InvalidOperationException("Plan generation failed."); var graphBuilder = new PackRunExecutionGraphBuilder(); var simulationEngine = new PackRunSimulationEngine(); var graph = graphBuilder.Build(plan); var requestedAt = new DateTimeOffset(2025, 11, 30, 10, 0, 0, TimeSpan.Zero); var context = new PackRunExecutionContext("run-test", plan, requestedAt, "tenant-alpha"); var state = PackRunStateFactory.CreateInitialState(context, graph, simulationEngine, requestedAt); return (context, state); } private sealed class FixedTimeProvider : TimeProvider { private readonly DateTimeOffset now; public FixedTimeProvider(DateTimeOffset now) { this.now = now; } public override DateTimeOffset GetUtcNow() => now; } }