feat(centralui): execution-chain tree view on the Audit Log page
This commit is contained in:
@@ -222,6 +222,66 @@ public class AuditLogQueryServiceTests
|
||||
Assert.NotSame(resolvedRepos[0], resolvedRepos[1]);
|
||||
}
|
||||
|
||||
// ─────────────────────────────────────────────────────────────────────────
|
||||
// Audit Log ParentExecutionId feature (Task 10): GetExecutionTreeAsync —
|
||||
// a thin pass-through over IAuditLogRepository.GetExecutionTreeAsync, mirroring
|
||||
// QueryAsync's scope-per-call contract on the production path.
|
||||
// ─────────────────────────────────────────────────────────────────────────
|
||||
|
||||
[Fact]
|
||||
public async Task GetExecutionTreeAsync_ForwardsExecutionId_ToRepository()
|
||||
{
|
||||
var repo = Substitute.For<IAuditLogRepository>();
|
||||
var executionId = Guid.Parse("aaaaaaaa-bbbb-cccc-dddd-eeeeeeeeeeee");
|
||||
var expected = new List<ExecutionTreeNode>
|
||||
{
|
||||
new(executionId, null, 3,
|
||||
new[] { "ApiOutbound" }, new[] { "Delivered" },
|
||||
"plant-a", "boiler-3",
|
||||
new DateTime(2026, 5, 20, 12, 0, 0, DateTimeKind.Utc),
|
||||
new DateTime(2026, 5, 20, 12, 0, 5, DateTimeKind.Utc)),
|
||||
};
|
||||
repo.GetExecutionTreeAsync(executionId, Arg.Any<CancellationToken>())
|
||||
.Returns(Task.FromResult<IReadOnlyList<ExecutionTreeNode>>(expected));
|
||||
|
||||
var sut = new AuditLogQueryService(repo, EmptyAggregator());
|
||||
|
||||
var result = await sut.GetExecutionTreeAsync(executionId);
|
||||
|
||||
Assert.Same(expected, result);
|
||||
await repo.Received(1).GetExecutionTreeAsync(executionId, Arg.Any<CancellationToken>());
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task GetExecutionTreeAsync_OpensFreshScopePerCall_OnProductionCtor()
|
||||
{
|
||||
// The production ctor must resolve a fresh repository per call — same
|
||||
// scope-per-query contract QueryAsync upholds, so the page's auto-load
|
||||
// never shares the circuit-scoped DbContext.
|
||||
var resolvedRepos = new List<IAuditLogRepository>();
|
||||
|
||||
var services = new ServiceCollection();
|
||||
services.AddScoped<IAuditLogRepository>(_ =>
|
||||
{
|
||||
var repo = Substitute.For<IAuditLogRepository>();
|
||||
repo.GetExecutionTreeAsync(Arg.Any<Guid>(), Arg.Any<CancellationToken>())
|
||||
.Returns(Task.FromResult<IReadOnlyList<ExecutionTreeNode>>(Array.Empty<ExecutionTreeNode>()));
|
||||
resolvedRepos.Add(repo);
|
||||
return repo;
|
||||
});
|
||||
|
||||
await using var provider = services.BuildServiceProvider();
|
||||
var sut = new AuditLogQueryService(
|
||||
provider.GetRequiredService<IServiceScopeFactory>(),
|
||||
EmptyAggregator());
|
||||
|
||||
await sut.GetExecutionTreeAsync(Guid.NewGuid());
|
||||
await sut.GetExecutionTreeAsync(Guid.NewGuid());
|
||||
|
||||
Assert.Equal(2, resolvedRepos.Count);
|
||||
Assert.NotSame(resolvedRepos[0], resolvedRepos[1]);
|
||||
}
|
||||
|
||||
private static SiteHealthState StateWithBacklog(string siteId, int? pending)
|
||||
{
|
||||
SiteAuditBacklogSnapshot? backlog = pending.HasValue
|
||||
|
||||
Reference in New Issue
Block a user