refactor(auditlog): GetExecutionTreeAsync recurses over a distinct edge set
This commit is contained in:
@@ -770,18 +770,28 @@ public class AuditLogRepositoryTests : IClassFixture<MsSqlMigrationFixture>
|
||||
|
||||
// A 3-level chain: root -> mid -> leaf. Each execution emits two rows so
|
||||
// RowCount aggregation is exercised; the child rows carry the parent's
|
||||
// ExecutionId as ParentExecutionId.
|
||||
// ExecutionId as ParentExecutionId. Each execution is given a DISTINCT
|
||||
// channel, and its two rows carry DISTINCT statuses and timestamps, so
|
||||
// the per-node Channels/Statuses sets and the FirstOccurred/LastOccurred
|
||||
// span are meaningfully asserted (not all-defaults).
|
||||
var rootExec = Guid.NewGuid();
|
||||
var midExec = Guid.NewGuid();
|
||||
var leafExec = Guid.NewGuid();
|
||||
|
||||
var t0 = new DateTime(2026, 10, 5, 9, 0, 0, DateTimeKind.Utc);
|
||||
await repo.InsertIfNotExistsAsync(NewEvent(siteId, occurredAtUtc: t0, executionId: rootExec));
|
||||
await repo.InsertIfNotExistsAsync(NewEvent(siteId, occurredAtUtc: t0.AddMinutes(1), executionId: rootExec));
|
||||
await repo.InsertIfNotExistsAsync(NewEvent(siteId, occurredAtUtc: t0.AddMinutes(2), executionId: midExec, parentExecutionId: rootExec));
|
||||
await repo.InsertIfNotExistsAsync(NewEvent(siteId, occurredAtUtc: t0.AddMinutes(3), executionId: midExec, parentExecutionId: rootExec));
|
||||
await repo.InsertIfNotExistsAsync(NewEvent(siteId, occurredAtUtc: t0.AddMinutes(4), executionId: leafExec, parentExecutionId: midExec));
|
||||
await repo.InsertIfNotExistsAsync(NewEvent(siteId, occurredAtUtc: t0.AddMinutes(5), executionId: leafExec, parentExecutionId: midExec));
|
||||
var rootT0 = t0;
|
||||
var rootT1 = t0.AddMinutes(1);
|
||||
var midT0 = t0.AddMinutes(2);
|
||||
var midT1 = t0.AddMinutes(3);
|
||||
var leafT0 = t0.AddMinutes(4);
|
||||
var leafT1 = t0.AddMinutes(5);
|
||||
|
||||
await repo.InsertIfNotExistsAsync(NewEvent(siteId, occurredAtUtc: rootT0, channel: AuditChannel.ApiOutbound, status: AuditStatus.Submitted, executionId: rootExec));
|
||||
await repo.InsertIfNotExistsAsync(NewEvent(siteId, occurredAtUtc: rootT1, channel: AuditChannel.ApiOutbound, status: AuditStatus.Delivered, executionId: rootExec));
|
||||
await repo.InsertIfNotExistsAsync(NewEvent(siteId, occurredAtUtc: midT0, channel: AuditChannel.DbOutbound, status: AuditStatus.Submitted, executionId: midExec, parentExecutionId: rootExec));
|
||||
await repo.InsertIfNotExistsAsync(NewEvent(siteId, occurredAtUtc: midT1, channel: AuditChannel.DbOutbound, status: AuditStatus.Failed, executionId: midExec, parentExecutionId: rootExec));
|
||||
await repo.InsertIfNotExistsAsync(NewEvent(siteId, occurredAtUtc: leafT0, channel: AuditChannel.Notification, status: AuditStatus.Submitted, executionId: leafExec, parentExecutionId: midExec));
|
||||
await repo.InsertIfNotExistsAsync(NewEvent(siteId, occurredAtUtc: leafT1, channel: AuditChannel.Notification, status: AuditStatus.Parked, executionId: leafExec, parentExecutionId: midExec));
|
||||
|
||||
var expected = new[] { rootExec, midExec, leafExec };
|
||||
|
||||
@@ -807,6 +817,37 @@ public class AuditLogRepositoryTests : IClassFixture<MsSqlMigrationFixture>
|
||||
Assert.Equal(2, root.RowCount);
|
||||
Assert.Equal(2, mid.RowCount);
|
||||
Assert.Equal(2, leaf.RowCount);
|
||||
|
||||
// Each populated node aggregates its own rows' channels and
|
||||
// statuses — distinct per execution, so a regression that mixes
|
||||
// executions or drops the per-id aggregate would be caught.
|
||||
Assert.Equal(
|
||||
new[] { nameof(AuditChannel.ApiOutbound) },
|
||||
root.Channels);
|
||||
Assert.Equal(
|
||||
new[] { nameof(AuditChannel.DbOutbound) },
|
||||
mid.Channels);
|
||||
Assert.Equal(
|
||||
new[] { nameof(AuditChannel.Notification) },
|
||||
leaf.Channels);
|
||||
|
||||
Assert.True(
|
||||
new[] { nameof(AuditStatus.Submitted), nameof(AuditStatus.Delivered) }
|
||||
.ToHashSet().SetEquals(root.Statuses));
|
||||
Assert.True(
|
||||
new[] { nameof(AuditStatus.Submitted), nameof(AuditStatus.Failed) }
|
||||
.ToHashSet().SetEquals(mid.Statuses));
|
||||
Assert.True(
|
||||
new[] { nameof(AuditStatus.Submitted), nameof(AuditStatus.Parked) }
|
||||
.ToHashSet().SetEquals(leaf.Statuses));
|
||||
|
||||
// Each populated node's timestamp span covers exactly its two rows.
|
||||
Assert.Equal(rootT0, root.FirstOccurredAtUtc);
|
||||
Assert.Equal(rootT1, root.LastOccurredAtUtc);
|
||||
Assert.Equal(midT0, mid.FirstOccurredAtUtc);
|
||||
Assert.Equal(midT1, mid.LastOccurredAtUtc);
|
||||
Assert.Equal(leafT0, leaf.FirstOccurredAtUtc);
|
||||
Assert.Equal(leafT1, leaf.LastOccurredAtUtc);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user