feat(python): add --parent drill-down to galaxy-browse for 5/5 CLI parity
Add --parent-gobject-id (integer) to the galaxy-browse CLI command so the Python client matches the Go (-parent) and Rust (--parent-gobject-id) CLIs. When set, drives BrowseChildren paging via browse_children_raw (page size 500, repeated-token guard) and renders the same JSON node shape (flattened object fields + hasChildrenHint + empty children array) and indented-text tree as the root-walk path. --depth is ignored on the parent path with a one-line stderr warning, matching the Go/Rust behaviour. Tests added in TDD order.
This commit is contained in:
@@ -224,12 +224,16 @@ class _FakeGalaxyClient:
|
||||
last_deploy=None,
|
||||
events=None,
|
||||
browse_roots=None,
|
||||
browse_children_pages=None,
|
||||
) -> None:
|
||||
self._ok = ok
|
||||
self._objects = objects or []
|
||||
self._last_deploy = last_deploy
|
||||
self._events = events or []
|
||||
self._browse_roots = browse_roots or []
|
||||
# List of BrowseChildrenReply-like objects to serve in order (paged).
|
||||
self._browse_children_pages = browse_children_pages or []
|
||||
self._browse_children_calls: list = []
|
||||
self.browse_options = None
|
||||
|
||||
async def __aenter__(self) -> "_FakeGalaxyClient":
|
||||
@@ -248,6 +252,13 @@ class _FakeGalaxyClient:
|
||||
self.browse_options = options
|
||||
return self._browse_roots
|
||||
|
||||
async def browse_children_raw(self, request):
|
||||
"""Return the next queued BrowseChildrenReply page; raises if queue empty."""
|
||||
self._browse_children_calls.append(request)
|
||||
if not self._browse_children_pages:
|
||||
raise AssertionError("browse_children_raw called but no pages queued")
|
||||
return self._browse_children_pages.pop(0)
|
||||
|
||||
async def get_last_deploy_time(self):
|
||||
# Mirrors galaxy.py: protobuf ToDatetime() yields a timezone-NAIVE UTC datetime.
|
||||
return self._last_deploy
|
||||
@@ -509,3 +520,128 @@ def test_galaxy_browse_rejects_out_of_range_depth(
|
||||
|
||||
assert result.exit_code != 0
|
||||
assert "--depth must be between 0 and 50" in result.output
|
||||
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# --parent-gobject-id drill-down tests
|
||||
# ---------------------------------------------------------------------------
|
||||
|
||||
|
||||
def _fake_browse_children_reply(children_and_hints, *, next_page_token=""):
|
||||
"""Build a minimal fake BrowseChildrenReply-like object."""
|
||||
from zb_mom_ww_mxgateway.generated import galaxy_repository_pb2 as galaxy_pb
|
||||
|
||||
reply = galaxy_pb.BrowseChildrenReply()
|
||||
for obj, hint in children_and_hints:
|
||||
reply.children.append(obj)
|
||||
reply.child_has_children.append(hint)
|
||||
reply.next_page_token = next_page_token
|
||||
return reply
|
||||
|
||||
|
||||
def test_galaxy_browse_parent_fetches_one_level_json(monkeypatch: pytest.MonkeyPatch) -> None:
|
||||
"""--parent-gobject-id N calls browse_children_raw and renders one-level JSON."""
|
||||
from zb_mom_ww_mxgateway.generated import galaxy_repository_pb2 as galaxy_pb
|
||||
|
||||
child_a = galaxy_pb.GalaxyObject(gobject_id=10, tag_name="PumpA", browse_name="PumpA")
|
||||
child_b = galaxy_pb.GalaxyObject(gobject_id=11, tag_name="PumpB", browse_name="PumpB")
|
||||
page = _fake_browse_children_reply([(child_a, True), (child_b, False)])
|
||||
fake = _FakeGalaxyClient(browse_children_pages=[page])
|
||||
_patch_galaxy_connect(monkeypatch, fake)
|
||||
|
||||
result = CliRunner().invoke(
|
||||
main,
|
||||
["galaxy-browse", "--plaintext", "--parent-gobject-id", "7", "--json"],
|
||||
)
|
||||
|
||||
assert result.exit_code == 0, result.output
|
||||
payload = json.loads(result.output)
|
||||
|
||||
# One BrowseChildren RPC was issued with the correct parent id.
|
||||
assert len(fake._browse_children_calls) == 1
|
||||
call_req = fake._browse_children_calls[0]
|
||||
assert call_req.parent_gobject_id == 7
|
||||
|
||||
# JSON shape mirrors the lazy-browse node shape.
|
||||
assert payload["command"] == "galaxy-browse"
|
||||
nodes = payload["nodes"]
|
||||
assert len(nodes) == 2
|
||||
assert nodes[0]["tagName"] == "PumpA"
|
||||
assert nodes[0]["hasChildrenHint"] is True
|
||||
assert nodes[0]["children"] == []
|
||||
assert nodes[1]["gobjectId"] == 11
|
||||
assert nodes[1]["hasChildrenHint"] is False
|
||||
assert nodes[1]["children"] == []
|
||||
|
||||
|
||||
def test_galaxy_browse_parent_renders_text_tree(monkeypatch: pytest.MonkeyPatch) -> None:
|
||||
"""--parent-gobject-id N text output: count line then marker lines (no indent)."""
|
||||
from zb_mom_ww_mxgateway.generated import galaxy_repository_pb2 as galaxy_pb
|
||||
|
||||
child = galaxy_pb.GalaxyObject(gobject_id=10, tag_name="PumpA", browse_name="PumpA")
|
||||
page = _fake_browse_children_reply([(child, False)])
|
||||
fake = _FakeGalaxyClient(browse_children_pages=[page])
|
||||
_patch_galaxy_connect(monkeypatch, fake)
|
||||
|
||||
result = CliRunner().invoke(
|
||||
main,
|
||||
["galaxy-browse", "--plaintext", "--parent-gobject-id", "7"],
|
||||
)
|
||||
|
||||
assert result.exit_code == 0, result.output
|
||||
lines = result.output.splitlines()
|
||||
assert lines[0] == "1"
|
||||
assert lines[1] == "- PumpA PumpA (gobject 10)"
|
||||
|
||||
|
||||
def test_galaxy_browse_parent_pages_correctly(monkeypatch: pytest.MonkeyPatch) -> None:
|
||||
"""--parent-gobject-id loops on next_page_token until exhausted."""
|
||||
from zb_mom_ww_mxgateway.generated import galaxy_repository_pb2 as galaxy_pb
|
||||
|
||||
child_a = galaxy_pb.GalaxyObject(gobject_id=10, tag_name="PumpA", browse_name="PumpA")
|
||||
child_b = galaxy_pb.GalaxyObject(gobject_id=11, tag_name="PumpB", browse_name="PumpB")
|
||||
page1 = _fake_browse_children_reply([(child_a, False)], next_page_token="tok1")
|
||||
page2 = _fake_browse_children_reply([(child_b, True)])
|
||||
fake = _FakeGalaxyClient(browse_children_pages=[page1, page2])
|
||||
_patch_galaxy_connect(monkeypatch, fake)
|
||||
|
||||
result = CliRunner().invoke(
|
||||
main,
|
||||
["galaxy-browse", "--plaintext", "--parent-gobject-id", "7", "--json"],
|
||||
)
|
||||
|
||||
assert result.exit_code == 0, result.output
|
||||
assert len(fake._browse_children_calls) == 2
|
||||
# Second call must carry the page token from the first reply.
|
||||
assert fake._browse_children_calls[1].page_token == "tok1"
|
||||
payload = json.loads(result.output)
|
||||
assert len(payload["nodes"]) == 2
|
||||
|
||||
|
||||
def test_galaxy_browse_parent_warns_when_depth_also_set(
|
||||
monkeypatch: pytest.MonkeyPatch,
|
||||
) -> None:
|
||||
"""When both --parent-gobject-id and --depth>0 are supplied a warning is emitted."""
|
||||
from zb_mom_ww_mxgateway.generated import galaxy_repository_pb2 as galaxy_pb
|
||||
|
||||
child = galaxy_pb.GalaxyObject(gobject_id=10, tag_name="PumpA", browse_name="PumpA")
|
||||
page = _fake_browse_children_reply([(child, False)])
|
||||
fake = _FakeGalaxyClient(browse_children_pages=[page])
|
||||
_patch_galaxy_connect(monkeypatch, fake)
|
||||
|
||||
# CliRunner mixes stderr into output in this Click version.
|
||||
result = CliRunner().invoke(
|
||||
main,
|
||||
["galaxy-browse", "--plaintext", "--parent-gobject-id", "7", "--depth", "2", "--json"],
|
||||
)
|
||||
|
||||
assert result.exit_code == 0, result.output
|
||||
assert "--depth is ignored" in result.output
|
||||
|
||||
|
||||
def test_galaxy_browse_help_shows_parent_gobject_id() -> None:
|
||||
"""--parent-gobject-id appears in the galaxy-browse --help output."""
|
||||
result = CliRunner().invoke(main, ["galaxy-browse", "--help"])
|
||||
|
||||
assert result.exit_code == 0
|
||||
assert "--parent-gobject-id" in result.output
|
||||
|
||||
Reference in New Issue
Block a user