using System; using System.Collections.Immutable; using System.Text.Json; namespace StellaOps.Scheduler.Models.Tests; public sealed class SamplePayloadTests { private static readonly string SamplesRoot = LocateSamplesRoot(); [Fact] public void ScheduleSample_RoundtripsThroughCanonicalSerializer() { var json = ReadSample("schedule.json"); var schedule = CanonicalJsonSerializer.Deserialize(json); Assert.Equal("sch_20251018a", schedule.Id); Assert.Equal("tenant-alpha", schedule.TenantId); var canonical = CanonicalJsonSerializer.Serialize(schedule); AssertJsonEquivalent(json, canonical); } [Fact] public void RunSample_RoundtripsThroughCanonicalSerializer() { var json = ReadSample("run.json"); var run = CanonicalJsonSerializer.Deserialize(json); Assert.Equal(RunState.Running, run.State); Assert.Equal(42, run.Stats.Deltas); var canonical = CanonicalJsonSerializer.Serialize(run); AssertJsonEquivalent(json, canonical); } [Fact] public void ImpactSetSample_RoundtripsThroughCanonicalSerializer() { var json = ReadSample("impact-set.json"); var impact = CanonicalJsonSerializer.Deserialize(json); Assert.True(impact.UsageOnly); Assert.Single(impact.Images); var canonical = CanonicalJsonSerializer.Serialize(impact); AssertJsonEquivalent(json, canonical); } [Fact] public void AuditSample_RoundtripsThroughCanonicalSerializer() { var json = ReadSample("audit.json"); var audit = CanonicalJsonSerializer.Deserialize(json); Assert.Equal("scheduler", audit.Category); Assert.Equal("pause", audit.Action); var canonical = CanonicalJsonSerializer.Serialize(audit); AssertJsonEquivalent(json, canonical); } [Fact] public void GraphBuildJobSample_RoundtripsThroughCanonicalSerializer() { var json = ReadSample("graph-build-job.json"); var job = CanonicalJsonSerializer.Deserialize(json); Assert.Equal(GraphJobStatus.Running, job.Status); Assert.Equal(GraphBuildJobTrigger.SbomVersion, job.Trigger); var canonical = CanonicalJsonSerializer.Serialize(job); AssertJsonEquivalent(json, canonical); } [Fact] public void GraphOverlayJobSample_RoundtripsThroughCanonicalSerializer() { var json = ReadSample("graph-overlay-job.json"); var job = CanonicalJsonSerializer.Deserialize(json); Assert.Equal(GraphJobStatus.Queued, job.Status); Assert.Equal(GraphOverlayJobTrigger.Policy, job.Trigger); Assert.Equal(GraphOverlayKind.Policy, job.OverlayKind); var canonical = CanonicalJsonSerializer.Serialize(job); AssertJsonEquivalent(json, canonical); } [Fact] public void PolicyRunRequestSample_RoundtripsThroughCanonicalSerializer() { var json = ReadSample("policy-run-request.json"); var request = CanonicalJsonSerializer.Deserialize(json); Assert.Equal("default", request.TenantId); Assert.Equal(PolicyRunMode.Incremental, request.Mode); Assert.Equal("run:P-7:2025-10-26:auto", request.RunId); Assert.True(request.Inputs.CaptureExplain); Assert.Equal(2, request.Inputs.SbomSet.Length); var canonical = CanonicalJsonSerializer.Serialize(request); AssertJsonEquivalent(json, canonical); } [Fact] public void PolicyRunStatusSample_RoundtripsThroughCanonicalSerializer() { var json = ReadSample("policy-run-status.json"); var status = CanonicalJsonSerializer.Deserialize(json); Assert.Equal(PolicyRunExecutionStatus.Succeeded, status.Status); Assert.Equal(1742, status.Stats.Components); Assert.Equal("scheduler", status.Metadata["orchestrator"]); var canonical = CanonicalJsonSerializer.Serialize(status); AssertJsonEquivalent(json, canonical); } [Fact] public void PolicyDiffSummarySample_RoundtripsThroughCanonicalSerializer() { var json = ReadSample("policy-diff-summary.json"); var summary = CanonicalJsonSerializer.Deserialize(json); Assert.Equal(12, summary.Added); Assert.Contains(summary.BySeverity.Keys, key => string.Equals(key, "critical", StringComparison.OrdinalIgnoreCase)); Assert.Equal(2, summary.RuleHits.Length); var canonical = CanonicalJsonSerializer.Serialize(summary); AssertJsonEquivalent(json, canonical); } [Fact] public void PolicyExplainTraceSample_RoundtripsThroughCanonicalSerializer() { var json = ReadSample("policy-explain-trace.json"); var trace = CanonicalJsonSerializer.Deserialize(json); Assert.Equal("finding:sbom:S-42/pkg:npm/lodash@4.17.21", trace.FindingId); Assert.Equal(PolicyVerdictStatus.Blocked, trace.Verdict.Status); Assert.Equal(2, trace.RuleChain.Length); Assert.Equal(2, trace.Evidence.Length); var canonical = CanonicalJsonSerializer.Serialize(trace); AssertJsonEquivalent(json, canonical); } [Fact] public void PolicyRunJob_RoundtripsThroughCanonicalSerializer() { var metadata = ImmutableSortedDictionary.CreateBuilder(StringComparer.Ordinal); metadata["source"] = "cli"; metadata["trigger"] = "manual"; var job = new PolicyRunJob( SchemaVersion: SchedulerSchemaVersions.PolicyRunJob, Id: "job_20251026T140500Z", TenantId: "tenant-alpha", PolicyId: "P-7", PolicyVersion: 4, Mode: PolicyRunMode.Incremental, Priority: PolicyRunPriority.High, PriorityRank: -1, RunId: "run:P-7:2025-10-26:auto", RequestedBy: "user:cli", CorrelationId: "req-20251026T140500Z", Metadata: metadata.ToImmutable(), Inputs: new PolicyRunInputs( sbomSet: new[] { "sbom:S-42", "sbom:S-318" }, advisoryCursor: DateTimeOffset.Parse("2025-10-26T13:59:00Z"), captureExplain: true), QueuedAt: DateTimeOffset.Parse("2025-10-26T14:05:00Z"), Status: PolicyRunJobStatus.Pending, AttemptCount: 1, LastAttemptAt: DateTimeOffset.Parse("2025-10-26T14:05:30Z"), LastError: "transient network error", CreatedAt: DateTimeOffset.Parse("2025-10-26T14:04:50Z"), UpdatedAt: DateTimeOffset.Parse("2025-10-26T14:05:10Z"), AvailableAt: DateTimeOffset.Parse("2025-10-26T14:05:20Z"), SubmittedAt: null, CompletedAt: null, LeaseOwner: "worker-01", LeaseExpiresAt: DateTimeOffset.Parse("2025-10-26T14:06:30Z"), CancellationRequested: false, CancellationRequestedAt: null, CancellationReason: null, CancelledAt: null); var canonical = CanonicalJsonSerializer.Serialize(job); var roundtrip = CanonicalJsonSerializer.Deserialize(canonical); Assert.Equal(job.TenantId, roundtrip.TenantId); Assert.Equal(job.PolicyId, roundtrip.PolicyId); Assert.Equal(job.Priority, roundtrip.Priority); Assert.Equal(job.Status, roundtrip.Status); Assert.Equal(job.RunId, roundtrip.RunId); Assert.Equal("cli", roundtrip.Metadata!["source"]); } private static string ReadSample(string fileName) { var path = Path.Combine(SamplesRoot, fileName); return File.ReadAllText(path); } private static string LocateSamplesRoot() { var current = AppContext.BaseDirectory; while (!string.IsNullOrEmpty(current)) { var candidate = Path.Combine(current, "samples", "api", "scheduler"); if (Directory.Exists(candidate)) { return candidate; } var parent = Path.GetDirectoryName(current.TrimEnd(Path.DirectorySeparatorChar, Path.AltDirectorySeparatorChar)); if (string.Equals(parent, current, StringComparison.Ordinal)) { break; } current = parent; } throw new DirectoryNotFoundException("Unable to locate samples/api/scheduler in repository tree."); } private static void AssertJsonEquivalent(string expected, string actual) { var normalizedExpected = NormalizeJson(expected); var normalizedActual = NormalizeJson(actual); Assert.Equal(normalizedExpected, normalizedActual); } private static string NormalizeJson(string json) { using var document = JsonDocument.Parse(json); return JsonSerializer.Serialize(document.RootElement, new JsonSerializerOptions { WriteIndented = false }); } }