513fa1aa16
Comment/XML-doc changes only: - add summaries to ~10k test methods, fixtures, fakes and helpers (+ <param> on theories, <returns> on async Task methods, <inheritdoc/> on interface implementations the checker can resolve) - strip ~2.6k project-tracking references (WP*, P*-*, row-NN, Task N, #NN, Component-0NN) from comments, keeping the explanatory text; reword false positives in comments only - real docs on fakes implementing BCL/library interfaces (IDisposable, ILogger, IOptionsMonitor, IAsyncLifetime) - reword the last 5 src/ false positives (CWE-1236, failures-1, year-0001, M365) Only non-comment edits: four one-line nested classes split across lines so members can carry docs. Build 0 errors / 0 warnings.
172 lines
7.0 KiB
C#
172 lines
7.0 KiB
C#
using NSubstitute;
|
|
using ZB.MOM.WW.ScadaBridge.AuditLog.Kpi;
|
|
using ZB.MOM.WW.ScadaBridge.Commons.Interfaces.Kpi;
|
|
using ZB.MOM.WW.ScadaBridge.Commons.Interfaces.Repositories;
|
|
using ZB.MOM.WW.ScadaBridge.Commons.Types;
|
|
using ZB.MOM.WW.ScadaBridge.Commons.Types.Kpi;
|
|
|
|
namespace ZB.MOM.WW.ScadaBridge.AuditLog.Tests.Kpi;
|
|
|
|
/// <summary>
|
|
/// "KPI History & Trends" coverage for <see cref="AuditLogKpiSampleSource"/> —
|
|
/// the <see cref="ZB.MOM.WW.ScadaBridge.Commons.Interfaces.Kpi.IKpiSampleSource"/> the
|
|
/// central recorder enumerates to snapshot Audit Log KPIs. Asserts the source maps the
|
|
/// repository's <see cref="AuditLogKpiSnapshot"/> onto exactly three Global-scope samples
|
|
/// with the canonical metric names, the recorder-supplied capture instant, and a null
|
|
/// <c>ScopeKey</c>.
|
|
/// </summary>
|
|
public class AuditLogKpiSampleSourceTests
|
|
{
|
|
private static readonly DateTime CapturedAt = new(2026, 6, 17, 12, 30, 0, DateTimeKind.Utc);
|
|
|
|
/// <summary>Verifies that the sample source reports the AuditLog source name.</summary>
|
|
[Fact]
|
|
public void Source_is_AuditLog()
|
|
{
|
|
var repo = Substitute.For<IAuditLogRepository>();
|
|
var sut = new AuditLogKpiSampleSource(repo);
|
|
|
|
Assert.Equal(KpiSources.AuditLog, sut.Source);
|
|
}
|
|
|
|
/// <summary>Verifies that collecting emits three global samples mapped from the health snapshot.</summary>
|
|
/// <returns>A task that represents the asynchronous operation.</returns>
|
|
[Fact]
|
|
public async Task CollectAsync_emits_three_global_samples_mapped_from_snapshot()
|
|
{
|
|
// Distinct values so a swapped mapping (e.g. error ↔ backlog) is caught.
|
|
var snapshot = new AuditLogKpiSnapshot(
|
|
TotalEventsLastHour: 4200,
|
|
ErrorEventsLastHour: 17,
|
|
BacklogTotal: 305,
|
|
AsOfUtc: new DateTime(2026, 6, 17, 12, 30, 5, DateTimeKind.Utc));
|
|
|
|
var repo = Substitute.For<IAuditLogRepository>();
|
|
repo.GetKpiSnapshotAsync(Arg.Any<TimeSpan>(), Arg.Any<DateTime?>(), Arg.Any<CancellationToken>())
|
|
.Returns(snapshot);
|
|
|
|
var sut = new AuditLogKpiSampleSource(repo);
|
|
|
|
var samples = await sut.CollectAsync(CapturedAt);
|
|
|
|
Assert.Equal(3, samples.Count);
|
|
|
|
// Every sample: AuditLog source, Global scope, null ScopeKey, the
|
|
// recorder-stamped capture instant.
|
|
Assert.All(samples, s =>
|
|
{
|
|
Assert.Equal(KpiSources.AuditLog, s.Source);
|
|
Assert.Equal(KpiScopes.Global, s.Scope);
|
|
Assert.Null(s.ScopeKey);
|
|
Assert.Equal(CapturedAt, s.CapturedAtUtc);
|
|
});
|
|
|
|
// Exact (Metric, Value) catalog mapping.
|
|
AssertMetric(samples, "totalEventsLastHour", 4200);
|
|
AssertMetric(samples, "errorEventsLastHour", 17);
|
|
AssertMetric(samples, "backlogTotal", 305);
|
|
}
|
|
|
|
/// <summary>Verifies that the sample window is anchored on the snapshot's capture time.</summary>
|
|
/// <returns>A task that represents the asynchronous operation.</returns>
|
|
[Fact]
|
|
public async Task CollectAsync_anchors_the_window_on_capturedAtUtc()
|
|
{
|
|
var repo = Substitute.For<IAuditLogRepository>();
|
|
repo.GetKpiSnapshotAsync(Arg.Any<TimeSpan>(), Arg.Any<DateTime?>(), Arg.Any<CancellationToken>())
|
|
.Returns(new AuditLogKpiSnapshot(0, 0, 0, CapturedAt));
|
|
|
|
var sut = new AuditLogKpiSampleSource(repo);
|
|
|
|
await sut.CollectAsync(CapturedAt);
|
|
|
|
// The trailing 1h window is anchored on the recorder's shared instant,
|
|
// not the repository's server-side UtcNow, so the sample is reproducible.
|
|
await repo.Received(1).GetKpiSnapshotAsync(
|
|
TimeSpan.FromHours(1),
|
|
CapturedAt,
|
|
Arg.Any<CancellationToken>());
|
|
}
|
|
|
|
/// <summary>Verifies that collecting returns no samples when the snapshot is null.</summary>
|
|
/// <returns>A task that represents the asynchronous operation.</returns>
|
|
[Fact]
|
|
public async Task CollectAsync_returns_empty_when_snapshot_is_null()
|
|
{
|
|
var repo = Substitute.For<IAuditLogRepository>();
|
|
repo.GetKpiSnapshotAsync(Arg.Any<TimeSpan>(), Arg.Any<DateTime?>(), Arg.Any<CancellationToken>())
|
|
.Returns((AuditLogKpiSnapshot?)null!);
|
|
|
|
var sut = new AuditLogKpiSampleSource(repo);
|
|
|
|
var samples = await sut.CollectAsync(CapturedAt);
|
|
|
|
Assert.NotNull(samples);
|
|
Assert.Empty(samples);
|
|
}
|
|
|
|
/// <summary>Verifies that the backlog provider's value is used when one is registered.</summary>
|
|
/// <returns>A task that represents the asynchronous operation.</returns>
|
|
[Fact]
|
|
public async Task CollectAsync_uses_backlog_provider_when_present()
|
|
{
|
|
// The repository's snapshot carries the hardwired zero the append-only
|
|
// repo always emits; the provider carries the real cross-site aggregate.
|
|
var snapshot = new AuditLogKpiSnapshot(
|
|
TotalEventsLastHour: 10,
|
|
ErrorEventsLastHour: 1,
|
|
BacklogTotal: 0,
|
|
AsOfUtc: CapturedAt);
|
|
|
|
var repo = Substitute.For<IAuditLogRepository>();
|
|
repo.GetKpiSnapshotAsync(Arg.Any<TimeSpan>(), Arg.Any<DateTime?>(), Arg.Any<CancellationToken>())
|
|
.Returns(snapshot);
|
|
|
|
var backlog = Substitute.For<IAuditBacklogProvider>();
|
|
backlog.GetPendingBacklogTotal().Returns(42L);
|
|
|
|
var sut = new AuditLogKpiSampleSource(repo, backlog);
|
|
|
|
var samples = await sut.CollectAsync(CapturedAt);
|
|
|
|
// Volume + error metrics still come from the snapshot; backlog is the
|
|
// provider's real aggregate, not the snapshot's zero.
|
|
AssertMetric(samples, "totalEventsLastHour", 10);
|
|
AssertMetric(samples, "errorEventsLastHour", 1);
|
|
AssertMetric(samples, "backlogTotal", 42);
|
|
}
|
|
|
|
/// <summary>Verifies that the snapshot's own backlog total is used when no backlog provider is registered.</summary>
|
|
/// <returns>A task that represents the asynchronous operation.</returns>
|
|
[Fact]
|
|
public async Task CollectAsync_falls_back_to_snapshot_backlog_when_provider_absent()
|
|
{
|
|
// No provider (sites / unit tests): the sample source degrades to the
|
|
// snapshot's own BacklogTotal — unchanged behavior from before the provider existed.
|
|
var snapshot = new AuditLogKpiSnapshot(
|
|
TotalEventsLastHour: 10,
|
|
ErrorEventsLastHour: 1,
|
|
BacklogTotal: 7,
|
|
AsOfUtc: CapturedAt);
|
|
|
|
var repo = Substitute.For<IAuditLogRepository>();
|
|
repo.GetKpiSnapshotAsync(Arg.Any<TimeSpan>(), Arg.Any<DateTime?>(), Arg.Any<CancellationToken>())
|
|
.Returns(snapshot);
|
|
|
|
var sut = new AuditLogKpiSampleSource(repo, backlogProvider: null);
|
|
|
|
var samples = await sut.CollectAsync(CapturedAt);
|
|
|
|
AssertMetric(samples, "backlogTotal", 7);
|
|
}
|
|
|
|
private static void AssertMetric(
|
|
IReadOnlyList<Commons.Entities.Kpi.KpiSample> samples,
|
|
string metric,
|
|
double expectedValue)
|
|
{
|
|
var sample = Assert.Single(samples, s => s.Metric == metric);
|
|
Assert.Equal(expectedValue, sample.Value);
|
|
}
|
|
}
|