feat(ui): add Node column + filter to AuditLog grid
This commit is contained in:
@@ -297,6 +297,91 @@ public class AuditLogRepositoryTests : IClassFixture<MsSqlMigrationFixture>
|
||||
Assert.All(rows, r => Assert.Equal(siteId, r.SourceSiteId));
|
||||
}
|
||||
|
||||
// ──────────────────────────────────────────────────────────────────────
|
||||
// Task 15: SourceNodes filter + GetDistinctSourceNodesAsync. Pins the new
|
||||
// Node multi-select contract — non-empty list → SQL IN (…); NULL
|
||||
// SourceNode rows are excluded when the filter is set; the distinct
|
||||
// enumeration omits nulls and orders ascending.
|
||||
// ──────────────────────────────────────────────────────────────────────
|
||||
|
||||
[SkippableFact]
|
||||
public async Task QueryAsync_FilterBySourceNode_ReturnsMatchingRows()
|
||||
{
|
||||
Skip.IfNot(_fixture.Available, _fixture.SkipReason);
|
||||
|
||||
var siteId = NewSiteId();
|
||||
await using var context = CreateContext();
|
||||
var repo = new AuditLogRepository(context);
|
||||
|
||||
var t0 = new DateTime(2026, 5, 4, 10, 0, 0, DateTimeKind.Utc);
|
||||
await repo.InsertIfNotExistsAsync(NewEvent(siteId, occurredAtUtc: t0, sourceNode: "central-a"));
|
||||
await repo.InsertIfNotExistsAsync(NewEvent(siteId, occurredAtUtc: t0.AddMinutes(1), sourceNode: "central-b"));
|
||||
await repo.InsertIfNotExistsAsync(NewEvent(siteId, occurredAtUtc: t0.AddMinutes(2), sourceNode: null));
|
||||
|
||||
var rows = await repo.QueryAsync(
|
||||
new AuditLogQueryFilter(
|
||||
SourceSiteIds: new[] { siteId },
|
||||
SourceNodes: new[] { "central-a" }),
|
||||
new AuditLogPaging(PageSize: 10));
|
||||
|
||||
Assert.Single(rows);
|
||||
Assert.Equal("central-a", rows[0].SourceNode);
|
||||
}
|
||||
|
||||
[SkippableFact]
|
||||
public async Task QueryAsync_FilterByMultipleSourceNodes_ReturnsUnion()
|
||||
{
|
||||
Skip.IfNot(_fixture.Available, _fixture.SkipReason);
|
||||
|
||||
var siteId = NewSiteId();
|
||||
await using var context = CreateContext();
|
||||
var repo = new AuditLogRepository(context);
|
||||
|
||||
var t0 = new DateTime(2026, 5, 4, 11, 0, 0, DateTimeKind.Utc);
|
||||
await repo.InsertIfNotExistsAsync(NewEvent(siteId, occurredAtUtc: t0, sourceNode: "central-a"));
|
||||
await repo.InsertIfNotExistsAsync(NewEvent(siteId, occurredAtUtc: t0.AddMinutes(1), sourceNode: "central-b"));
|
||||
await repo.InsertIfNotExistsAsync(NewEvent(siteId, occurredAtUtc: t0.AddMinutes(2), sourceNode: "site-plant-a-node-a"));
|
||||
|
||||
var rows = await repo.QueryAsync(
|
||||
new AuditLogQueryFilter(
|
||||
SourceSiteIds: new[] { siteId },
|
||||
SourceNodes: new[] { "central-a", "central-b" }),
|
||||
new AuditLogPaging(PageSize: 10));
|
||||
|
||||
Assert.Equal(2, rows.Count);
|
||||
Assert.All(rows, r => Assert.Contains(r.SourceNode, new[] { "central-a", "central-b" }));
|
||||
}
|
||||
|
||||
[SkippableFact]
|
||||
public async Task GetDistinctSourceNodesAsync_ReturnsDistinctNonNullValues_Ordered()
|
||||
{
|
||||
Skip.IfNot(_fixture.Available, _fixture.SkipReason);
|
||||
|
||||
var siteId = NewSiteId();
|
||||
await using var context = CreateContext();
|
||||
var repo = new AuditLogRepository(context);
|
||||
|
||||
var t0 = new DateTime(2026, 5, 4, 12, 0, 0, DateTimeKind.Utc);
|
||||
await repo.InsertIfNotExistsAsync(NewEvent(siteId, occurredAtUtc: t0, sourceNode: "central-b"));
|
||||
await repo.InsertIfNotExistsAsync(NewEvent(siteId, occurredAtUtc: t0.AddMinutes(1), sourceNode: "central-a"));
|
||||
// Duplicate of "central-a" — must collapse to one entry.
|
||||
await repo.InsertIfNotExistsAsync(NewEvent(siteId, occurredAtUtc: t0.AddMinutes(2), sourceNode: "central-a"));
|
||||
// Null row — must be excluded entirely.
|
||||
await repo.InsertIfNotExistsAsync(NewEvent(siteId, occurredAtUtc: t0.AddMinutes(3), sourceNode: null));
|
||||
|
||||
var nodes = await repo.GetDistinctSourceNodesAsync();
|
||||
|
||||
// The whole table is scanned (no per-test scoping on this query), so the
|
||||
// assertion is "our seeded values appear once each, in order" rather
|
||||
// than a strict equality on the full result.
|
||||
Assert.Contains("central-a", nodes);
|
||||
Assert.Contains("central-b", nodes);
|
||||
// No null entry — the WHERE SourceNode IS NOT NULL clause drops them.
|
||||
Assert.DoesNotContain(nodes, n => n is null);
|
||||
// Ordered ascending.
|
||||
Assert.Equal(nodes.OrderBy(n => n, StringComparer.Ordinal), nodes);
|
||||
}
|
||||
|
||||
[SkippableFact]
|
||||
public async Task QueryAsync_FilterByExecutionId_ReturnsMatchingRows()
|
||||
{
|
||||
|
||||
Reference in New Issue
Block a user