using Microsoft.Data.SqlClient; using Shouldly; using Xunit; using ZB.MOM.WW.OtOpcUa.Core.Abstractions; namespace ZB.MOM.WW.OtOpcUa.Driver.Historian.SqlServer.IntegrationTests; [Collection(SqlHistorianCollection.Name)] public sealed class ProvisioningTests(SqlHistorianFixture fx) { private (SqlServerTagProvisioner P, string S) Make(string? schema = null) { var s = schema ?? SqlHistorianFixture.UniqueSchema(); var o = new SqlServerHistorianOptions { ConnectionString = fx.ConnectionString, Schema = s }; var c = new SqlHistorianConnection(o); return (new SqlServerTagProvisioner(c, new SqlServerSchemaBootstrapper(c, o)), s); } private async Task<(byte Type, string? Eu, string? Desc)> TagAsync(string s, string name) { await using var c = new SqlConnection(fx.ConnectionString); await c.OpenAsync(); await using var cmd = c.CreateCommand(); cmd.CommandText = $"SELECT DataType, EngineeringUnit, Description FROM [{s}].Tag WHERE Name=@n"; cmd.Parameters.AddWithValue("@n", name); await using var r = await cmd.ExecuteReaderAsync(); (await r.ReadAsync()).ShouldBeTrue(); return (r.GetByte(0), r.IsDBNull(1) ? null : r.GetString(1), r.IsDBNull(2) ? null : r.GetString(2)); } [Fact] public async Task Upsert_creates_then_changes_DataType_on_rerun_and_every_type_is_accepted() { fx.SkipIfUnavailable(); var (p, s) = Make(); var r1 = await p.EnsureTagsAsync([new("a", DriverDataType.Boolean, "degC", "desc"), new("s", DriverDataType.String, null, null)], CancellationToken.None); r1.ShouldBe(new HistorianProvisionResult(2, 2, 0, 0)); (await TagAsync(s, "a")).ShouldBe(((byte)DriverDataType.Boolean, "degC", "desc")); var r2 = await p.EnsureTagsAsync([new("a", DriverDataType.Float64, null, null)], CancellationToken.None); r2.ShouldBe(new HistorianProvisionResult(1, 1, 0, 0)); (await TagAsync(s, "a")).ShouldBe(((byte)DriverDataType.Float64, "degC", "desc")); // null keeps stored metadata } [Fact] public async Task Duplicate_names_and_bad_names_are_tallied_not_thrown() { fx.SkipIfUnavailable(); var (p, _) = Make(); var r = await p.EnsureTagsAsync([ new("d", DriverDataType.Int32, null, null), new("d", DriverDataType.Int64, null, null), new("", DriverDataType.Int32, null, null), new(new string('x', 401), DriverDataType.Int32, null, null)], CancellationToken.None); r.ShouldBe(new HistorianProvisionResult(4, 2, 0, 2)); } [Fact] public async Task More_tags_than_one_chunk_all_land() { fx.SkipIfUnavailable(); var (p, s) = Make(); var reqs = Enumerable.Range(0, 1000).Select(i => new HistorianTagProvisionRequest($"bulk{i}", DriverDataType.Float32, null, null)).ToList(); (await p.EnsureTagsAsync(reqs, CancellationToken.None)).ShouldBe(new HistorianProvisionResult(1000, 1000, 0, 0)); } [Fact] public async Task Two_concurrent_provisioners_of_the_same_new_tags_both_report_Ensured_with_no_Failed() { fx.SkipIfUnavailable(); var s = SqlHistorianFixture.UniqueSchema(); var (p0, _) = Make(s); await p0.EnsureTagsAsync([new("warm", DriverDataType.Int32, null, null)], CancellationToken.None); // bootstrap once for (var round = 0; round < 10; round++) { var reqs = Enumerable.Range(0, 60).Select(i => new HistorianTagProvisionRequest($"r{round}.t{i}", DriverDataType.Float64, "u", null)).ToList(); var rs = await Task.WhenAll(Enumerable.Range(0, 4).Select(_ => Task.Run(() => Make(s).P.EnsureTagsAsync(reqs, CancellationToken.None)))); foreach (var r in rs) r.ShouldBe(new HistorianProvisionResult(60, 60, 0, 0)); } await using var c = new SqlConnection(fx.ConnectionString); await c.OpenAsync(); await using var cmd = c.CreateCommand(); cmd.CommandText = $"SELECT COUNT(*) FROM [{s}].Tag"; Convert.ToInt32(await cmd.ExecuteScalarAsync()).ShouldBe(601); } [Fact] public async Task A_connection_failure_is_a_Failed_tally_not_an_exception() { fx.SkipIfUnavailable(); var o = new SqlServerHistorianOptions { ConnectionString = fx.ConnectionString + ";Connect Timeout=1", Schema = SqlHistorianFixture.UniqueSchema(), AutoCreateSchema = false }; var c = new SqlHistorianConnection(o); var p = new SqlServerTagProvisioner(c, new SqlServerSchemaBootstrapper(c, o)); (await p.EnsureTagsAsync([new("x", DriverDataType.Int32, null, null)], CancellationToken.None)).ShouldBe(new HistorianProvisionResult(1, 0, 0, 1)); } }