Files
lmxopcua/tests/ZB.MOM.WW.OtOpcUa.Driver.AbCip.IntegrationTests/AbServerProfileTests.cs
Joseph Doherty 32dff7f1d6 ab_server integration fixture — per-family profiles + documented CI-fetch contract. Closes task #180 (AB CIP follow-up — ab_server CI fixture). Replaces the prior hardcoded single-family fixture with a parametric AbServerProfile abstraction covering ControlLogix / CompactLogix / Micro800 / GuardLogix. Prebuilt-Windows-binary fetch is documented as a CI YAML step rather than fabricated C#-side, because SHA-pinned binary distribution is a CI workflow concern (libplctag owns releases, we pin a version + verify hash) not a test-framework concern. New AbServerProfile record + KnownProfiles static class at tests/.../AbServerProfile.cs. Four profiles: ControlLogix (widest coverage — DINT/REAL/BOOL/SINT/STRING atomic + DINT[16] array so the driver's @tags Symbol-Object decoder + array-bound path both get end-to-end coverage), CompactLogix (atomic subset — driver-side ConnectionSize quirk from PR 10 still applies since ab_server doesn't enforce the narrower limit), Micro800 (ab_server has no dedicated --plc micro800 mode — falls back to controllogix while driver-side path enforces empty routing + unconnected-only per PR 11; real Micro800 coverage requires a 2080 lab rig), GuardLogix (ab_server has no safety subsystem — profile emulates the _S-suffixed naming contract the driver's safety-ViewOnly classification reads in PR 12; real safety-lock behavior requires a 1756-L8xS physical rig). Each profile composes --plc + --tag args via BuildCliArgs(port) — pure string formatter so the composition logic is unit-testable without launching the simulator. AbServerFixture gains a ctor overload taking AbServerProfile + port (defaults back to ControlLogix on parameterless ctor so existing test suites keep compiling). Fixture's InitializeAsync hands the profile's CLI args to ProcessStartInfo.Arguments. New AbServerTheoryAttribute mirrors AbServerFactAttribute but extends TheoryAttribute so a single test can MemberData over KnownProfiles.All + cover all four families. AbCipReadSmokeTests converted from single-fact to theory parametrized over KnownProfiles.All — one row per family reads TestDINT + asserts Good status + Healthy driver state. Fixture lifecycle is explicit try/finally rather than await using because IAsyncLifetime.DisposeAsync returns ValueTask + xUnit's concrete IAsyncDisposable shim depends on xunit version; explicit beats implicit here. Eight new unit tests in AbServerProfileTests.cs (runs without the simulator so CI green even when the binary is absent): BuildCliArgs composes port + plc + tag flags in the documented order; empty seed-tag list still emits port + plc; SeedTag.ToCliSpec handles both 2-segment scalar + 3-segment array; KnownProfiles.ForFamily returns expected --plc arg for every family (verifies Micro800 + GuardLogix both fall back to controllogix); KnownProfiles.All covers every AbCipPlcFamily enum value (regression guard — adding a new family without a profile fails this test); ControlLogix seeds every atomic type the driver supports; GuardLogix seeds at least one _S-suffixed safety tag. Integration tests still skip cleanly when ab_server isn't on PATH. 11/11 unit tests passing in this project (8 new + 3 prior). Full Admin solution builds 0 errors. docs/v2/test-data-sources.md gets a new "CI fixture" subsection under §2.Gotchas with the exact GitHub Actions YAML step — fetch the pinned libplctag release, SHA256-verify against a pinned hash recorded in the repo's CI lockfile (drift = fail closed), extract, append to PATH. The C# harness stays PATH-driven so dev-box installs (cmake + make from source) work identically to CI.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-19 23:57:24 -04:00

91 lines
3.2 KiB
C#

using Shouldly;
using Xunit;
using ZB.MOM.WW.OtOpcUa.Driver.AbCip;
namespace ZB.MOM.WW.OtOpcUa.Driver.AbCip.IntegrationTests;
/// <summary>
/// Pure-unit tests for the profile → CLI arg composition. Runs without <c>ab_server</c>
/// on PATH so CI without the binary still exercises these contracts + catches any
/// profile-definition drift (e.g. a typo in <c>--plc</c> mapping would silently make the
/// simulator boot with the wrong family).
/// </summary>
[Trait("Category", "Unit")]
public sealed class AbServerProfileTests
{
[Fact]
public void BuildCliArgs_Emits_Port_And_Plc_And_TagFlags()
{
var profile = new AbServerProfile(
Family: AbCipPlcFamily.ControlLogix,
AbServerPlcArg: "controllogix",
SeedTags: new AbServerSeedTag[]
{
new("A", "DINT"),
new("B", "REAL"),
},
Notes: "test");
profile.BuildCliArgs(44818).ShouldBe("--port 44818 --plc controllogix --tag A:DINT --tag B:REAL");
}
[Fact]
public void BuildCliArgs_NoSeedTags_Emits_Just_Port_And_Plc()
{
var profile = new AbServerProfile(
AbCipPlcFamily.ControlLogix, "controllogix", [], "empty");
profile.BuildCliArgs(5000).ShouldBe("--port 5000 --plc controllogix");
}
[Fact]
public void AbServerSeedTag_ArraySize_FormatsAsThirdSegment()
{
new AbServerSeedTag("TestArray", "DINT", ArraySize: 16)
.ToCliSpec().ShouldBe("TestArray:DINT:16");
}
[Fact]
public void AbServerSeedTag_NoArraySize_TwoSegments()
{
new AbServerSeedTag("TestScalar", "REAL")
.ToCliSpec().ShouldBe("TestScalar:REAL");
}
[Theory]
[InlineData(AbCipPlcFamily.ControlLogix, "controllogix")]
[InlineData(AbCipPlcFamily.CompactLogix, "compactlogix")]
[InlineData(AbCipPlcFamily.Micro800, "controllogix")] // falls back — ab_server lacks dedicated mode
[InlineData(AbCipPlcFamily.GuardLogix, "controllogix")] // falls back — ab_server lacks safety subsystem
public void KnownProfiles_ForFamily_Returns_Expected_AbServerPlcArg(AbCipPlcFamily family, string expected)
{
KnownProfiles.ForFamily(family).AbServerPlcArg.ShouldBe(expected);
}
[Fact]
public void KnownProfiles_All_Covers_Every_Family()
{
var covered = KnownProfiles.All.Select(p => p.Family).ToHashSet();
foreach (var family in Enum.GetValues<AbCipPlcFamily>())
covered.ShouldContain(family, $"Family {family} is missing a KnownProfiles entry.");
}
[Fact]
public void KnownProfiles_ControlLogix_Includes_AllAtomicTypes()
{
var tags = KnownProfiles.ControlLogix.SeedTags.Select(t => t.AbServerType).ToHashSet();
tags.ShouldContain("DINT");
tags.ShouldContain("REAL");
tags.ShouldContain("BOOL");
tags.ShouldContain("SINT");
tags.ShouldContain("STRING");
}
[Fact]
public void KnownProfiles_GuardLogix_SeedsSafetySuffixedTag()
{
KnownProfiles.GuardLogix.SeedTags
.ShouldContain(t => t.Name.EndsWith("_S"), "GuardLogix profile must seed at least one _S-suffixed tag for safety-classification coverage.");
}
}