f0700571e8
51 new test cases (34 methods) in Sql.Common.Tests covering AtTimeEvaluator, AggregateEvaluator (hand-built tag-A bucket rows, ProcessedRead literals), BucketPlan, LiveWritePreparer and HistoryUpdatePlanner (incl. resolutionTicks=10 truncate-before-dedup and the superseded-duplicate outcome), plus EventContinuation, HistorianHealthCounters, ClampSeverity and StoredSample.FromStored. Each was falsified by reverting the rule it pins (37 mutations, all red, all restored). Claude-Session: https://claude.ai/code/session_01Ef2ozxS7QPanGT8uoATbTe
72 lines
2.8 KiB
C#
72 lines
2.8 KiB
C#
using Shouldly;
|
|
using Xunit;
|
|
using ZB.MOM.WW.OtOpcUa.Core.Abstractions;
|
|
using static ZB.MOM.WW.OtOpcUa.Driver.Historian.Sql.Common.Tests.Fixture;
|
|
|
|
namespace ZB.MOM.WW.OtOpcUa.Driver.Historian.Sql.Common.Tests;
|
|
|
|
public sealed class BucketPlanTests
|
|
{
|
|
[Fact]
|
|
public void BucketPlan_rejects_more_than_100k_buckets()
|
|
{
|
|
var ex = Should.Throw<ArgumentOutOfRangeException>(() =>
|
|
BucketPlan.Create(DateTime.UnixEpoch, DateTime.UnixEpoch.AddSeconds(100_001), TimeSpan.FromSeconds(1)));
|
|
ex.ParamName.ShouldBe("interval");
|
|
}
|
|
|
|
[Fact]
|
|
public void Exactly_100k_one_tick_buckets_are_allowed()
|
|
{
|
|
var p = BucketPlan.Create(At(0), At(0).AddTicks(100_000), TimeSpan.FromTicks(1));
|
|
p.BucketCount.ShouldBe(100_000);
|
|
p.IntervalTicks.ShouldBe(1);
|
|
}
|
|
|
|
[Fact]
|
|
public void A_partial_last_interval_is_its_own_bucket()
|
|
{
|
|
var p = BucketPlan.Create(At(0), At(50), TimeSpan.FromSeconds(20));
|
|
p.BucketCount.ShouldBe(3);
|
|
p.BucketStart(2).ShouldBe(At(40));
|
|
p.BucketStart(2).Kind.ShouldBe(DateTimeKind.Utc);
|
|
}
|
|
|
|
[Fact]
|
|
public void Zero_or_oversized_interval_is_one_bucket_and_an_empty_window_none()
|
|
{
|
|
BucketPlan.Create(At(0), At(80), TimeSpan.Zero).BucketCount.ShouldBe(1);
|
|
var wide = BucketPlan.Create(At(0), At(80), TimeSpan.FromDays(1));
|
|
wide.BucketCount.ShouldBe(1);
|
|
wide.IntervalTicks.ShouldBe(80 * TimeSpan.TicksPerSecond);
|
|
BucketPlan.Create(At(0), At(0), TimeSpan.FromSeconds(1)).BucketCount.ShouldBe(0);
|
|
}
|
|
|
|
[Fact]
|
|
public void A_reverse_window_is_planned_forward_and_assembled_descending()
|
|
{
|
|
var p = BucketPlan.Create(At(80), At(0), TimeSpan.FromSeconds(20));
|
|
p.Reversed.ShouldBeTrue();
|
|
p.StartUtc.ShouldBe(At(0));
|
|
p.EndUtc.ShouldBe(At(80));
|
|
var s = p.Assemble(new Dictionary<long, DataValueSnapshot>(), t => new DataValueSnapshot(null, 0, t, t));
|
|
s.Select(x => x.SourceTimestampUtc).ShouldBe([At(60), At(40), At(20), At(0)]);
|
|
}
|
|
|
|
[Fact]
|
|
public void Assemble_fills_missing_buckets_with_the_empty_factory()
|
|
{
|
|
var p = BucketPlan.Create(At(0), At(60), TimeSpan.FromSeconds(20));
|
|
var hit = new DataValueSnapshot(7d, 0, At(20), At(20));
|
|
var s = p.Assemble(new Dictionary<long, DataValueSnapshot> { [1] = hit }, t => new DataValueSnapshot(null, 1u, t, t));
|
|
s.Select(x => x.Value).ShouldBe([null, (object?)7d, null]);
|
|
s[0].SourceTimestampUtc.ShouldBe(At(0));
|
|
s[2].SourceTimestampUtc.ShouldBe(At(40));
|
|
}
|
|
|
|
[Fact]
|
|
public void StartUnixTicks_is_ticks_since_the_unix_epoch()
|
|
=> BucketPlan.Create(DateTime.UnixEpoch.AddTicks(1234567), DateTime.UnixEpoch.AddSeconds(10), TimeSpan.Zero)
|
|
.StartUnixTicks.ShouldBe(1234567L);
|
|
}
|