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.
This commit is contained in:
master
2025-10-19 18:36:22 +03:00
parent 2062da7a8b
commit d099a90f9b
966 changed files with 91038 additions and 1850 deletions

View File

@@ -0,0 +1,108 @@
using StellaOps.Scheduler.Models;
namespace StellaOps.Scheduler.Models.Tests;
public sealed class RunStateMachineTests
{
[Fact]
public void EnsureTransition_FromQueuedToRunningSetsStartedAt()
{
var run = new Run(
id: "run-queued",
tenantId: "tenant-alpha",
trigger: RunTrigger.Manual,
state: RunState.Queued,
stats: RunStats.Empty,
createdAt: DateTimeOffset.Parse("2025-10-18T03:00:00Z"));
var transitionTime = DateTimeOffset.Parse("2025-10-18T03:05:00Z");
var updated = RunStateMachine.EnsureTransition(
run,
RunState.Running,
transitionTime,
mutateStats: builder => builder.SetQueued(1));
Assert.Equal(RunState.Running, updated.State);
Assert.Equal(transitionTime.ToUniversalTime(), updated.StartedAt);
Assert.Equal(1, updated.Stats.Queued);
Assert.Null(updated.Error);
}
[Fact]
public void EnsureTransition_ToCompletedPopulatesFinishedAt()
{
var run = new Run(
id: "run-running",
tenantId: "tenant-alpha",
trigger: RunTrigger.Manual,
state: RunState.Running,
stats: RunStats.Empty,
createdAt: DateTimeOffset.Parse("2025-10-18T03:00:00Z"),
startedAt: DateTimeOffset.Parse("2025-10-18T03:05:00Z"));
var completedAt = DateTimeOffset.Parse("2025-10-18T03:10:00Z");
var updated = RunStateMachine.EnsureTransition(
run,
RunState.Completed,
completedAt,
mutateStats: builder =>
{
builder.SetQueued(1);
builder.SetCompleted(1);
});
Assert.Equal(RunState.Completed, updated.State);
Assert.Equal(completedAt.ToUniversalTime(), updated.FinishedAt);
Assert.Equal(1, updated.Stats.Completed);
}
[Fact]
public void EnsureTransition_ErrorRequiresMessage()
{
var run = new Run(
id: "run-running",
tenantId: "tenant-alpha",
trigger: RunTrigger.Manual,
state: RunState.Running,
stats: RunStats.Empty,
createdAt: DateTimeOffset.Parse("2025-10-18T03:00:00Z"),
startedAt: DateTimeOffset.Parse("2025-10-18T03:05:00Z"));
var timestamp = DateTimeOffset.Parse("2025-10-18T03:06:00Z");
var ex = Assert.Throws<InvalidOperationException>(
() => RunStateMachine.EnsureTransition(run, RunState.Error, timestamp));
Assert.Contains("requires a non-empty error message", ex.Message, StringComparison.Ordinal);
}
[Fact]
public void Validate_ThrowsWhenTerminalWithoutFinishedAt()
{
var run = new Run(
id: "run-bad",
tenantId: "tenant-alpha",
trigger: RunTrigger.Manual,
state: RunState.Completed,
stats: RunStats.Empty,
createdAt: DateTimeOffset.Parse("2025-10-18T03:00:00Z"),
startedAt: DateTimeOffset.Parse("2025-10-18T03:05:00Z"));
Assert.Throws<InvalidOperationException>(() => RunStateMachine.Validate(run));
}
[Fact]
public void RunReasonExtension_NormalizesImpactWindow()
{
var reason = new RunReason(manualReason: "delta");
var from = DateTimeOffset.Parse("2025-10-18T01:00:00+02:00");
var to = DateTimeOffset.Parse("2025-10-18T03:30:00+02:00");
var updated = reason.WithImpactWindow(from, to);
Assert.Equal(from.ToUniversalTime().ToString("O"), updated.ImpactWindowFrom);
Assert.Equal(to.ToUniversalTime().ToString("O"), updated.ImpactWindowTo);
}
}