Files
git.stella-ops.org/src/JobEngine/StellaOps.Scheduler.__Tests/StellaOps.Scheduler.Models.Tests/SchedulerSchemaMigrationTests.cs

179 lines
7.2 KiB
C#

using System.Text.Json.Nodes;
using StellaOps.Scheduler.Models;
using StellaOps.TestKit;
namespace StellaOps.Scheduler.Models.Tests;
public sealed class SchedulerSchemaMigrationTests
{
[Trait("Category", TestCategories.Unit)]
[Fact]
public void UpgradeSchedule_DefaultsSchemaVersionWhenMissing()
{
var schedule = new Schedule(
id: "sch-01",
tenantId: "tenant-alpha",
name: "Nightly",
enabled: true,
cronExpression: "0 2 * * *",
timezone: "UTC",
mode: ScheduleMode.AnalysisOnly,
selection: new Selector(SelectorScope.AllImages, tenantId: "tenant-alpha"),
onlyIf: null,
notify: null,
limits: null,
createdAt: DateTimeOffset.Parse("2025-10-18T00:00:00Z"),
createdBy: "svc-scheduler",
updatedAt: DateTimeOffset.Parse("2025-10-18T00:00:00Z"),
updatedBy: "svc-scheduler");
var json = JsonNode.Parse(CanonicalJsonSerializer.Serialize(schedule))!.AsObject();
json.Remove("schemaVersion");
var result = SchedulerSchemaMigration.UpgradeSchedule(json);
Assert.Equal(SchedulerSchemaVersions.Schedule, result.Value.SchemaVersion);
Assert.Equal(SchedulerSchemaVersions.Schedule, result.ToVersion);
Assert.Empty(result.Warnings);
}
[Trait("Category", TestCategories.Unit)]
[Fact]
public void UpgradeRun_StrictModeRemovesUnknownProperties()
{
var run = new Run(
id: "run-01",
tenantId: "tenant-alpha",
trigger: RunTrigger.Manual,
state: RunState.Queued,
stats: RunStats.Empty,
createdAt: DateTimeOffset.Parse("2025-10-18T01:10:00Z"));
var json = JsonNode.Parse(CanonicalJsonSerializer.Serialize(run))!.AsObject();
json["extraField"] = "to-be-removed";
var result = SchedulerSchemaMigration.UpgradeRun(json, strict: true);
Assert.Contains(result.Warnings, warning => warning.Contains("extraField", StringComparison.Ordinal));
}
[Trait("Category", TestCategories.Unit)]
[Fact]
public void UpgradeImpactSet_ThrowsForUnsupportedVersion()
{
var impactSet = new ImpactSet(
selector: new Selector(SelectorScope.AllImages, "tenant-alpha"),
images: Array.Empty<ImpactImage>(),
usageOnly: false,
generatedAt: DateTimeOffset.Parse("2025-10-18T02:00:00Z"));
var json = JsonNode.Parse(CanonicalJsonSerializer.Serialize(impactSet))!.AsObject();
json["schemaVersion"] = "scheduler.impact-set@99";
var ex = Assert.Throws<NotSupportedException>(() => SchedulerSchemaMigration.UpgradeImpactSet(json));
Assert.Contains("Unsupported scheduler schema version", ex.Message, StringComparison.Ordinal);
}
[Trait("Category", TestCategories.Unit)]
[Fact]
public void UpgradeSchedule_Legacy0_UpgradesToLatestVersion()
{
var legacy = new JsonObject
{
["schemaVersion"] = SchedulerSchemaVersions.ScheduleLegacy0,
["id"] = "sch-legacy",
["tenantId"] = "tenant-alpha",
["name"] = "Legacy Nightly",
["enabled"] = true,
["cronExpression"] = "0 2 * * *",
["timezone"] = "UTC",
["mode"] = "analysis-only",
["selection"] = new JsonObject
{
["scope"] = "all-images",
["tenantId"] = "tenant-alpha",
},
["notify"] = new JsonObject
{
["onNewFindings"] = "true",
["minSeverity"] = "HIGH",
},
["limits"] = new JsonObject
{
["maxJobs"] = "5",
["parallelism"] = -2,
},
["subscribers"] = "ops-team",
["createdAt"] = "2025-10-10T00:00:00Z",
["createdBy"] = "system",
["updatedAt"] = "2025-10-10T01:00:00Z",
["updatedBy"] = "system",
};
var result = SchedulerSchemaMigration.UpgradeSchedule(legacy, strict: true);
Assert.Equal(SchedulerSchemaVersions.ScheduleLegacy0, result.FromVersion);
Assert.Equal(SchedulerSchemaVersions.Schedule, result.ToVersion);
Assert.Equal(SchedulerSchemaVersions.Schedule, result.Value.SchemaVersion);
Assert.True(result.Value.Notify.IncludeKev);
Assert.Empty(result.Value.Subscribers);
Assert.Contains(result.Warnings, warning => warning.Contains("schedule.limits.parallelism", StringComparison.Ordinal));
Assert.Contains(result.Warnings, warning => warning.Contains("schedule.subscribers", StringComparison.Ordinal));
}
[Trait("Category", TestCategories.Unit)]
[Fact]
public void UpgradeRun_Legacy0_BackfillsMissingStats()
{
var legacy = new JsonObject
{
["schemaVersion"] = SchedulerSchemaVersions.RunLegacy0,
["id"] = "run-legacy",
["tenantId"] = "tenant-alpha",
["trigger"] = "manual",
["state"] = "queued",
["stats"] = new JsonObject
{
["candidates"] = "4",
["queued"] = 2,
},
["createdAt"] = "2025-10-10T02:00:00Z",
};
var result = SchedulerSchemaMigration.UpgradeRun(legacy, strict: true);
Assert.Equal(SchedulerSchemaVersions.RunLegacy0, result.FromVersion);
Assert.Equal(SchedulerSchemaVersions.Run, result.ToVersion);
Assert.Equal(SchedulerSchemaVersions.Run, result.Value.SchemaVersion);
Assert.Equal(4, result.Value.Stats.Candidates);
Assert.Equal(0, result.Value.Stats.NewMedium);
Assert.Equal(RunState.Queued, result.Value.State);
Assert.Empty(result.Value.Deltas);
Assert.Contains(result.Warnings, warning => warning.Contains("run.stats.newMedium", StringComparison.Ordinal));
}
[Trait("Category", TestCategories.Unit)]
[Fact]
public void UpgradeImpactSet_Legacy0_ComputesTotal()
{
var legacy = new JsonObject
{
["schemaVersion"] = SchedulerSchemaVersions.ImpactSetLegacy0,
["selector"] = JsonNode.Parse("""{"scope":"all-images","tenantId":"tenant-alpha"}"""),
["images"] = new JsonArray(
JsonNode.Parse("""{"imageDigest":"sha256:1111111111111111111111111111111111111111111111111111111111111111","registry":"docker.io","repository":"library/nginx"}"""),
JsonNode.Parse("""{"imageDigest":"sha256:2222222222222222222222222222222222222222222222222222222222222222","registry":"docker.io","repository":"library/httpd"}""")),
["usageOnly"] = "false",
["generatedAt"] = "2025-10-10T03:00:00Z",
};
var result = SchedulerSchemaMigration.UpgradeImpactSet(legacy, strict: true);
Assert.Equal(SchedulerSchemaVersions.ImpactSetLegacy0, result.FromVersion);
Assert.Equal(SchedulerSchemaVersions.ImpactSet, result.ToVersion);
Assert.Equal(2, result.Value.Total);
Assert.Equal(2, result.Value.Images.Length);
Assert.Contains(result.Warnings, warning => warning.Contains("impact set total", StringComparison.OrdinalIgnoreCase));
}
}