using StellaOps.Scheduler.Models; namespace StellaOps.Scheduler.Models.Tests; public sealed class RunValidationTests { [Fact] public void RunStatsRejectsNegativeValues() { Assert.Throws(() => new RunStats(candidates: -1)); Assert.Throws(() => new RunStats(deduped: -1)); Assert.Throws(() => new RunStats(queued: -1)); Assert.Throws(() => new RunStats(completed: -1)); Assert.Throws(() => new RunStats(deltas: -1)); Assert.Throws(() => new RunStats(newCriticals: -1)); Assert.Throws(() => new RunStats(newHigh: -1)); Assert.Throws(() => new RunStats(newMedium: -1)); Assert.Throws(() => new RunStats(newLow: -1)); } [Fact] public void DeltaSummarySortsTopFindingsBySeverityThenId() { var summary = new DeltaSummary( imageDigest: "sha256:0011", newFindings: 3, newCriticals: 1, newHigh: 1, newMedium: 1, newLow: 0, kevHits: new[] { "CVE-2025-0002", "CVE-2025-0001" }, topFindings: new[] { new DeltaFinding("pkg:maven/b", "CVE-2025-0002", SeverityRank.High), new DeltaFinding("pkg:maven/a", "CVE-2024-0001", SeverityRank.Critical), new DeltaFinding("pkg:maven/c", "CVE-2025-0008", SeverityRank.Medium), }, reportUrl: "https://ui.example/reports/sha256:0011", attestation: new DeltaAttestation(uuid: "rekor-1", verified: true), detectedAt: DateTimeOffset.Parse("2025-10-18T00:01:02Z")); Assert.Equal(new[] { "pkg:maven/a", "pkg:maven/b", "pkg:maven/c" }, summary.TopFindings.Select(f => f.Purl)); Assert.Equal(new[] { "CVE-2025-0001", "CVE-2025-0002" }, summary.KevHits); } [Fact] public void RunSerializationIncludesDeterministicOrdering() { var stats = new RunStats(candidates: 10, deduped: 8, queued: 8, completed: 5, deltas: 3, newCriticals: 2); var run = new Run( id: "run_001", tenantId: "tenant-alpha", trigger: RunTrigger.Feedser, state: RunState.Running, stats: stats, reason: new RunReason(feedserExportId: "exp-123"), scheduleId: "sch_001", createdAt: DateTimeOffset.Parse("2025-10-18T01:00:00Z"), startedAt: DateTimeOffset.Parse("2025-10-18T01:00:05Z"), finishedAt: null, error: null, deltas: new[] { new DeltaSummary( imageDigest: "sha256:aaa", newFindings: 1, newCriticals: 1, newHigh: 0, newMedium: 0, newLow: 0) }); var json = CanonicalJsonSerializer.Serialize(run); Assert.Equal(SchedulerSchemaVersions.Run, run.SchemaVersion); Assert.Contains("\"trigger\":\"feedser\"", json, StringComparison.Ordinal); Assert.Contains("\"stats\":{\"candidates\":10,\"deduped\":8,\"queued\":8,\"completed\":5,\"deltas\":3,\"newCriticals\":2,\"newHigh\":0,\"newMedium\":0,\"newLow\":0}", json, StringComparison.Ordinal); } }