Files
mxaccessgw/clients/python/tests/test_review_findings_037_038.py
T
Joseph Doherty 6c853b43af fix(clients): resolve 2026-06-18 array-write review findings
- Client.Dotnet-030: add advise-supervisory to IsKnownGatewayCommand (was dead/unreachable, exit 2)
- Client.Go-035/036/037: usage+README list advise-supervisory; add session-id guard test; fix write2 README wording
- Client.Python-037/038: drop regressed 'scaffold' from pyproject; add advise-supervisory CLI tests
- Client.Rust-039/040: document write_array_elements/advise-supervisory in design doc; pin outer MxValue data_type==0
- Client.Java-049/050/051: sync CLIENT_VERSION to 0.1.2; add advise-supervisory test; guard negative uint32 inputs (pending windev gradle verification)

Client READMEs also updated for Server-057 add-family normalization wording.
.NET/Go/Python/Rust verified green locally; Java pending windev.
2026-06-18 10:58:33 -04:00

164 lines
5.4 KiB
Python

"""Regression tests for Client.Python-037 and Client.Python-038.
Client.Python-037: ``pyproject.toml`` description must not contain "scaffold".
Client.Python-038: ``advise-supervisory`` CLI subcommand must have coverage
(registration smoke test + happy-path command-shape test).
Tests are TDD-first — written before the fix and expected to pass once the
source change lands.
"""
from __future__ import annotations
import json
from pathlib import Path
from typing import Any
from click.testing import CliRunner
from zb_mom_ww_mxgateway import ClientOptions, GatewayClient
from zb_mom_ww_mxgateway.generated import mxaccess_gateway_pb2 as pb
from zb_mom_ww_mxgateway_cli.commands import main
# ---------------------------------------------------------------------------
# Client.Python-037 — pyproject.toml description must not contain "scaffold".
# ---------------------------------------------------------------------------
def test_pyproject_description_does_not_contain_scaffold() -> None:
"""The ``description`` field in ``pyproject.toml`` must not include the
word "scaffold" — a regression of Client.Python-001 that re-entered the
file at the package-rename commit.
"""
pyproject = (
Path(__file__).resolve().parent.parent / "pyproject.toml"
).read_text(encoding="utf-8")
# Find the description line and assert "scaffold" is absent.
for line in pyproject.splitlines():
stripped = line.strip()
if stripped.startswith("description"):
assert "scaffold" not in stripped.lower(), (
f"pyproject.toml description must not contain 'scaffold': {stripped!r}"
)
return
raise AssertionError("pyproject.toml has no 'description' line")
# ---------------------------------------------------------------------------
# Client.Python-038 — advise-supervisory must be registered + have a happy path.
# ---------------------------------------------------------------------------
def test_advise_supervisory_is_registered() -> None:
"""``advise-supervisory`` must be a registered subcommand of ``main``.
A ``--help`` invocation must exit 0 and the help text must include the
required options (--server-handle and --item-handle).
"""
runner = CliRunner()
result = runner.invoke(main, ["advise-supervisory", "--help"])
assert result.exit_code == 0, result.output
assert "--server-handle" in result.output
assert "--item-handle" in result.output
# --------------- fake-stub infrastructure (mirrors test_review_findings_022_to_026) ----
class _FakeUnary:
def __init__(self, replies: list[Any]) -> None:
self.replies = replies
self.requests: list[Any] = []
self.metadata: tuple[tuple[str, str], ...] | None = None
async def __call__(self, request: Any, *, metadata: tuple[tuple[str, str], ...]) -> Any:
self.requests.append(request)
self.metadata = metadata
return self.replies.pop(0)
class _FakeStub:
def __init__(self) -> None:
self.open_session = _FakeUnary(
[
pb.OpenSessionReply(
session_id="session-1",
protocol_status=pb.ProtocolStatus(code=pb.PROTOCOL_STATUS_CODE_OK),
),
],
)
self.invoke = _FakeUnary([])
self.OpenSession = self.open_session
self.Invoke = self.invoke
def set_invoke_replies(self, replies: list[Any]) -> None:
self.invoke.replies = replies
def _install_fake_connect(monkeypatch: Any, stub: _FakeStub) -> None:
"""Patch ``GatewayClient.connect`` so the CLI uses the supplied fake stub."""
real_connect = GatewayClient.connect
@classmethod # type: ignore[misc]
async def _spy_connect(cls: Any, options: ClientOptions, **kwargs: Any) -> GatewayClient:
return await real_connect(options, stub=stub)
monkeypatch.setattr(GatewayClient, "connect", _spy_connect)
def test_cli_advise_supervisory_happy_path(monkeypatch: Any) -> None:
"""``advise-supervisory`` must forward server_handle and item_handle in an
``MX_COMMAND_KIND_ADVISE_SUPERVISORY`` ``MxCommand``.
Pattern mirrors ``test_cli_acknowledge_alarm_happy_path`` in
``test_review_findings_022_to_026.py``.
"""
stub = _FakeStub()
stub.set_invoke_replies(
[
pb.MxCommandReply(
session_id="session-1",
kind=pb.MX_COMMAND_KIND_ADVISE_SUPERVISORY,
protocol_status=pb.ProtocolStatus(code=pb.PROTOCOL_STATUS_CODE_OK),
),
],
)
_install_fake_connect(monkeypatch, stub)
runner = CliRunner()
result = runner.invoke(
main,
[
"advise-supervisory",
"--endpoint",
"localhost:5000",
"--plaintext",
"--session-id",
"session-1",
"--server-handle",
"7",
"--item-handle",
"42",
"--json",
],
)
assert result.exit_code == 0, result.output
payload = json.loads(result.output)
assert payload["ok"] is True
# Verify the MxCommand shape forwarded to the gateway.
assert len(stub.invoke.requests) == 1
cmd = stub.invoke.requests[0].command
assert cmd.kind == pb.MX_COMMAND_KIND_ADVISE_SUPERVISORY
assert cmd.advise_supervisory.server_handle == 7
assert cmd.advise_supervisory.item_handle == 42