Files
lmxopcua/tests/Drivers/ZB.MOM.WW.OtOpcUa.Driver.FOCAS.Tests/FocasCapabilityMatrixTests.cs
Joseph Doherty a25593a9c6 chore: organize solution into module folders (Core/Server/Drivers/Client/Tooling)
Group all 69 projects into category subfolders under src/ and tests/ so the
Rider Solution Explorer mirrors the module structure. Folders: Core, Server,
Drivers (with a nested Driver CLIs subfolder), Client, Tooling.

- Move every project folder on disk with git mv (history preserved as renames).
- Recompute relative paths in 57 .csproj files: cross-category ProjectReferences,
  the lib/ HintPath+None refs in Driver.Historian.Wonderware, and the external
  mxaccessgw refs in Driver.Galaxy and its test project.
- Rebuild ZB.MOM.WW.OtOpcUa.slnx with nested solution folders.
- Re-prefix project paths in functional scripts (e2e, compliance, smoke SQL,
  integration, install).

Build green (0 errors); unit tests pass. Docs left for a separate pass.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-17 01:55:28 -04:00

157 lines
6.8 KiB
C#

using Shouldly;
using Xunit;
using ZB.MOM.WW.OtOpcUa.Driver.FOCAS;
namespace ZB.MOM.WW.OtOpcUa.Driver.FOCAS.Tests;
/// <summary>
/// Version-matrix coverage for <see cref="FocasCapabilityMatrix"/>. Encodes the
/// documented Fanuc FOCAS Developer Kit support boundaries per CNC series so a
/// config-time change that widens or narrows a range without updating
/// <c>docs/v2/focas-version-matrix.md</c> fails a test. Every assertion cites the
/// specific matrix row it reflects.
/// </summary>
[Trait("Category", "Unit")]
public sealed class FocasCapabilityMatrixTests
{
// ---- Macro ranges ----
[Theory]
[InlineData(FocasCncSeries.Sixteen_i, 999, true)]
[InlineData(FocasCncSeries.Sixteen_i, 1000, false)] // above legacy ceiling
[InlineData(FocasCncSeries.Zero_i_D, 999, true)]
[InlineData(FocasCncSeries.Zero_i_D, 9999, false)] // 0i-D is still legacy-ceiling
[InlineData(FocasCncSeries.Zero_i_F, 9999, true)] // widened on 0i-F
[InlineData(FocasCncSeries.Zero_i_F, 10000, false)]
[InlineData(FocasCncSeries.Thirty_i, 99999, true)] // highest-end
[InlineData(FocasCncSeries.Thirty_i, 100000, false)]
[InlineData(FocasCncSeries.PowerMotion_i, 999, true)]
[InlineData(FocasCncSeries.PowerMotion_i, 1000, false)] // atypical coverage
public void Macro_range_matches_series(FocasCncSeries series, int number, bool accepted)
{
var address = new FocasAddress(FocasAreaKind.Macro, null, number, null);
var result = FocasCapabilityMatrix.Validate(series, address);
(result is null).ShouldBe(accepted,
$"Macro #{number} on {series}: expected {(accepted ? "accept" : "reject")}, got {(result ?? "accept")}");
}
// ---- Parameter ranges ----
[Theory]
[InlineData(FocasCncSeries.Sixteen_i, 9999, true)]
[InlineData(FocasCncSeries.Sixteen_i, 10000, false)] // 16i capped at 9999
[InlineData(FocasCncSeries.Zero_i_F, 14999, true)]
[InlineData(FocasCncSeries.Zero_i_F, 15000, false)]
[InlineData(FocasCncSeries.Thirty_i, 29999, true)]
[InlineData(FocasCncSeries.Thirty_i, 30000, false)]
public void Parameter_range_matches_series(FocasCncSeries series, int number, bool accepted)
{
var address = new FocasAddress(FocasAreaKind.Parameter, null, number, null);
var result = FocasCapabilityMatrix.Validate(series, address);
(result is null).ShouldBe(accepted);
}
// ---- PMC letters ----
[Theory]
[InlineData(FocasCncSeries.Sixteen_i, "X", true)]
[InlineData(FocasCncSeries.Sixteen_i, "Y", true)]
[InlineData(FocasCncSeries.Sixteen_i, "R", true)]
[InlineData(FocasCncSeries.Sixteen_i, "F", false)] // 16i has no F/G signal groups
[InlineData(FocasCncSeries.Sixteen_i, "G", false)]
[InlineData(FocasCncSeries.Sixteen_i, "K", false)]
[InlineData(FocasCncSeries.Zero_i_D, "E", true)] // widened since 0i-D
[InlineData(FocasCncSeries.Zero_i_D, "F", false)] // still no F on 0i-D
[InlineData(FocasCncSeries.Zero_i_F, "F", true)] // F/G added on 0i-F
[InlineData(FocasCncSeries.Zero_i_F, "K", false)] // K/T still 30i-only
[InlineData(FocasCncSeries.Thirty_i, "K", true)]
[InlineData(FocasCncSeries.Thirty_i, "T", true)]
[InlineData(FocasCncSeries.Thirty_i, "Q", false)] // unsupported even on 30i
public void Pmc_letter_matches_series(FocasCncSeries series, string letter, bool accepted)
{
var address = new FocasAddress(FocasAreaKind.Pmc, letter, 0, null);
var result = FocasCapabilityMatrix.Validate(series, address);
(result is null).ShouldBe(accepted,
$"PMC letter '{letter}' on {series}: expected {(accepted ? "accept" : "reject")}, got {(result ?? "accept")}");
}
// ---- PMC number ceiling ----
[Theory]
[InlineData(FocasCncSeries.Sixteen_i, "R", 999, true)]
[InlineData(FocasCncSeries.Sixteen_i, "R", 1000, false)]
[InlineData(FocasCncSeries.Zero_i_D, "R", 1999, true)]
[InlineData(FocasCncSeries.Zero_i_D, "R", 2000, false)]
[InlineData(FocasCncSeries.Zero_i_F, "R", 9999, true)]
[InlineData(FocasCncSeries.Zero_i_F, "R", 10000, false)]
[InlineData(FocasCncSeries.Thirty_i, "R", 59999, true)]
[InlineData(FocasCncSeries.Thirty_i, "R", 60000, false)]
public void Pmc_number_ceiling_matches_series(FocasCncSeries series, string letter, int number, bool accepted)
{
var address = new FocasAddress(FocasAreaKind.Pmc, letter, number, null);
var result = FocasCapabilityMatrix.Validate(series, address);
(result is null).ShouldBe(accepted);
}
// ---- Unknown series is permissive ----
[Theory]
[InlineData("Z", 999_999)] // absurd PMC address
[InlineData("Q", 0)] // non-existent letter
public void Unknown_series_accepts_any_PMC(string letter, int number)
{
var address = new FocasAddress(FocasAreaKind.Pmc, letter, number, null);
FocasCapabilityMatrix.Validate(FocasCncSeries.Unknown, address).ShouldBeNull();
}
[Fact]
public void Unknown_series_accepts_any_macro_number()
{
var address = new FocasAddress(FocasAreaKind.Macro, null, 999_999, null);
FocasCapabilityMatrix.Validate(FocasCncSeries.Unknown, address).ShouldBeNull();
}
[Fact]
public void Unknown_series_accepts_any_parameter_number()
{
var address = new FocasAddress(FocasAreaKind.Parameter, null, 999_999, null);
FocasCapabilityMatrix.Validate(FocasCncSeries.Unknown, address).ShouldBeNull();
}
// ---- Reason messages include enough context to diagnose ----
[Fact]
public void Rejection_message_names_series_and_limit()
{
var address = new FocasAddress(FocasAreaKind.Macro, null, 100_000, null);
var reason = FocasCapabilityMatrix.Validate(FocasCncSeries.Zero_i_F, address);
reason.ShouldNotBeNull();
reason.ShouldContain("100000");
reason.ShouldContain("Zero_i_F");
reason.ShouldContain("9999");
}
[Fact]
public void Pmc_rejection_lists_accepted_letters()
{
var address = new FocasAddress(FocasAreaKind.Pmc, "Q", 0, null);
var reason = FocasCapabilityMatrix.Validate(FocasCncSeries.Thirty_i, address);
reason.ShouldNotBeNull();
reason.ShouldContain("'Q'");
reason.ShouldContain("X"); // some accepted letter should appear
reason.ShouldContain("Y");
}
// ---- PMC address letter is case-insensitive ----
[Theory]
[InlineData("x")]
[InlineData("X")]
[InlineData("f")]
public void Pmc_letter_match_is_case_insensitive_on_30i(string letter)
{
var address = new FocasAddress(FocasAreaKind.Pmc, letter, 0, null);
FocasCapabilityMatrix.Validate(FocasCncSeries.Thirty_i, address).ShouldBeNull();
}
}