|
| 1 | +"""Unit tests for R-SDK3 additions: LabelInfo, EdgeTypeInfo, TraverseResult. |
| 2 | +
|
| 3 | +All tests are mock-based — no proto stubs or running server required. |
| 4 | +Pattern mirrors test_types.py: fake proto objects with the same attribute |
| 5 | +interface that real generated messages provide. |
| 6 | +""" |
| 7 | + |
| 8 | +from coordinode.client import ( |
| 9 | + EdgeResult, |
| 10 | + EdgeTypeInfo, |
| 11 | + LabelInfo, |
| 12 | + NodeResult, |
| 13 | + PropertyDefinitionInfo, |
| 14 | + TraverseResult, |
| 15 | +) |
| 16 | + |
| 17 | + |
| 18 | +# ── Fake proto stubs ───────────────────────────────────────────────────────── |
| 19 | + |
| 20 | + |
| 21 | +class _FakePropDef: |
| 22 | + """Matches proto PropertyDefinition shape.""" |
| 23 | + |
| 24 | + def __init__(self, name: str, type_: int, required: bool = False, unique: bool = False) -> None: |
| 25 | + self.name = name |
| 26 | + self.type = type_ |
| 27 | + self.required = required |
| 28 | + self.unique = unique |
| 29 | + |
| 30 | + |
| 31 | +class _FakeLabel: |
| 32 | + """Matches proto Label shape.""" |
| 33 | + |
| 34 | + def __init__(self, name: str, version: int = 1, properties=None) -> None: |
| 35 | + self.name = name |
| 36 | + self.version = version |
| 37 | + self.properties = properties or [] |
| 38 | + |
| 39 | + |
| 40 | +class _FakeEdgeType: |
| 41 | + """Matches proto EdgeType shape.""" |
| 42 | + |
| 43 | + def __init__(self, name: str, version: int = 1, properties=None) -> None: |
| 44 | + self.name = name |
| 45 | + self.version = version |
| 46 | + self.properties = properties or [] |
| 47 | + |
| 48 | + |
| 49 | +class _FakeNode: |
| 50 | + """Matches proto Node shape.""" |
| 51 | + |
| 52 | + def __init__(self, node_id: int, labels=None, properties=None) -> None: |
| 53 | + self.node_id = node_id |
| 54 | + self.labels = labels or [] |
| 55 | + self.properties = properties or {} |
| 56 | + |
| 57 | + |
| 58 | +class _FakeEdge: |
| 59 | + """Matches proto Edge shape.""" |
| 60 | + |
| 61 | + def __init__(self, edge_id: int, edge_type: str, source: int, target: int, properties=None) -> None: |
| 62 | + self.edge_id = edge_id |
| 63 | + self.edge_type = edge_type |
| 64 | + self.source_node_id = source |
| 65 | + self.target_node_id = target |
| 66 | + self.properties = properties or {} |
| 67 | + |
| 68 | + |
| 69 | +class _FakeTraverseResponse: |
| 70 | + """Matches proto TraverseResponse shape.""" |
| 71 | + |
| 72 | + def __init__(self, nodes=None, edges=None) -> None: |
| 73 | + self.nodes = nodes or [] |
| 74 | + self.edges = edges or [] |
| 75 | + |
| 76 | + |
| 77 | +# ── PropertyDefinitionInfo ─────────────────────────────────────────────────── |
| 78 | + |
| 79 | + |
| 80 | +class TestPropertyDefinitionInfo: |
| 81 | + def test_fields_are_mapped(self): |
| 82 | + # type=3 = PROPERTY_TYPE_STRING (int value from proto enum) |
| 83 | + p = PropertyDefinitionInfo(_FakePropDef("name", 3, required=True, unique=False)) |
| 84 | + assert p.name == "name" |
| 85 | + assert p.type == 3 |
| 86 | + assert p.required is True |
| 87 | + assert p.unique is False |
| 88 | + |
| 89 | + def test_repr_contains_name(self): |
| 90 | + p = PropertyDefinitionInfo(_FakePropDef("age", 1)) |
| 91 | + assert "age" in repr(p) |
| 92 | + |
| 93 | + def test_optional_flags_default_false(self): |
| 94 | + p = PropertyDefinitionInfo(_FakePropDef("x", 2)) |
| 95 | + assert p.required is False |
| 96 | + assert p.unique is False |
| 97 | + |
| 98 | + |
| 99 | +# ── LabelInfo ──────────────────────────────────────────────────────────────── |
| 100 | + |
| 101 | + |
| 102 | +class TestLabelInfo: |
| 103 | + def test_empty_properties(self): |
| 104 | + label = LabelInfo(_FakeLabel("Person", version=2)) |
| 105 | + assert label.name == "Person" |
| 106 | + assert label.version == 2 |
| 107 | + assert label.properties == [] |
| 108 | + |
| 109 | + def test_properties_are_wrapped(self): |
| 110 | + props = [_FakePropDef("name", 3), _FakePropDef("age", 1)] |
| 111 | + label = LabelInfo(_FakeLabel("User", properties=props)) |
| 112 | + assert len(label.properties) == 2 |
| 113 | + assert all(isinstance(p, PropertyDefinitionInfo) for p in label.properties) |
| 114 | + assert label.properties[0].name == "name" |
| 115 | + assert label.properties[1].name == "age" |
| 116 | + |
| 117 | + def test_repr_contains_name(self): |
| 118 | + label = LabelInfo(_FakeLabel("Movie")) |
| 119 | + assert "Movie" in repr(label) |
| 120 | + |
| 121 | + def test_version_zero(self): |
| 122 | + # Schema registry may return version=0 for newly created labels. |
| 123 | + label = LabelInfo(_FakeLabel("Draft", version=0)) |
| 124 | + assert label.version == 0 |
| 125 | + |
| 126 | + |
| 127 | +# ── EdgeTypeInfo ───────────────────────────────────────────────────────────── |
| 128 | + |
| 129 | + |
| 130 | +class TestEdgeTypeInfo: |
| 131 | + def test_basic_fields(self): |
| 132 | + et = EdgeTypeInfo(_FakeEdgeType("KNOWS", version=1)) |
| 133 | + assert et.name == "KNOWS" |
| 134 | + assert et.version == 1 |
| 135 | + assert et.properties == [] |
| 136 | + |
| 137 | + def test_properties_are_wrapped(self): |
| 138 | + props = [_FakePropDef("since", 6)] # 6 = TIMESTAMP |
| 139 | + et = EdgeTypeInfo(_FakeEdgeType("FOLLOWS", properties=props)) |
| 140 | + assert len(et.properties) == 1 |
| 141 | + assert et.properties[0].name == "since" |
| 142 | + |
| 143 | + def test_repr_contains_name(self): |
| 144 | + et = EdgeTypeInfo(_FakeEdgeType("RATED")) |
| 145 | + assert "RATED" in repr(et) |
| 146 | + |
| 147 | + |
| 148 | +# ── TraverseResult ─────────────────────────────────────────────────────────── |
| 149 | + |
| 150 | + |
| 151 | +class TestTraverseResult: |
| 152 | + def test_empty_response(self): |
| 153 | + result = TraverseResult(_FakeTraverseResponse()) |
| 154 | + assert result.nodes == [] |
| 155 | + assert result.edges == [] |
| 156 | + |
| 157 | + def test_nodes_are_wrapped_as_node_results(self): |
| 158 | + nodes = [_FakeNode(1, ["Person"]), _FakeNode(2, ["Movie"])] |
| 159 | + result = TraverseResult(_FakeTraverseResponse(nodes=nodes)) |
| 160 | + assert len(result.nodes) == 2 |
| 161 | + assert all(isinstance(n, NodeResult) for n in result.nodes) |
| 162 | + assert result.nodes[0].id == 1 |
| 163 | + assert result.nodes[1].id == 2 |
| 164 | + |
| 165 | + def test_edges_are_wrapped_as_edge_results(self): |
| 166 | + edges = [_FakeEdge(10, "KNOWS", source=1, target=2)] |
| 167 | + result = TraverseResult(_FakeTraverseResponse(edges=edges)) |
| 168 | + assert len(result.edges) == 1 |
| 169 | + assert isinstance(result.edges[0], EdgeResult) |
| 170 | + assert result.edges[0].source_id == 1 |
| 171 | + assert result.edges[0].target_id == 2 |
| 172 | + assert result.edges[0].type == "KNOWS" |
| 173 | + |
| 174 | + def test_mixed_nodes_and_edges(self): |
| 175 | + nodes = [_FakeNode(1, ["A"]), _FakeNode(2, ["B"]), _FakeNode(3, ["C"])] |
| 176 | + edges = [ |
| 177 | + _FakeEdge(10, "REL", 1, 2), |
| 178 | + _FakeEdge(11, "REL", 2, 3), |
| 179 | + ] |
| 180 | + result = TraverseResult(_FakeTraverseResponse(nodes=nodes, edges=edges)) |
| 181 | + assert len(result.nodes) == 3 |
| 182 | + assert len(result.edges) == 2 |
| 183 | + |
| 184 | + def test_repr_shows_counts(self): |
| 185 | + nodes = [_FakeNode(1, [])] |
| 186 | + result = TraverseResult(_FakeTraverseResponse(nodes=nodes)) |
| 187 | + r = repr(result) |
| 188 | + assert "1" in r # 1 node |
| 189 | + assert "0" in r # 0 edges |
0 commit comments