feat(historian): QuestDb provider, registry + consumer-gated validator, compose env
Claude-Session: https://claude.ai/code/session_01Ef2ozxS7QPanGT8uoATbTe
This commit is contained in:
+90
@@ -0,0 +1,90 @@
|
||||
using Shouldly;
|
||||
using Xunit;
|
||||
using ZB.MOM.WW.OtOpcUa.Core.Abstractions;
|
||||
|
||||
namespace ZB.MOM.WW.OtOpcUa.Driver.Historian.QuestDb.Tests;
|
||||
|
||||
public sealed class QuestDbHistorianProviderTests
|
||||
{
|
||||
private static QuestDbHistorianProvider Make() => new(new QuestDbHistorianOptions
|
||||
{
|
||||
ConnectionString = "Host=localhost;Port=18812;Username=admin;Password=quest;Database=qdb;ServerCompatibilityMode=NoTypeLoading",
|
||||
});
|
||||
|
||||
[Fact]
|
||||
public void Name_and_all_six_kinds()
|
||||
{
|
||||
var p = Make();
|
||||
p.Name.ShouldBe("QuestDb");
|
||||
p.SupportedKinds.ShouldBe(Enum.GetValues<HistorianValueKind>(), ignoreOrder: true);
|
||||
p.SupportedKinds.Count.ShouldBe(6);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Creates_all_five_seams_without_touching_questdb()
|
||||
{
|
||||
var sp = new NullSp();
|
||||
var p = Make();
|
||||
p.CreateDataSource(sp).ShouldBeOfType<QuestDbHistorianDataSource>();
|
||||
p.CreateValueWriter(sp).ShouldBeOfType<QuestDbHistorianValueWriter>();
|
||||
p.CreateAlarmWriter(sp).ShouldBeOfType<QuestDbAlarmHistorianWriter>();
|
||||
p.CreateProvisioning(sp).ShouldBeOfType<QuestDbTagProvisioner>();
|
||||
p.CreateUpdater(sp).ShouldBeOfType<QuestDbHistorianDataUpdater>();
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Supported_kinds_are_a_frozen_set() =>
|
||||
Make().SupportedKinds.ShouldBeAssignableTo<System.Collections.Frozen.FrozenSet<HistorianValueKind>>();
|
||||
|
||||
/// <summary>
|
||||
/// An invalid table prefix must not fail provider construction (the host resolves the provider even when no
|
||||
/// consumer is enabled); it surfaces only when a seam is actually built.
|
||||
/// </summary>
|
||||
[Fact]
|
||||
public void An_invalid_prefix_fails_only_when_a_seam_is_built()
|
||||
{
|
||||
var p = new QuestDbHistorianProvider(new QuestDbHistorianOptions { ConnectionString = "Host=x", TablePrefix = "bad;prefix" });
|
||||
p.Name.ShouldBe("QuestDb");
|
||||
Should.Throw<ArgumentException>(() => p.CreateDataSource(new NullSp()));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Every_seam_shares_one_connection_bootstrapper_catalog_and_wal_wait()
|
||||
{
|
||||
var sp = new NullSp();
|
||||
var p = Make();
|
||||
var a = (QuestDbHistorianDataSource)p.CreateDataSource(sp);
|
||||
var b = (QuestDbHistorianDataSource)p.CreateDataSource(sp);
|
||||
var w = p.CreateValueWriter(sp);
|
||||
var u = p.CreateUpdater(sp)!;
|
||||
var prov = p.CreateProvisioning(sp);
|
||||
var alarm = p.CreateAlarmWriter(sp);
|
||||
|
||||
Field(a, "_conn").ShouldBeSameAs(Field(b, "_conn"));
|
||||
Field(a, "_conn").ShouldBeSameAs(Field(w, "_conn"));
|
||||
Field(a, "_conn").ShouldBeSameAs(Field(u, "_conn"));
|
||||
Field(a, "_conn").ShouldBeSameAs(Field(alarm, "_conn"));
|
||||
Field(a, "_bootstrap").ShouldBeSameAs(Field(b, "_bootstrap"));
|
||||
Field(a, "_bootstrap").ShouldBeSameAs(Field(prov, "_bootstrap"));
|
||||
Field(a, "_bootstrap").ShouldBeSameAs(Field(alarm, "_bootstrap"));
|
||||
Field(a, "_catalog").ShouldBeSameAs(Field(b, "_catalog"));
|
||||
Field(a, "_catalog").ShouldBeSameAs(Field(w, "_catalog"));
|
||||
Field(a, "_catalog").ShouldBeSameAs(Field(u, "_tags"));
|
||||
Field(a, "_catalog").ShouldBeSameAs(Field(prov, "_catalog"));
|
||||
Field(w, "_wal").ShouldBeSameAs(Field(u, "_wal"));
|
||||
Field(w, "_wal").ShouldBeSameAs(Field(prov, "_wal"));
|
||||
Field(w, "_wal").ShouldBeSameAs(Field(alarm, "_wal"));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void The_constructor_rejects_null_options() =>
|
||||
Should.Throw<ArgumentNullException>(() => new QuestDbHistorianProvider(null!));
|
||||
|
||||
private static object? Field(object o, string name) =>
|
||||
o.GetType().GetField(name, System.Reflection.BindingFlags.Instance | System.Reflection.BindingFlags.NonPublic)!.GetValue(o);
|
||||
|
||||
private sealed class NullSp : IServiceProvider
|
||||
{
|
||||
public object? GetService(Type serviceType) => null;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user