Files
lmxopcua/tests/Server/ZB.MOM.WW.OtOpcUa.Runtime.Tests/Historian/HistorianRetentionServiceTests.cs
T

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)]);
}
}