c36224d956
- HardwareGateGuardTests: only StringWriteGateTests may name a write path in HardwareGate/; the allow-list is exactly the two Z-35162 (10.209.70.160) STRING tags; the opt-in, the (host, tag) check and the AB_DIFF_ENDPOINT refusal are present. Proven red by a WriteAsync mutation. - StringWriteGateTests: allow-list entries are (host, tag); any other endpoint is refused; skips while AB_DIFF_ENDPOINT is set, like IntegrationWriteGate. - AbCip.md #566: the close happened somewhere in the 16-60 s window; RST vs FIN not captured. Claude-Session: https://claude.ai/code/session_01Ef2ozxS7QPanGT8uoATbTe
71 lines
3.8 KiB
C#
71 lines
3.8 KiB
C#
using System.Runtime.CompilerServices;
|
|
using Shouldly;
|
|
using Xunit;
|
|
|
|
namespace ZB.MOM.WW.OtOpcUa.Driver.AbCip.IntegrationTests.HardwareGate;
|
|
|
|
/// <summary>
|
|
/// Offline guard for the hardware-gate folder — deliberately NOT env-gated, so it runs in every build. The folder sits
|
|
/// outside the read-only <c>Differential</c> guard because one test in it writes; this pins how far that goes:
|
|
/// only <see cref="StringWriteGateTests"/> may name a write path, its allow-list is exactly the two approved
|
|
/// (Z-35162, tag) pairs, and its explicit opt-in and differential-run refusal are present.
|
|
/// </summary>
|
|
public sealed class HardwareGateGuardTests
|
|
{
|
|
[Fact]
|
|
public void Only_the_string_write_gate_names_a_write_path()
|
|
{
|
|
// Built by concatenation so this file's own text never matches itself.
|
|
string[] forbidden =
|
|
[
|
|
"Write" + "Async", "Encode" + "Value", "WriteBit" + "InDInt", "Write" + "Tag", "Write" + "Request", "AbCip" + "Driver",
|
|
];
|
|
var sources = Directory.GetFiles(Path.GetDirectoryName(ThisFile())!, "*.cs");
|
|
sources.ShouldContain(f => Path.GetFileName(f) == nameof(StringWriteGateTests) + ".cs");
|
|
sources.ShouldContain(f => Path.GetFileName(f) == nameof(IdleConnectionEvidenceTests) + ".cs");
|
|
|
|
var hits = (from f in sources
|
|
where Path.GetFileName(f) != nameof(StringWriteGateTests) + ".cs"
|
|
let text = File.ReadAllText(f)
|
|
from token in forbidden
|
|
where text.Contains(token, StringComparison.Ordinal)
|
|
select $"{Path.GetFileName(f)} names {token}").ToList();
|
|
hits.ShouldBeEmpty();
|
|
|
|
// Positive control: the scan does see the one file that is allowed to write.
|
|
var writer = File.ReadAllText(sources.Single(f => Path.GetFileName(f) == nameof(StringWriteGateTests) + ".cs"));
|
|
writer.ShouldContain(forbidden[0]);
|
|
}
|
|
|
|
[Fact]
|
|
public void The_write_allow_list_is_exactly_the_two_approved_pairs_on_Z35162()
|
|
{
|
|
StringWriteGateTests.Approved.ShouldBe(
|
|
[
|
|
("10.209.70.160", "Program:ES03_TemperatureControl._StrTmp"),
|
|
("10.209.70.160", "Program:ES03_TemperatureControl._Prefix"),
|
|
]);
|
|
|
|
StringWriteGateTests.IsApproved("ab://10.209.70.160/1,0", "Program:ES03_TemperatureControl._StrTmp").ShouldBeTrue();
|
|
StringWriteGateTests.IsApproved("ab://10.209.70.160:44818/1,0", "Program:ES03_TemperatureControl._Prefix").ShouldBeTrue();
|
|
// Same tag on any other controller, another tag on Z-35162, or a malformed gateway: refused.
|
|
StringWriteGateTests.IsApproved("ab://10.209.70.161/1,0", "Program:ES03_TemperatureControl._StrTmp").ShouldBeFalse();
|
|
StringWriteGateTests.IsApproved("ab://10.100.0.35/1,0", "Program:ES03_TemperatureControl._StrTmp").ShouldBeFalse();
|
|
StringWriteGateTests.IsApproved("ab://10.209.70.160/1,0", "test2").ShouldBeFalse();
|
|
StringWriteGateTests.IsApproved("ab://10.209.70.160/1,0", "Program:UN01_Press._Prefix").ShouldBeFalse();
|
|
StringWriteGateTests.IsApproved("10.209.70.160", "Program:ES03_TemperatureControl._StrTmp").ShouldBeFalse();
|
|
}
|
|
|
|
[Fact]
|
|
public void The_string_write_gate_keeps_its_opt_in_and_refuses_during_a_differential_run()
|
|
{
|
|
var text = File.ReadAllText(Path.Combine(Path.GetDirectoryName(ThisFile())!, nameof(StringWriteGateTests) + ".cs"));
|
|
text.ShouldContain("""GetEnvironmentVariable("AB_HW_WRITE_APPROVED") != "1") Assert.Skip(""");
|
|
text.ShouldContain("GetEnvironmentVariable(IntegrationWriteGate.DifferentialEndpointVar) is { Length: > 0 })");
|
|
text.ShouldContain("if (!IsApproved(gateway, tagPath))");
|
|
IntegrationWriteGate.DifferentialEndpointVar.ShouldBe("AB_DIFF_ENDPOINT");
|
|
}
|
|
|
|
private static string ThisFile([CallerFilePath] string path = "") => path;
|
|
}
|