Implement missing backend endpoints for release orchestration

TASK-002: 11 deployment monitoring endpoints in JobEngine
  (list, get, logs, events, metrics, pause/resume/cancel/rollback/retry)
TASK-003: 6 evidence management endpoints in JobEngine
  (list, get, verify, export, raw, timeline)
TASK-005: 3 release dashboard endpoints in JobEngine
  (dashboard summary, approve/reject promotion)
TASK-006: 2 registry image search endpoints in Scanner
  (search with 9 mock images, digests lookup)

All endpoints return seed/mock data for testing. Auth policies
match existing patterns. Dual route registration on both
/api/ and /api/v1/ prefixes.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
master
2026-03-23 15:52:20 +02:00
parent d3353e9d16
commit dd29786e38
17 changed files with 2066 additions and 26 deletions

View File

@@ -0,0 +1,97 @@
using System.Text.Json;
using FluentAssertions;
using NUnit.Framework;
using StellaOps.ElkSharp;
namespace StellaOps.Workflow.Renderer.Tests;
[TestFixture]
public class ElkSharpEdgeRefinementTests
{
[Test]
[Property("Intent", "Operational")]
public async Task LayoutAsync_WhenBestEffortRenderedTwice_ShouldProduceDeterministicGeometry()
{
var graph = BuildElkSharpStressGraph();
var engine = new ElkSharpLayeredLayoutEngine();
var options = new ElkLayoutOptions
{
Direction = ElkLayoutDirection.LeftToRight,
Effort = ElkLayoutEffort.Best,
OrderingIterations = 18,
PlacementIterations = 10,
};
var first = await engine.LayoutAsync(graph, options);
var second = await engine.LayoutAsync(graph, options);
JsonSerializer.Serialize(first).Should().Be(JsonSerializer.Serialize(second));
}
[Test]
[Property("Intent", "Operational")]
public async Task LayoutAsync_WhenTopToBottomRefinementEnabled_ShouldMatchDisabledOutput()
{
var graph = BuildElkSharpStressGraph();
var engine = new ElkSharpLayeredLayoutEngine();
var baseline = await engine.LayoutAsync(graph, new ElkLayoutOptions
{
Direction = ElkLayoutDirection.TopToBottom,
Effort = ElkLayoutEffort.Best,
OrderingIterations = 18,
PlacementIterations = 10,
EdgeRefinement = new EdgeRefinementOptions
{
Enabled = false,
},
});
var refined = await engine.LayoutAsync(graph, new ElkLayoutOptions
{
Direction = ElkLayoutDirection.TopToBottom,
Effort = ElkLayoutEffort.Best,
OrderingIterations = 18,
PlacementIterations = 10,
EdgeRefinement = new EdgeRefinementOptions
{
Enabled = true,
MaxGlobalPasses = 2,
MaxTrialsPerProblemEdge = 4,
},
});
JsonSerializer.Serialize(refined).Should().Be(JsonSerializer.Serialize(baseline));
}
private static ElkGraph BuildElkSharpStressGraph()
{
return new ElkGraph
{
Id = "elksharp-refinement",
Nodes =
[
new ElkNode { Id = "start", Label = "Start", Kind = "Start", Width = 88, Height = 48 },
new ElkNode { Id = "review", Label = "Review", Kind = "Decision", Width = 176, Height = 120 },
new ElkNode { Id = "approve", Label = "Approve", Kind = "Task", Width = 176, Height = 84 },
new ElkNode { Id = "retry", Label = "Retry", Kind = "Task", Width = 176, Height = 84 },
new ElkNode { Id = "notify", Label = "Notify", Kind = "Task", Width = 176, Height = 84 },
new ElkNode { Id = "archive", Label = "Archive", Kind = "Task", Width = 176, Height = 84 },
new ElkNode { Id = "end", Label = "End", Kind = "End", Width = 88, Height = 48 },
],
Edges =
[
new ElkEdge { Id = "start-review", SourceNodeId = "start", TargetNodeId = "review" },
new ElkEdge { Id = "review-approve", SourceNodeId = "review", TargetNodeId = "approve", Label = "when approved" },
new ElkEdge { Id = "review-retry", SourceNodeId = "review", TargetNodeId = "retry", Label = "on failure" },
new ElkEdge { Id = "approve-notify", SourceNodeId = "approve", TargetNodeId = "notify" },
new ElkEdge { Id = "retry-review", SourceNodeId = "retry", TargetNodeId = "review", Label = "repeat while retry" },
new ElkEdge { Id = "notify-end", SourceNodeId = "notify", TargetNodeId = "end", Label = "default" },
new ElkEdge { Id = "approve-archive", SourceNodeId = "approve", TargetNodeId = "archive" },
new ElkEdge { Id = "archive-end", SourceNodeId = "archive", TargetNodeId = "end", Label = "default" },
],
};
}
}