feat(historian): SQL Server backend skeleton - schema DDL, locked bootstrapper, options, connection/mapper/tag-catalog helpers, status consts, env-gated fixture

Claude-Session: https://claude.ai/code/session_01Ef2ozxS7QPanGT8uoATbTe
This commit is contained in:
Joseph Doherty
2026-10-09 12:52:15 -04:00
parent ff7c6c531a
commit 17962d4233
19 changed files with 1187 additions and 0 deletions
@@ -0,0 +1,46 @@
using Shouldly;
using Xunit;
namespace ZB.MOM.WW.OtOpcUa.Driver.Historian.SqlServer.Tests;
public sealed class SqlServerHistorianOptionsTests
{
private static SqlServerHistorianOptions Valid() => new() { ConnectionString = "Server=x" };
[Fact]
public void Defaults_with_a_connection_string_are_valid() => Valid().Validate().ShouldBeEmpty();
[Fact]
public void Missing_connection_string_is_rejected() =>
new SqlServerHistorianOptions().Validate().ShouldContain(e => e.Contains("ConnectionString"));
[Theory]
[InlineData("hist]; DROP TABLE x;--")]
[InlineData("1abc")]
[InlineData("a b")]
[InlineData("")]
[InlineData("a.b")]
public void Invalid_schema_names_are_rejected_before_any_sql_runs(string schema)
{
var o = Valid();
o.Schema = schema;
o.Validate().ShouldContain(e => e.Contains("Schema"));
Should.Throw<ArgumentException>(() => new SqlHistorianConnection(o));
}
[Fact]
public void Non_positive_timeout_is_rejected()
{
var o = Valid();
o.CommandTimeout = TimeSpan.Zero;
o.Validate().ShouldContain(e => e.Contains("CommandTimeout"));
}
[Fact]
public void Q_schema_qualifies_and_brackets()
{
var o = Valid();
o.Schema = "my_s";
new SqlHistorianConnection(o).Q("Tag").ShouldBe("[my_s].[Tag]");
}
}