Migrate historian from SQL to aahClientManaged SDK and resolve all OPC UA Part 11 gaps

Replace direct SQL queries against Historian Runtime database with the Wonderware
Historian managed SDK (ArchestrA.HistorianAccess). Add HistoryServerCapabilities node,
AggregateFunctions folder, continuation points, ReadAtTime interpolation, ReturnBounds,
ReadModified rejection, HistoricalDataConfiguration per node, historical event access,
and client-side StandardDeviation aggregate support. Remove screenshot tests.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
Joseph Doherty
2026-04-06 16:38:00 -04:00
parent 5c89a44255
commit 41f0e9ec4c
35 changed files with 1858 additions and 536 deletions

View File

@@ -28,6 +28,7 @@ namespace ZB.MOM.WW.LmxOpcUa.Tests.Configuration
configuration.GetSection("GalaxyRepository").Bind(config.GalaxyRepository);
configuration.GetSection("Dashboard").Bind(config.Dashboard);
configuration.GetSection("Security").Bind(config.Security);
configuration.GetSection("Historian").Bind(config.Historian);
return config;
}
@@ -315,5 +316,75 @@ namespace ZB.MOM.WW.LmxOpcUa.Tests.Configuration
configuration.GetSection("OpcUa").Bind(config);
config.ApplicationUri.ShouldBe("urn:test:app");
}
[Fact]
public void Historian_Section_BindsFromJson()
{
var config = LoadFromJson();
config.Historian.Enabled.ShouldBe(false);
config.Historian.ServerName.ShouldBe("localhost");
config.Historian.IntegratedSecurity.ShouldBe(true);
config.Historian.Port.ShouldBe(32568);
config.Historian.CommandTimeoutSeconds.ShouldBe(30);
config.Historian.MaxValuesPerRead.ShouldBe(10000);
}
[Fact]
public void Historian_Section_BindsCustomValues()
{
var configuration = new ConfigurationBuilder()
.AddInMemoryCollection(new[]
{
new KeyValuePair<string, string>("Historian:Enabled", "true"),
new KeyValuePair<string, string>("Historian:ServerName", "historian-server"),
new KeyValuePair<string, string>("Historian:IntegratedSecurity", "false"),
new KeyValuePair<string, string>("Historian:UserName", "testuser"),
new KeyValuePair<string, string>("Historian:Password", "testpass"),
new KeyValuePair<string, string>("Historian:Port", "12345"),
new KeyValuePair<string, string>("Historian:CommandTimeoutSeconds", "60"),
new KeyValuePair<string, string>("Historian:MaxValuesPerRead", "5000")
})
.Build();
var config = new HistorianConfiguration();
configuration.GetSection("Historian").Bind(config);
config.Enabled.ShouldBe(true);
config.ServerName.ShouldBe("historian-server");
config.IntegratedSecurity.ShouldBe(false);
config.UserName.ShouldBe("testuser");
config.Password.ShouldBe("testpass");
config.Port.ShouldBe(12345);
config.CommandTimeoutSeconds.ShouldBe(60);
config.MaxValuesPerRead.ShouldBe(5000);
}
[Fact]
public void Validator_HistorianEnabled_EmptyServerName_ReturnsFalse()
{
var config = new AppConfiguration();
config.Historian.Enabled = true;
config.Historian.ServerName = "";
ConfigurationValidator.ValidateAndLog(config).ShouldBe(false);
}
[Fact]
public void Validator_HistorianEnabled_InvalidPort_ReturnsFalse()
{
var config = new AppConfiguration();
config.Historian.Enabled = true;
config.Historian.Port = 0;
ConfigurationValidator.ValidateAndLog(config).ShouldBe(false);
}
[Fact]
public void Validator_HistorianEnabled_NoIntegratedSecurity_EmptyUserName_ReturnsFalse()
{
var config = new AppConfiguration();
config.Historian.Enabled = true;
config.Historian.IntegratedSecurity = false;
config.Historian.UserName = "";
ConfigurationValidator.ValidateAndLog(config).ShouldBe(false);
}
}
}