Files
lmxopcua/tests/ZB.MOM.WW.OtOpcUa.Driver.FOCAS.Tests/Series/PasswordUnlockTests.cs
2026-04-26 05:45:13 -04:00

62 lines
3.2 KiB
C#

using Shouldly;
using Xunit;
using ZB.MOM.WW.OtOpcUa.Core.Abstractions;
namespace ZB.MOM.WW.OtOpcUa.Driver.FOCAS.Tests.Series;
/// <summary>
/// Issue #271, plan PR F4-d — series-level (would-be integration) coverage of
/// <c>cnc_wrunlockparam</c>. Hardware-gated: the FOCAS driver has no public
/// simulator (task #222) so the live-controller cases require a real CNC with
/// parameter-protect on. The CI lane for this assembly runs the unit-test fakes
/// under <see cref="FocasUnlockTests"/>; this file is a scaffold that runs
/// against a simulator + matching <c>mock_set_password</c> admin endpoint when
/// <c>FOCAS_TRUST_WIRE=1</c>.
/// </summary>
/// <remarks>
/// Build-only today: the simulator gate (FOCAS_TRUST_WIRE) skips at runtime so
/// CI doesn't need the simulator binary. When the simulator's
/// <c>cnc_wrunlockparam</c> + <c>mock_set_password</c> endpoints land
/// (<c>docs/v2/implementation/focas-simulator-plan.md</c>) the gated test
/// becomes a real round-trip.
/// </remarks>
[Trait("Category", "Series")]
public sealed class PasswordUnlockTests
{
[Fact]
public void Single_unlock_retry_path_is_documented()
{
// Build-only scaffold — see FocasUnlockTests for the actual fake-backed
// assertion. The integration version of this test (gated on a FOCAS
// simulator with mock_set_password) will:
// 1. Configure the simulator with password "1234".
// 2. Spin up FocasDriver with FocasDeviceOptions.Password = "1234".
// 3. Issue a cnc_wrparam against PARAM:1815 — expect Good (unlock applied
// on connect).
// 4. Use the simulator's admin endpoint to flip the password to "5678"
// mid-session (forces EW_PASSWD on the next write).
// 5. Issue another write — expect EW_PASSWD on attempt 1, then BadUserAccessDenied
// surfaced because the new password doesn't match the cached one.
// 6. Reconfigure FocasDeviceOptions.Password = "5678" on a new instance,
// issue write — expect Good (unlock applied on first connect).
// For now this test merely asserts the type contract; the simulator is
// tracked under task #222 + the focas-simulator-plan.md document.
typeof(IFocasClient).GetMethod(nameof(IFocasClient.UnlockAsync))
.ShouldNotBeNull();
// Driver-side records the password redaction invariant.
var dev = new FocasDeviceOptions("focas://1.2.3.4:8193", Password: "1234");
dev.ToString().ShouldNotContain("1234");
}
[Fact(Skip = "Hardware-gated — requires the FOCAS simulator with cnc_wrunlockparam + mock_set_password endpoints (task #222 / focas-simulator-plan.md).")]
public Task Live_simulator_unlock_retry_round_trip()
{
// Body deliberately empty — the [Skip] attribute keeps this off the CI lane.
// When the simulator lands, this test materialises a FocasDriver pointed at
// the simulator + drives the EW_PASSWD -> unlock -> retry path through real
// wire calls. See <c>docs/v2/implementation/focas-simulator-plan.md</c>
// § "FOCAS password unlock" for the simulator-side endpoints.
return Task.CompletedTask;
}
}