Files
git.stella-ops.org/src/StellaOps.Scheduler.Models.Tests/ScheduleSerializationTests.cs
master 5ce40d2eeb feat: Initialize Zastava Webhook service with TLS and Authority authentication
- Added Program.cs to set up the web application with Serilog for logging, health check endpoints, and a placeholder admission endpoint.
- Configured Kestrel server to use TLS 1.3 and handle client certificates appropriately.
- Created StellaOps.Zastava.Webhook.csproj with necessary dependencies including Serilog and Polly.
- Documented tasks in TASKS.md for the Zastava Webhook project, outlining current work and exit criteria for each task.
2025-10-19 18:36:22 +03:00

114 lines
4.4 KiB
C#

using System.Text.Json;
using StellaOps.Scheduler.Models;
namespace StellaOps.Scheduler.Models.Tests;
public sealed class ScheduleSerializationTests
{
[Fact]
public void ScheduleSerialization_IsDeterministicRegardlessOfInputOrdering()
{
var selectionA = new Selector(
SelectorScope.ByNamespace,
tenantId: "tenant-alpha",
namespaces: new[] { "team-b", "team-a" },
repositories: new[] { "app/service-api", "app/service-web" },
digests: new[] { "sha256:bb", "sha256:aa" },
includeTags: new[] { "prod", "canary" },
labels: new[]
{
new LabelSelector("env", new[] { "prod", "staging" }),
new LabelSelector("app", new[] { "web", "api" }),
},
resolvesTags: true);
var selectionB = new Selector(
scope: SelectorScope.ByNamespace,
tenantId: "tenant-alpha",
namespaces: new[] { "team-a", "team-b" },
repositories: new[] { "app/service-web", "app/service-api" },
digests: new[] { "sha256:aa", "sha256:bb" },
includeTags: new[] { "canary", "prod" },
labels: new[]
{
new LabelSelector("app", new[] { "api", "web" }),
new LabelSelector("env", new[] { "staging", "prod" }),
},
resolvesTags: true);
var scheduleA = new Schedule(
id: "sch_001",
tenantId: "tenant-alpha",
name: "Nightly Prod",
enabled: true,
cronExpression: "0 2 * * *",
timezone: "UTC",
mode: ScheduleMode.AnalysisOnly,
selection: selectionA,
onlyIf: new ScheduleOnlyIf(lastReportOlderThanDays: 7, policyRevision: "policy@42"),
notify: new ScheduleNotify(onNewFindings: true, SeverityRank.High, includeKev: true),
limits: new ScheduleLimits(maxJobs: 1000, ratePerSecond: 25, parallelism: 4),
createdAt: DateTimeOffset.Parse("2025-10-18T23:00:00Z"),
createdBy: "svc_scheduler",
updatedAt: DateTimeOffset.Parse("2025-10-18T23:00:00Z"),
updatedBy: "svc_scheduler");
var scheduleB = new Schedule(
id: scheduleA.Id,
tenantId: scheduleA.TenantId,
name: scheduleA.Name,
enabled: scheduleA.Enabled,
cronExpression: scheduleA.CronExpression,
timezone: scheduleA.Timezone,
mode: scheduleA.Mode,
selection: selectionB,
onlyIf: scheduleA.OnlyIf,
notify: scheduleA.Notify,
limits: scheduleA.Limits,
createdAt: scheduleA.CreatedAt,
createdBy: scheduleA.CreatedBy,
updatedAt: scheduleA.UpdatedAt,
updatedBy: scheduleA.UpdatedBy,
subscribers: scheduleA.Subscribers);
var jsonA = CanonicalJsonSerializer.Serialize(scheduleA);
var jsonB = CanonicalJsonSerializer.Serialize(scheduleB);
Assert.Equal(jsonA, jsonB);
using var doc = JsonDocument.Parse(jsonA);
var root = doc.RootElement;
Assert.Equal(SchedulerSchemaVersions.Schedule, root.GetProperty("schemaVersion").GetString());
Assert.Equal("analysis-only", root.GetProperty("mode").GetString());
Assert.Equal("tenant-alpha", root.GetProperty("tenantId").GetString());
var namespaces = root.GetProperty("selection").GetProperty("namespaces").EnumerateArray().Select(e => e.GetString()).ToArray();
Assert.Equal(new[] { "team-a", "team-b" }, namespaces);
}
[Theory]
[InlineData("")]
[InlineData("not-a-timezone")]
public void Schedule_ThrowsWhenTimezoneInvalid(string timezone)
{
var selection = new Selector(SelectorScope.AllImages, tenantId: "tenant-alpha");
Assert.ThrowsAny<Exception>(() => new Schedule(
id: "sch_002",
tenantId: "tenant-alpha",
name: "Invalid timezone",
enabled: true,
cronExpression: "0 3 * * *",
timezone: timezone,
mode: ScheduleMode.AnalysisOnly,
selection: selection,
onlyIf: null,
notify: null,
limits: null,
createdAt: DateTimeOffset.UtcNow,
createdBy: "svc",
updatedAt: DateTimeOffset.UtcNow,
updatedBy: "svc"));
}
}