96 lines
3.6 KiB
C#
96 lines
3.6 KiB
C#
using System.Diagnostics.Metrics;
|
|
using Shouldly;
|
|
using Xunit;
|
|
using ZB.MOM.WW.OtOpcUa.Commons.Observability;
|
|
using ZB.MOM.WW.OtOpcUa.Runtime.Historian;
|
|
using ZB.MOM.WW.OtOpcUa.Runtime.Redundancy;
|
|
|
|
namespace ZB.MOM.WW.OtOpcUa.Runtime.Tests.Historian;
|
|
|
|
public sealed class HistorianRetentionServiceTests
|
|
{
|
|
private sealed class FakeRetention(Func<Task<IReadOnlyList<RetentionPurgeResult>>> run) : IHistorianRetention
|
|
{
|
|
public int Calls;
|
|
public TimeSpan Interval => TimeSpan.FromHours(1);
|
|
public Task<IReadOnlyList<RetentionPurgeResult>> PurgeAsync(CancellationToken ct)
|
|
{
|
|
Interlocked.Increment(ref Calls);
|
|
return run();
|
|
}
|
|
}
|
|
|
|
private sealed class FakeRole(bool record) : IHistorizationRoleView
|
|
{
|
|
public bool ShouldRecordHistorizedValues => record;
|
|
public void PublishHistorizationRole(bool shouldRecordHistorizedValues) { }
|
|
}
|
|
|
|
private static Task<IReadOnlyList<RetentionPurgeResult>> Rows(long sample, long ev) =>
|
|
Task.FromResult<IReadOnlyList<RetentionPurgeResult>>(
|
|
[new RetentionPurgeResult("Sample", sample), new RetentionPurgeResult("Event", ev)]);
|
|
|
|
[Fact]
|
|
public async Task Runs_when_role_is_unknown_and_when_recording()
|
|
{
|
|
var r1 = new FakeRetention(() => Rows(0, 0));
|
|
(await new HistorianRetentionService(r1, null).TickAsync(CancellationToken.None)).ShouldBeTrue();
|
|
var r2 = new FakeRetention(() => Rows(0, 0));
|
|
(await new HistorianRetentionService(r2, new FakeRole(true)).TickAsync(CancellationToken.None)).ShouldBeTrue();
|
|
r1.Calls.ShouldBe(1);
|
|
r2.Calls.ShouldBe(1);
|
|
}
|
|
|
|
[Fact]
|
|
public async Task Skips_when_not_the_recording_node()
|
|
{
|
|
var r = new FakeRetention(() => Rows(1, 1));
|
|
(await new HistorianRetentionService(r, new FakeRole(false)).TickAsync(CancellationToken.None)).ShouldBeFalse();
|
|
r.Calls.ShouldBe(0);
|
|
}
|
|
|
|
[Fact]
|
|
public async Task Null_retention_is_inert()
|
|
{
|
|
(await new HistorianRetentionService(null, null).TickAsync(CancellationToken.None)).ShouldBeFalse();
|
|
}
|
|
|
|
[Fact]
|
|
public async Task A_throwing_purge_does_not_escape()
|
|
{
|
|
var r = new FakeRetention(() => throw new InvalidOperationException("boom"));
|
|
(await new HistorianRetentionService(r, null).TickAsync(CancellationToken.None)).ShouldBeFalse();
|
|
r.Calls.ShouldBe(1);
|
|
}
|
|
|
|
[Fact]
|
|
public async Task Emits_purged_counter_per_table_and_skips_zero_rows()
|
|
{
|
|
var marker = Guid.NewGuid().ToString("N");
|
|
var seen = new List<(string Table, long Value)>();
|
|
using var listener = new MeterListener();
|
|
listener.InstrumentPublished = (inst, l) =>
|
|
{
|
|
if (inst.Meter.Name == OtOpcUaTelemetry.MeterName && inst.Name == HistorianRetentionService.PurgedCounterName)
|
|
l.EnableMeasurementEvents(inst);
|
|
};
|
|
listener.SetMeasurementEventCallback<long>((_, value, tags, _) =>
|
|
{
|
|
string? table = null; var mine = false;
|
|
foreach (var t in tags)
|
|
{
|
|
if (t.Key == "table") table = (string?)t.Value;
|
|
if (t.Key == "test" && (string?)t.Value == marker) mine = true;
|
|
}
|
|
if (mine) lock (seen) seen.Add((table!, value));
|
|
});
|
|
listener.Start();
|
|
|
|
var r = new FakeRetention(() => Rows(7, 0));
|
|
var svc = new HistorianRetentionService(r, null, null, new KeyValuePair<string, object?>("test", marker));
|
|
await svc.TickAsync(CancellationToken.None);
|
|
|
|
lock (seen) seen.ShouldBe([("Sample", 7L)]);
|
|
}
|
|
}
|