Files
lmxopcua/tests/Drivers/ZB.MOM.WW.OtOpcUa.Driver.Historian.QuestDb.Tests/QuestDbTimeClustersTests.cs
T

63 lines
2.8 KiB
C#

using Shouldly;
using Xunit;
namespace ZB.MOM.WW.OtOpcUa.Driver.Historian.QuestDb.Tests;
/// <summary>
/// The shared existence-query clustering (moved here, names unchanged, from the writer's <c>WriteTests</c> and the
/// updater's <c>UpdateTests</c> when the two private copies were consolidated into <see cref="QuestDbTimeClusters"/>).
/// </summary>
public sealed class QuestDbTimeClustersTests
{
private static readonly DateTime T0 = new(2026, 3, 1, 12, 0, 0, DateTimeKind.Utc);
[Fact]
public void Cluster_splits_on_a_gap_over_the_limit_and_at_the_per_cluster_cap()
{
var g = TimeSpan.FromMinutes(1);
QuestDbTimeClusters.Cluster([], g, 10).ShouldBeEmpty();
QuestDbTimeClusters.Cluster([T0], g, 10).ShouldBe([(T0, T0)]);
// exactly the gap stays together; one tick over splits
QuestDbTimeClusters.Cluster([T0, T0 + g], g, 10).ShouldBe([(T0, T0 + g)]);
QuestDbTimeClusters.Cluster([T0, T0 + g + TimeSpan.FromTicks(10)], g, 10)
.ShouldBe([(T0, T0), (T0 + g + TimeSpan.FromTicks(10), T0 + g + TimeSpan.FromTicks(10))]);
// outlier gets its own range
var far = T0.AddYears(1);
QuestDbTimeClusters.Cluster([T0, T0.AddSeconds(1), far], g, 10).ShouldBe([(T0, T0.AddSeconds(1)), (far, far)]);
// cap: 5 close times, max 2 per cluster
var five = Enumerable.Range(0, 5).Select(i => T0.AddSeconds(i)).ToList();
QuestDbTimeClusters.Cluster(five, g, 2)
.ShouldBe([(five[0], five[1]), (five[2], five[3]), (five[4], five[4])]);
}
[Fact]
public void Cluster_splits_where_the_gap_exceeds_the_limit()
{
var gap = QuestDbTimeClusters.ExistenceGap;
var c = QuestDbTimeClusters.Cluster(
[T0.AddHours(2), T0, T0.AddSeconds(30), T0.AddHours(1)], gap);
c.ShouldBe([(T0, T0.AddSeconds(30)), (T0.AddHours(1), T0.AddHours(1)), (T0.AddHours(2), T0.AddHours(2))]);
QuestDbTimeClusters.Cluster([], gap).ShouldBeEmpty();
}
[Fact]
public void The_existence_gap_is_one_minute_and_the_default_cap_is_unbounded()
{
QuestDbTimeClusters.ExistenceGap.ShouldBe(TimeSpan.FromMinutes(1));
var many = Enumerable.Range(0, 5000).Select(i => T0.AddMilliseconds(i)).ToList();
QuestDbTimeClusters.Cluster(many, QuestDbTimeClusters.ExistenceGap).ShouldBe([(many[0], many[^1])]);
}
[Fact]
public void Unsorted_input_is_sorted_before_capping()
{
var times = new[] { T0.AddSeconds(3), T0, T0.AddSeconds(2), T0.AddSeconds(1) };
QuestDbTimeClusters.Cluster(times, TimeSpan.FromMinutes(1), 2)
.ShouldBe([(T0, T0.AddSeconds(1)), (T0.AddSeconds(2), T0.AddSeconds(3))]);
}
[Fact]
public void A_cap_below_one_is_rejected() =>
Should.Throw<ArgumentOutOfRangeException>(() => QuestDbTimeClusters.Cluster([T0], TimeSpan.FromMinutes(1), 0));
}