Files
lmxopcua/tests/ZB.MOM.WW.OtOpcUa.Driver.FOCAS.Tests/FocasCapabilityMatrixTests.cs
Joseph Doherty 63099115bf Auto: focas-f2c — PMC F/G for 16i
Capability-matrix correctness fix: real 16i ladders use F (CNC->PMC) and
G (PMC->CNC) signal groups for handshakes, but PmcLetters(Sixteen_i) was
returning {X,Y,R,D} only. Widen to {X,Y,F,G,R,D}; M/C/E/A/K/T remain
0i-F / 30i-only. Updated the matching test row.

Closes #265
2026-04-25 19:49:25 -04:00

158 lines
6.9 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", true)] // #265: F/G handshakes are documented on 16i ladders
[InlineData(FocasCncSeries.Sixteen_i, "G", true)]
[InlineData(FocasCncSeries.Sixteen_i, "M", false)] // M/C/K/T still 0i-F / 30i-only
[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();
}
}