using Shouldly; using Xunit; using ZB.MOM.WW.OtOpcUa.Core.Abstractions; namespace ZB.MOM.WW.OtOpcUa.Driver.Historian.SqlServer.IntegrationTests; /// /// Names longer than the 400-character Tag.Name / Event.SourceName columns are refused, never /// truncated. Each test seeds a series under the 400-character PREFIX of the long name: a sized /// NVarChar(400) parameter would silently cut the long name to that prefix and hit the wrong series. /// [Collection(SqlHistorianCollection.Name)] public sealed class LongNameTests(SqlHistorianFixture fx) { private static readonly CancellationToken Ct = CancellationToken.None; private static DateTime At(double s) => ReadTestDb.At(s); private const uint BadInvalidArgument = 0x80AB0000u; private const uint BadNoData = 0x809B0000u; private static readonly string Prefix = new('p', 400); private static readonly string Long = Prefix + "-x"; [Fact] public async Task The_writer_refuses_a_long_name_as_a_per_tag_failure_and_writes_nothing() { var db = await ReadTestDb.CreateAsync(fx); await db.SeedAsync(Prefix, (At(0), HistorianValue.FromDouble(1), ReadTestDb.Good)); var w = new SqlServerHistorianValueWriter(db.Conn, db.Boot, new SqlServerTagCatalog(db.Conn, db.Boot)); (await w.WriteLiveValuesAsync(Long, [new(At(5), HistorianValue.FromDouble(2), 0u)], Ct)).ShouldBeFalse(); (await db.ScalarAsync($"SELECT COUNT(*) FROM {db.Conn.Q("Sample")}")).ShouldBe(1); (await db.ScalarAsync($"SELECT COUNT(*) FROM {db.Conn.Q("Tag")}")).ShouldBe(1); } [Fact] public async Task The_updater_answers_BadInvalidArgument_for_every_item_of_a_long_name() { var db = await ReadTestDb.CreateAsync(fx); await db.SeedAsync(Prefix, (At(0), HistorianValue.FromDouble(1), ReadTestDb.Good)); var u = new SqlServerHistorianDataUpdater(db.Conn, new SqlServerTagCatalog(db.Conn, db.Boot)); (await u.UpdateDataAsync(Long, HistoryUpdateMode.Update, [new(At(0), HistorianValue.FromDouble(9), 0u), new(At(1), HistorianValue.FromDouble(9), 0u)], Ct)) .ShouldBe([BadInvalidArgument, BadInvalidArgument]); (await u.DeleteRawAsync(Long, At(-1), At(10), Ct)).ShouldBe(BadInvalidArgument); (await u.DeleteAtTimeAsync(Long, [At(0)], Ct)).ShouldBe([BadInvalidArgument]); // The prefix series is untouched: not replaced, not deleted. var raw = (await db.Source().ReadRawAsync(Prefix, At(-1), At(10), 0, Ct)).Samples; raw.Select(s => s.Value).ShouldBe([(object)1d]); } [Fact] public async Task Reads_of_a_long_name_are_not_found_rather_than_the_prefix_series() { var db = await ReadTestDb.CreateAsync(fx); await db.SeedAsync(Prefix, (At(0), HistorianValue.FromDouble(1), ReadTestDb.Good)); var src = db.Source(); (await src.ReadRawAsync(Long, At(-1), At(10), 0, Ct)).Samples.ShouldBeEmpty(); (await src.ReadAtTimeAsync(Long, [At(0)], Ct)).Samples.Single().StatusCode.ShouldBe(BadNoData); (await src.ReadProcessedAsync(Long, At(-1), At(10), TimeSpan.Zero, HistoryAggregateType.Count, Ct)) .Samples.Single().StatusCode.ShouldBe(HistorianInfoBits.WithCalculated(BadNoData)); src.GetHealthSnapshot().TotalFailures.ShouldBe(0); } [Fact] public async Task An_event_read_filtered_by_a_long_source_matches_nothing() { var db = await ReadTestDb.CreateAsync(fx); await using (var c = await db.Conn.OpenAsync(Ct)) await using (var cmd = db.Conn.CreateCommand(c, $"INSERT {db.Conn.Q("Event")} (EventId, SourceName, AlarmId, AlarmName, AlarmTypeName, Severity, EventKind, Message, " + "UserName, Comment, EventTimeUtc, ReceivedTimeUtc) VALUES ('e1', @src, 'a', 'n', 'T', 500, 'Activated', NULL, NULL, NULL, @t, @t)")) { cmd.Parameters.AddWithValue("@src", Prefix); SqlHistorianConnection.AddDateTime2(cmd, "@t", At(0)); await cmd.ExecuteNonQueryAsync(Ct); } var src = db.Source(); (await src.ReadEventsAsync(Prefix, At(-1), At(10), 0, Ct)).Events.Count.ShouldBe(1); // control (await src.ReadEventsAsync(Long, At(-1), At(10), 0, Ct)).Events.ShouldBeEmpty(); } }