using Shouldly;
using Xunit;
using ZB.MOM.WW.OtOpcUa.Core.Hosting;
namespace ZB.MOM.WW.OtOpcUa.Driver.FOCAS.Tests;
///
/// Task #220 — covers the DriverConfig JSON contract that
/// parses when the bootstrap
/// pipeline (task #248) materialises FOCAS DriverInstance rows. Pure unit tests, no pipe
/// or CNC required.
///
[Trait("Category", "Unit")]
public sealed class FocasDriverFactoryExtensionsTests
{
[Fact]
public void Register_adds_FOCAS_entry_to_registry()
{
var registry = new DriverFactoryRegistry();
FocasDriverFactoryExtensions.Register(registry);
registry.TryGet("FOCAS").ShouldNotBeNull();
}
[Fact]
public void Register_is_case_insensitive_via_registry()
{
var registry = new DriverFactoryRegistry();
FocasDriverFactoryExtensions.Register(registry);
registry.TryGet("focas").ShouldNotBeNull();
registry.TryGet("Focas").ShouldNotBeNull();
}
[Fact]
public void CreateInstance_with_ipc_backend_and_valid_config_returns_FocasDriver()
{
const string json = """
{
"Backend": "ipc",
"PipeName": "OtOpcUaFocasHost",
"SharedSecret": "secret-for-test",
"ConnectTimeoutMs": 5000,
"Series": "Thirty_i",
"TimeoutMs": 3000,
"Devices": [
{ "HostAddress": "focas://10.0.0.5:8193", "DeviceName": "Lathe1" }
],
"Tags": [
{ "Name": "Override", "DeviceHostAddress": "focas://10.0.0.5:8193",
"Address": "R100", "DataType": "Int32", "Writable": true }
]
}
""";
var driver = FocasDriverFactoryExtensions.CreateInstance("focas-0", json);
driver.ShouldNotBeNull();
driver.DriverInstanceId.ShouldBe("focas-0");
driver.DriverType.ShouldBe("FOCAS");
}
[Fact]
public void CreateInstance_defaults_Backend_to_ipc_when_unspecified()
{
// No "Backend" key → defaults to ipc → requires PipeName + SharedSecret.
const string json = """
{ "PipeName": "p", "SharedSecret": "s" }
""";
var driver = FocasDriverFactoryExtensions.CreateInstance("focas-default", json);
driver.DriverType.ShouldBe("FOCAS");
}
[Fact]
public void CreateInstance_ipc_backend_missing_PipeName_throws()
{
const string json = """{ "Backend": "ipc", "SharedSecret": "s" }""";
Should.Throw(
() => FocasDriverFactoryExtensions.CreateInstance("focas-missing-pipe", json))
.Message.ShouldContain("PipeName");
}
[Fact]
public void CreateInstance_ipc_backend_missing_SharedSecret_throws()
{
const string json = """{ "Backend": "ipc", "PipeName": "p" }""";
Should.Throw(
() => FocasDriverFactoryExtensions.CreateInstance("focas-missing-secret", json))
.Message.ShouldContain("SharedSecret");
}
[Fact]
public void CreateInstance_fwlib_backend_does_not_require_pipe_fields()
{
// Direct in-process Fwlib32 path. No pipe config needed; driver connects the DLL
// natively on first use.
const string json = """{ "Backend": "fwlib" }""";
var driver = FocasDriverFactoryExtensions.CreateInstance("focas-fwlib", json);
driver.DriverInstanceId.ShouldBe("focas-fwlib");
}
[Fact]
public void CreateInstance_unimplemented_backend_yields_driver_that_fails_fast_on_use()
{
// Useful for staging DriverInstance rows in the config DB before the Host is
// actually deployed — the server boots but reads/writes surface clear errors.
const string json = """{ "Backend": "unimplemented" }""";
var driver = FocasDriverFactoryExtensions.CreateInstance("focas-unimpl", json);
driver.DriverInstanceId.ShouldBe("focas-unimpl");
}
[Fact]
public void CreateInstance_unknown_backend_throws_with_expected_list()
{
const string json = """{ "Backend": "gibberish", "PipeName": "p", "SharedSecret": "s" }""";
Should.Throw(
() => FocasDriverFactoryExtensions.CreateInstance("focas-bad-backend", json))
.Message.ShouldContain("gibberish");
}
[Fact]
public void CreateInstance_rejects_unknown_Series()
{
const string json = """
{ "Backend": "fwlib", "Series": "NotARealSeries" }
""";
Should.Throw(
() => FocasDriverFactoryExtensions.CreateInstance("focas-bad-series", json))
.Message.ShouldContain("NotARealSeries");
}
[Fact]
public void CreateInstance_rejects_tag_with_missing_DataType()
{
const string json = """
{
"Backend": "fwlib",
"Devices": [{ "HostAddress": "focas://1.1.1.1:8193" }],
"Tags": [{ "Name": "Broken", "DeviceHostAddress": "focas://1.1.1.1:8193", "Address": "R1" }]
}
""";
Should.Throw(
() => FocasDriverFactoryExtensions.CreateInstance("focas-bad-tag", json))
.Message.ShouldContain("DataType");
}
[Fact]
public void CreateInstance_null_or_whitespace_args_rejected()
{
Should.Throw(
() => FocasDriverFactoryExtensions.CreateInstance("", "{}"));
Should.Throw(
() => FocasDriverFactoryExtensions.CreateInstance("id", ""));
}
[Fact]
public void Register_twice_throws()
{
var registry = new DriverFactoryRegistry();
FocasDriverFactoryExtensions.Register(registry);
Should.Throw(
() => FocasDriverFactoryExtensions.Register(registry));
}
}