|
12 | 12 |
|
13 | 13 | import pytest |
14 | 14 |
|
15 | | -from coordinode import AsyncCoordinodeClient, CoordinodeClient |
| 15 | +from coordinode import AsyncCoordinodeClient, CoordinodeClient, LabelInfo, EdgeTypeInfo, TraverseResult |
16 | 16 |
|
17 | 17 | ADDR = os.environ.get("COORDINODE_ADDR", "localhost:7080") |
18 | 18 |
|
@@ -208,6 +208,98 @@ def test_get_schema_text(client): |
208 | 208 | client.cypher("MATCH (n:SchemaTestLabel {tag: $tag}) DETACH DELETE n", params={"tag": tag}) |
209 | 209 |
|
210 | 210 |
|
| 211 | +# ── get_labels / get_edge_types / traverse ──────────────────────────────────── |
| 212 | + |
| 213 | + |
| 214 | +def test_get_labels_returns_list(client): |
| 215 | + """get_labels() returns a non-empty list of LabelInfo after data is present.""" |
| 216 | + tag = uid() |
| 217 | + client.cypher("CREATE (n:GetLabelsTest {tag: $tag})", params={"tag": tag}) |
| 218 | + try: |
| 219 | + labels = client.get_labels() |
| 220 | + assert isinstance(labels, list) |
| 221 | + assert len(labels) > 0 |
| 222 | + assert all(isinstance(l, LabelInfo) for l in labels) |
| 223 | + names = [l.name for l in labels] |
| 224 | + assert "GetLabelsTest" in names, f"GetLabelsTest not in {names}" |
| 225 | + finally: |
| 226 | + client.cypher("MATCH (n:GetLabelsTest {tag: $tag}) DELETE n", params={"tag": tag}) |
| 227 | + |
| 228 | + |
| 229 | +def test_get_labels_has_property_definitions(client): |
| 230 | + """LabelInfo.properties is a list (may be empty for schema-free labels).""" |
| 231 | + client.cypher("MERGE (n:PropLabel {name: 'probe'})") |
| 232 | + try: |
| 233 | + labels = client.get_labels() |
| 234 | + found = next((l for l in labels if l.name == "PropLabel"), None) |
| 235 | + assert found is not None, "PropLabel not returned by get_labels()" |
| 236 | + assert isinstance(found.properties, list) |
| 237 | + finally: |
| 238 | + client.cypher("MATCH (n:PropLabel {name: 'probe'}) DELETE n") |
| 239 | + |
| 240 | + |
| 241 | +def test_get_edge_types_returns_list(client): |
| 242 | + """get_edge_types() returns a non-empty list of EdgeTypeInfo after data is present.""" |
| 243 | + tag = uid() |
| 244 | + client.cypher( |
| 245 | + "CREATE (a:EdgeTypeTestNode {tag: $tag})-[:GET_EDGE_TYPE_TEST]->(b:EdgeTypeTestNode {tag: $tag})", |
| 246 | + params={"tag": tag}, |
| 247 | + ) |
| 248 | + try: |
| 249 | + edge_types = client.get_edge_types() |
| 250 | + assert isinstance(edge_types, list) |
| 251 | + assert len(edge_types) > 0 |
| 252 | + assert all(isinstance(et, EdgeTypeInfo) for et in edge_types) |
| 253 | + type_names = [et.name for et in edge_types] |
| 254 | + assert "GET_EDGE_TYPE_TEST" in type_names, f"GET_EDGE_TYPE_TEST not in {type_names}" |
| 255 | + finally: |
| 256 | + client.cypher("MATCH (n:EdgeTypeTestNode {tag: $tag}) DETACH DELETE n", params={"tag": tag}) |
| 257 | + |
| 258 | + |
| 259 | +def test_traverse_returns_neighbours(client): |
| 260 | + """traverse() returns adjacent nodes reachable via the given edge type.""" |
| 261 | + tag = uid() |
| 262 | + client.cypher( |
| 263 | + "CREATE (a:TraverseRPC {tag: $tag, role: 'hub'})" |
| 264 | + "-[:TRAVERSE_TEST]->(b:TraverseRPC {tag: $tag, role: 'leaf1'})", |
| 265 | + params={"tag": tag}, |
| 266 | + ) |
| 267 | + rows = client.cypher( |
| 268 | + "MATCH (a:TraverseRPC {tag: $tag, role: 'hub'}) RETURN a AS node_id", |
| 269 | + params={"tag": tag}, |
| 270 | + ) |
| 271 | + try: |
| 272 | + assert len(rows) >= 1, "hub node not found" |
| 273 | + start_id = rows[0]["node_id"] |
| 274 | + result = client.traverse(start_id, "TRAVERSE_TEST", direction="outbound", max_depth=1) |
| 275 | + assert isinstance(result, TraverseResult) |
| 276 | + assert len(result.nodes) >= 1, "traverse() returned no neighbour nodes" |
| 277 | + finally: |
| 278 | + client.cypher("MATCH (n:TraverseRPC {tag: $tag}) DETACH DELETE n", params={"tag": tag}) |
| 279 | + |
| 280 | + |
| 281 | +def test_traverse_inbound_direction(client): |
| 282 | + """traverse() with direction='inbound' reaches nodes that point TO start_id.""" |
| 283 | + tag = uid() |
| 284 | + client.cypher( |
| 285 | + "CREATE (src:TraverseIn {tag: $tag})-[:INBOUND_TEST]->(dst:TraverseIn {tag: $tag})", |
| 286 | + params={"tag": tag}, |
| 287 | + ) |
| 288 | + # Get dst node id — traverse INBOUND from dst should reach src. |
| 289 | + rows = client.cypher( |
| 290 | + "MATCH (src:TraverseIn {tag: $tag})-[:INBOUND_TEST]->(dst:TraverseIn {tag: $tag}) RETURN dst AS node_id", |
| 291 | + params={"tag": tag}, |
| 292 | + ) |
| 293 | + try: |
| 294 | + assert len(rows) >= 1 |
| 295 | + dst_id = rows[0]["node_id"] |
| 296 | + result = client.traverse(dst_id, "INBOUND_TEST", direction="inbound", max_depth=1) |
| 297 | + assert isinstance(result, TraverseResult) |
| 298 | + assert len(result.nodes) >= 1, "inbound traverse returned no nodes" |
| 299 | + finally: |
| 300 | + client.cypher("MATCH (n:TraverseIn {tag: $tag}) DETACH DELETE n", params={"tag": tag}) |
| 301 | + |
| 302 | + |
211 | 303 | # ── Hybrid search ───────────────────────────────────────────────────────────── |
212 | 304 |
|
213 | 305 |
|
|
0 commit comments