6b5fe6aa82
Server-054/055/056, Contracts-020/021/022, Tests-036/038/039, IntegrationTests-030/031/032 (+033 deferred to live rig), Client.Dotnet-026/028/029 (+027 won't-fix), Client.Go-030..034, Client.Python-032..036, Client.Rust-033..038. Key fix: SessionEventDistributor orphaned a subscriber that registered after the pump completed but before disposal (Server-056) -> register paths now complete late registrants under _lifecycleLock; regression test added. The racy dashboard-mirror gRPC test made deterministic (Tests-039). Verified green locally: gateway Tests targeted classes (GatewaySession, SessionEventDistributor, GatewayOptionsValidator, ProtobufContractRoundTrip, GatewaySessionDashboardMirror) + dotnet/go/python/rust client suites.
132 lines
4.7 KiB
Python
132 lines
4.7 KiB
Python
"""Regression tests for Client.Python-032..036.
|
|
|
|
Each test corresponds to a finding from the 2026-06-16 re-review. Tests are
|
|
TDD-first — they fail against the pre-fix source and pass against the fixed
|
|
source.
|
|
"""
|
|
|
|
from __future__ import annotations
|
|
|
|
import inspect
|
|
import re
|
|
from pathlib import Path
|
|
|
|
import click
|
|
import pytest
|
|
|
|
from zb_mom_ww_mxgateway_cli import commands as cli_commands
|
|
from zb_mom_ww_mxgateway_cli.commands import _parse_int_list, _parse_string_list
|
|
|
|
|
|
# ---------------------------------------------------------------------------
|
|
# Client.Python-032 — `_smoke` must not carry the dead `closed` guard variable.
|
|
# ---------------------------------------------------------------------------
|
|
|
|
|
|
def test_smoke_does_not_carry_dead_closed_guard() -> None:
|
|
"""`_smoke` must not reintroduce the dead `closed = False` / `if not closed`
|
|
guard removed by Client.Python-004. The variable is never reassigned, so the
|
|
guard misleads readers into expecting an early-close path that never exists.
|
|
"""
|
|
|
|
source = inspect.getsource(cli_commands._smoke)
|
|
assert "closed = False" not in source, (
|
|
"_smoke must not reintroduce the dead `closed = False` variable"
|
|
)
|
|
assert "if not closed:" not in source, (
|
|
"_smoke must not reintroduce the dead `if not closed:` guard"
|
|
)
|
|
|
|
|
|
# ---------------------------------------------------------------------------
|
|
# Client.Python-033 — `_parse_string_list` param_hint must reflect the caller.
|
|
# ---------------------------------------------------------------------------
|
|
|
|
|
|
def test_parse_string_list_default_param_hint_is_items() -> None:
|
|
with pytest.raises(click.BadParameter) as exc:
|
|
_parse_string_list("")
|
|
assert exc.value.param_hint == "--items"
|
|
|
|
|
|
def test_parse_string_list_accepts_caller_supplied_param_hint() -> None:
|
|
"""The write-bulk family passes `--values`, so an empty value must surface a
|
|
`--values` hint, not the irrelevant `--items` default.
|
|
"""
|
|
|
|
with pytest.raises(click.BadParameter) as exc:
|
|
_parse_string_list("", param_hint="--values")
|
|
assert exc.value.param_hint == "--values"
|
|
|
|
|
|
# ---------------------------------------------------------------------------
|
|
# Client.Python-034 — `_parse_int_list` must re-raise non-numeric tokens as
|
|
# click.BadParameter, not a raw ValueError traceback.
|
|
# ---------------------------------------------------------------------------
|
|
|
|
|
|
def test_parse_int_list_non_numeric_raises_bad_parameter() -> None:
|
|
with pytest.raises(click.BadParameter) as exc:
|
|
_parse_int_list("10,abc")
|
|
assert exc.value.param_hint == "--item-handles"
|
|
|
|
|
|
def test_parse_int_list_happy_path() -> None:
|
|
assert _parse_int_list("10, 20 ,30") == [10, 20, 30]
|
|
|
|
|
|
# ---------------------------------------------------------------------------
|
|
# Client.Python-035 — public browse types must be re-exported from the package
|
|
# root.
|
|
# ---------------------------------------------------------------------------
|
|
|
|
|
|
def test_browse_children_options_is_exported_from_package_root() -> None:
|
|
import zb_mom_ww_mxgateway as pkg
|
|
|
|
assert hasattr(pkg, "BrowseChildrenOptions")
|
|
assert "BrowseChildrenOptions" in pkg.__all__
|
|
|
|
|
|
def test_lazy_browse_node_is_exported_from_package_root() -> None:
|
|
import zb_mom_ww_mxgateway as pkg
|
|
|
|
assert hasattr(pkg, "LazyBrowseNode")
|
|
assert "LazyBrowseNode" in pkg.__all__
|
|
|
|
|
|
# ---------------------------------------------------------------------------
|
|
# Client.Python-036 — README "Browsing lazily" example must reference a method
|
|
# that actually exists on GalaxyRepositoryClient.
|
|
# ---------------------------------------------------------------------------
|
|
|
|
|
|
def _readme_path() -> Path:
|
|
return Path(__file__).resolve().parent.parent / "README.md"
|
|
|
|
|
|
def test_galaxy_client_exposes_browse_children_raw() -> None:
|
|
"""Guard the method name the README example depends on so future renames
|
|
break this test rather than only failing at runtime in user code.
|
|
"""
|
|
|
|
from zb_mom_ww_mxgateway import GalaxyRepositoryClient
|
|
|
|
assert hasattr(GalaxyRepositoryClient, "browse_children_raw")
|
|
|
|
|
|
def test_readme_browse_example_uses_existing_method() -> None:
|
|
"""The README's `galaxy.<method>(...BrowseChildrenRequest...)` call must name
|
|
a method that exists on GalaxyRepositoryClient.
|
|
"""
|
|
|
|
from zb_mom_ww_mxgateway import GalaxyRepositoryClient
|
|
|
|
text = _readme_path().read_text(encoding="utf-8")
|
|
called = set(re.findall(r"galaxy\.([A-Za-z_][A-Za-z0-9_]*)\s*\(", text))
|
|
assert called, "README must contain at least one galaxy.<method>(...) example"
|
|
for method in called:
|
|
assert hasattr(GalaxyRepositoryClient, method), (
|
|
f"README references galaxy.{method}() but no such method exists"
|
|
)
|