Restructure solution layout by module
This commit is contained in:
@@ -0,0 +1,245 @@
|
||||
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<Schedule>(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<Run>(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<ImpactSet>(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<AuditRecord>(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<GraphBuildJob>(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<GraphOverlayJob>(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<PolicyRunRequest>(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<PolicyRunStatus>(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<PolicyDiffSummary>(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<PolicyExplainTrace>(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<string, string>(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<PolicyRunJob>(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
|
||||
});
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user