97 lines
4.3 KiB
C#
97 lines
4.3 KiB
C#
using Shouldly;
|
|
using Xunit;
|
|
using ZB.MOM.WW.OtOpcUa.Core.Abstractions;
|
|
|
|
namespace ZB.MOM.WW.OtOpcUa.Driver.Historian.SqlServer.Tests;
|
|
|
|
public sealed class SqlServerHistorianProviderTests
|
|
{
|
|
private static SqlServerHistorianProvider Make() => new(new SqlServerHistorianOptions
|
|
{
|
|
ConnectionString = "Server=localhost;User Id=sa;Password=x;TrustServerCertificate=True",
|
|
});
|
|
|
|
[Theory]
|
|
[InlineData("Raw")]
|
|
[InlineData("AtTime")]
|
|
[InlineData("Processed")]
|
|
[InlineData("Events")]
|
|
[InlineData("Modified")]
|
|
public async Task A_connection_failure_is_exposed_as_a_transport_failure(string kind)
|
|
{
|
|
var provider = new SqlServerHistorianProvider(new SqlServerHistorianOptions {
|
|
ConnectionString = "Server=127.0.0.1,1;User Id=sa;Password=test;TrustServerCertificate=True;Connect Timeout=1;ConnectRetryCount=0;Pooling=False", AutoCreateSchema = false
|
|
});
|
|
using var source = provider.CreateDataSource(new NullSp());
|
|
var t = new DateTime(2026, 1, 1, 0, 0, 0, DateTimeKind.Utc);
|
|
Func<Task> read = kind switch
|
|
{
|
|
"Raw" => async () => await source.ReadRawAsync("T", t, t.AddHours(1), 10, TestContext.Current.CancellationToken),
|
|
"AtTime" => async () => await source.ReadAtTimeAsync("T", [t], TestContext.Current.CancellationToken),
|
|
"Processed" => async () => await source.ReadProcessedAsync("T", t, t.AddHours(1), TimeSpan.FromSeconds(10), HistoryAggregateType.Average, TestContext.Current.CancellationToken),
|
|
"Events" => async () => await source.ReadEventsAsync("T", t, t.AddHours(1), 10, TestContext.Current.CancellationToken),
|
|
_ => async () => await source.ReadModifiedAsync("T", t, t.AddHours(1), 10, null, TestContext.Current.CancellationToken)
|
|
};
|
|
var error = await Should.ThrowAsync<System.IO.IOException>(read);
|
|
error.InnerException.ShouldNotBeNull();
|
|
// A cached bootstrap outage must remain retryable on subsequent reads as well.
|
|
(await Should.ThrowAsync<System.IO.IOException>(read)).InnerException.ShouldNotBeNull();
|
|
}
|
|
|
|
[Fact]
|
|
public void Name_and_all_six_kinds()
|
|
{
|
|
var p = Make();
|
|
p.Name.ShouldBe("SqlServer");
|
|
p.SupportedKinds.ShouldBe(Enum.GetValues<HistorianValueKind>(), ignoreOrder: true);
|
|
p.SupportedKinds.Count.ShouldBe(6);
|
|
}
|
|
|
|
[Fact]
|
|
public void Creates_all_five_seams_without_touching_sql()
|
|
{
|
|
var sp = new NullSp();
|
|
var p = Make();
|
|
p.CreateDataSource(sp).ShouldBeOfType<SqlServerHistorianDataSource>();
|
|
p.CreateValueWriter(sp).ShouldBeOfType<SqlServerHistorianValueWriter>();
|
|
p.CreateAlarmWriter(sp).ShouldBeOfType<SqlServerAlarmHistorianWriter>();
|
|
p.CreateProvisioning(sp).ShouldBeOfType<SqlServerTagProvisioner>();
|
|
p.CreateUpdater(sp).ShouldBeOfType<SqlServerHistorianDataUpdater>();
|
|
}
|
|
|
|
[Fact]
|
|
public void Supported_kinds_are_a_frozen_set() =>
|
|
Make().SupportedKinds.ShouldBeAssignableTo<System.Collections.Frozen.FrozenSet<HistorianValueKind>>();
|
|
|
|
/// <summary>
|
|
/// An invalid schema 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_schema_fails_only_when_a_seam_is_built()
|
|
{
|
|
var p = new SqlServerHistorianProvider(new SqlServerHistorianOptions { ConnectionString = "x", Schema = "bad;schema" });
|
|
p.Name.ShouldBe("SqlServer");
|
|
Should.Throw<ArgumentException>(() => p.CreateDataSource(new NullSp()));
|
|
}
|
|
|
|
[Fact]
|
|
public void Every_seam_shares_one_connection_bootstrapper_and_catalog()
|
|
{
|
|
var sp = new NullSp();
|
|
var p = Make();
|
|
var a = (SqlServerHistorianDataSource)p.CreateDataSource(sp);
|
|
var b = (SqlServerHistorianDataSource)p.CreateDataSource(sp);
|
|
Field(a, "_bootstrap").ShouldBeSameAs(Field(b, "_bootstrap"));
|
|
Field(a, "_catalog").ShouldBeSameAs(Field(b, "_catalog"));
|
|
}
|
|
|
|
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;
|
|
}
|
|
}
|