Skip to content

Commit b8a626f

Browse files
committed
test: strengthen traverse assertions and add validation unit tests
- test_traverse_returns_neighbours: assert the specific leaf1 node ID is in result.nodes, not just that the count is non-zero - test_traverse_inbound_direction: capture src_id alongside dst_id so that when the server gains inbound support (XPASS), the assertion validates the correct node was returned - TestTraverseValidation: new unit test class for ValueError cases in AsyncCoordinodeClient.traverse() — invalid direction and max_depth < 1; validation fires before any RPC so no running server is required
1 parent c1f6ed9 commit b8a626f

2 files changed

Lines changed: 57 additions & 3 deletions

File tree

tests/integration/test_sdk.py

Lines changed: 21 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -276,9 +276,20 @@ def test_traverse_returns_neighbours(client):
276276
)
277277
assert len(rows) >= 1, "hub node not found"
278278
start_id = rows[0]["node_id"]
279+
280+
# Fetch the leaf1 node ID so we can assert it specifically appears in the result.
281+
leaf_rows = client.cypher(
282+
"MATCH (b:TraverseRPC {tag: $tag, role: 'leaf1'}) RETURN b AS node_id",
283+
params={"tag": tag},
284+
)
285+
assert len(leaf_rows) >= 1, "leaf1 node not found"
286+
leaf1_id = leaf_rows[0]["node_id"]
287+
279288
result = client.traverse(start_id, "TRAVERSE_TEST", direction="outbound", max_depth=1)
280289
assert isinstance(result, TraverseResult)
281290
assert len(result.nodes) >= 1, "traverse() returned no neighbour nodes"
291+
node_ids = {n.id for n in result.nodes}
292+
assert leaf1_id in node_ids, f"traverse() did not return the expected leaf1 node ({leaf1_id}); got: {node_ids}"
282293
finally:
283294
client.cypher("MATCH (n:TraverseRPC {tag: $tag}) DETACH DELETE n", params={"tag": tag})
284295

@@ -298,16 +309,23 @@ def test_traverse_inbound_direction(client):
298309
params={"tag": tag},
299310
)
300311
try:
301-
# Get dst node id — traverse INBOUND from dst should reach src.
312+
# Capture both src and dst so that when the server gains inbound support
313+
# (XPASS), the assertion verifies the *correct* node was returned, not just any node.
302314
rows = client.cypher(
303-
"MATCH (src:TraverseIn {tag: $tag})-[:INBOUND_TEST]->(dst:TraverseIn {tag: $tag}) RETURN dst AS node_id",
315+
"MATCH (src:TraverseIn {tag: $tag})-[:INBOUND_TEST]->(dst:TraverseIn {tag: $tag}) "
316+
"RETURN src AS src_id, dst AS dst_id",
304317
params={"tag": tag},
305318
)
306319
assert len(rows) >= 1
307-
dst_id = rows[0]["node_id"]
320+
src_id = rows[0]["src_id"]
321+
dst_id = rows[0]["dst_id"]
308322
result = client.traverse(dst_id, "INBOUND_TEST", direction="inbound", max_depth=1)
309323
assert isinstance(result, TraverseResult)
310324
assert len(result.nodes) >= 1, "inbound traverse returned no nodes"
325+
node_ids = {n.id for n in result.nodes}
326+
assert src_id in node_ids, (
327+
f"inbound traverse did not return the expected source node ({src_id}); got: {node_ids}"
328+
)
311329
finally:
312330
client.cypher("MATCH (n:TraverseIn {tag: $tag}) DETACH DELETE n", params={"tag": tag})
313331

tests/unit/test_schema_crud.py

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,12 @@
55
interface that real generated messages provide.
66
"""
77

8+
import asyncio
9+
10+
import pytest
11+
812
from coordinode.client import (
13+
AsyncCoordinodeClient,
914
EdgeResult,
1015
EdgeTypeInfo,
1116
LabelInfo,
@@ -189,3 +194,34 @@ def test_repr_shows_counts(self):
189194
r = repr(result)
190195
assert "nodes=1" in r
191196
assert "edges=0" in r
197+
198+
199+
# ── traverse() input validation ──────────────────────────────────────────────
200+
201+
202+
class TestTraverseValidation:
203+
"""Unit tests for AsyncCoordinodeClient.traverse() input validation.
204+
205+
Validation (direction and max_depth checks) runs before any RPC call, so no
206+
running server is required — only the client object needs to be instantiated.
207+
"""
208+
209+
def test_invalid_direction_raises(self):
210+
"""traverse() raises ValueError for an unrecognised direction string."""
211+
212+
async def _inner() -> None:
213+
client = AsyncCoordinodeClient("localhost:0")
214+
with pytest.raises(ValueError, match="Invalid direction"):
215+
await client.traverse(1, "KNOWS", direction="sideways")
216+
217+
asyncio.run(_inner())
218+
219+
def test_max_depth_below_one_raises(self):
220+
"""traverse() raises ValueError when max_depth is less than 1."""
221+
222+
async def _inner() -> None:
223+
client = AsyncCoordinodeClient("localhost:0")
224+
with pytest.raises(ValueError, match="max_depth must be >= 1"):
225+
await client.traverse(1, "KNOWS", max_depth=0)
226+
227+
asyncio.run(_inner())

0 commit comments

Comments
 (0)