using Microsoft.Data.SqlClient; using Shouldly; using Xunit; using ZB.MOM.WW.OtOpcUa.Core.Abstractions; using ZB.MOM.WW.OtOpcUa.Driver.Historian.Sql.Common; namespace ZB.MOM.WW.OtOpcUa.Driver.Historian.SqlServer.IntegrationTests; [Collection(SqlHistorianCollection.Name)] public sealed class RawReadTests(SqlHistorianFixture fx) { private static readonly CancellationToken Ct = CancellationToken.None; private static DateTime At(double s) => ReadTestDb.At(s); [Fact] public async Task A_missing_schema_surfaces_in_health_LastError_and_is_not_latched() { fx.SkipIfUnavailable(); var o = new SqlServerHistorianOptions { ConnectionString = fx.ConnectionString, Schema = SqlHistorianFixture.UniqueSchema(), AutoCreateSchema = false, }; var conn = new SqlHistorianConnection(o); var clock = new FixedClock(DateTimeOffset.UtcNow); var boot = new SqlServerSchemaBootstrapper(conn, o, timeProvider: clock); var src = new SqlServerHistorianDataSource(conn, boot, new SqlServerTagCatalog(conn, boot)); await Should.ThrowAsync(() => src.ReadRawAsync("A", At(0), At(1), 0, Ct)); await Should.ThrowAsync(() => src.ReadEventsAsync(null, At(0), At(1), 0, Ct)); var h = src.GetHealthSnapshot(); h.TotalFailures.ShouldBe(2); h.ConsecutiveFailures.ShouldBe(2); h.LastError!.ShouldContain("missing version 1"); h.ProcessConnectionOpen.ShouldBeFalse(); // Another node creates the schema; the same data source then succeeds (bootstrap failure is not latched). var creator = new SqlServerHistorianOptions { ConnectionString = o.ConnectionString, Schema = o.Schema }; await new SqlServerSchemaBootstrapper(new SqlHistorianConnection(creator), creator).EnsureAsync(Ct); clock.Now += BootstrapFailureCache.DefaultDuration; // past the 5 s failure cache (M-2) (await src.ReadRawAsync("A", At(0), At(1), 0, Ct)).Samples.ShouldBeEmpty(); src.GetHealthSnapshot().ConsecutiveFailures.ShouldBe(0); } }