|
15 | 15 | finalize_hint_list, |
16 | 16 | generate_hints, |
17 | 17 | ) |
| 18 | +from java_ontology import FUZZY_STRATEGY_SET |
18 | 19 | from mcp_v2 import FindOutput, SearchOutput, describe_v2, find_v2, neighbors_v2, resolve_v2, search_v2 |
19 | 20 |
|
20 | 21 | _TYPE_KINDS = frozenset({"class", "interface", "enum", "record", "annotation"}) |
@@ -253,6 +254,100 @@ def test_hints_neighbors_empty_with_edge_types_emits_kind_check(kuzu_graph) -> N |
253 | 254 | assert mcp_hints.TPL_NEIGHBORS_EMPTY_KIND_CHECK in out.hints |
254 | 255 |
|
255 | 256 |
|
| 257 | +def _neighbors_hint_payload( |
| 258 | + results: list[dict[str, Any]], |
| 259 | + *, |
| 260 | + requested_edge_types: list[str] | None = None, |
| 261 | +) -> dict[str, Any]: |
| 262 | + return { |
| 263 | + "success": True, |
| 264 | + "results": results, |
| 265 | + "requested_edge_types": requested_edge_types or ["DECLARES_CLIENT"], |
| 266 | + } |
| 267 | + |
| 268 | + |
| 269 | +def _edge_result(*, strategy: str | None = None, edge_type: str = "DECLARES_CLIENT") -> dict[str, Any]: |
| 270 | + attrs: dict[str, Any] = {} |
| 271 | + if strategy is not None: |
| 272 | + attrs["strategy"] = strategy |
| 273 | + return { |
| 274 | + "origin_id": "sym:pkg.Type#m()", |
| 275 | + "edge_type": edge_type, |
| 276 | + "direction": "out", |
| 277 | + "other": {"id": "client:svc:feign:t:GET:/p", "kind": "client"}, |
| 278 | + "attrs": attrs, |
| 279 | + } |
| 280 | + |
| 281 | + |
| 282 | +def _method_id_with_fuzzy_calls(kuzu_graph) -> str: |
| 283 | + rows = kuzu_graph._rows( # noqa: SLF001 |
| 284 | + "MATCH (m:Symbol)-[e:CALLS]->() " |
| 285 | + "WHERE e.strategy IN $strategies " |
| 286 | + "RETURN m.id AS id LIMIT 1", |
| 287 | + {"strategies": sorted(FUZZY_STRATEGY_SET)}, |
| 288 | + ) |
| 289 | + if not rows: |
| 290 | + pytest.fail("no CALLS edge with fuzzy strategy in bank fixture") |
| 291 | + return str(rows[0]["id"]) |
| 292 | + |
| 293 | + |
| 294 | +def test_hints_neighbors_fuzzy_strategy_layer_c_source_emits() -> None: |
| 295 | + payload = _neighbors_hint_payload([_edge_result(strategy="layer_c_source")]) |
| 296 | + hints = generate_hints("neighbors", payload) |
| 297 | + assert mcp_hints.TPL_NEIGHBORS_FUZZY_STRATEGY in hints |
| 298 | + assert "attrs.strategy" in hints[0] |
| 299 | + |
| 300 | + |
| 301 | +def test_hints_neighbors_fuzzy_strategy_annotation_absent() -> None: |
| 302 | + payload = _neighbors_hint_payload([_edge_result(strategy="annotation")]) |
| 303 | + assert generate_hints("neighbors", payload) == [] |
| 304 | + |
| 305 | + |
| 306 | +def test_hints_neighbors_fuzzy_strategy_calls_phantom_emits() -> None: |
| 307 | + payload = _neighbors_hint_payload( |
| 308 | + [_edge_result(strategy="phantom", edge_type="CALLS")], |
| 309 | + requested_edge_types=["CALLS"], |
| 310 | + ) |
| 311 | + hints = generate_hints("neighbors", payload) |
| 312 | + assert mcp_hints.TPL_NEIGHBORS_FUZZY_STRATEGY in hints |
| 313 | + |
| 314 | + |
| 315 | +def test_hints_neighbors_declares_no_strategy_attrs_empty() -> None: |
| 316 | + payload = _neighbors_hint_payload( |
| 317 | + [_edge_result(edge_type="DECLARES")], |
| 318 | + requested_edge_types=["DECLARES"], |
| 319 | + ) |
| 320 | + assert generate_hints("neighbors", payload) == [] |
| 321 | + |
| 322 | + |
| 323 | +def test_hints_neighbors_multi_origin_fuzzy_emits_once() -> None: |
| 324 | + payload = _neighbors_hint_payload( |
| 325 | + [ |
| 326 | + _edge_result(strategy="phantom", edge_type="CALLS"), |
| 327 | + _edge_result(strategy="annotation", edge_type="CALLS"), |
| 328 | + ], |
| 329 | + requested_edge_types=["CALLS"], |
| 330 | + ) |
| 331 | + hints = generate_hints("neighbors", payload) |
| 332 | + assert hints.count(mcp_hints.TPL_NEIGHBORS_FUZZY_STRATEGY) == 1 |
| 333 | + |
| 334 | + |
| 335 | +def test_hints_neighbors_layer_a_meta_no_fuzzy_hint() -> None: |
| 336 | + payload = _neighbors_hint_payload([_edge_result(strategy="layer_a_meta")]) |
| 337 | + assert generate_hints("neighbors", payload) == [] |
| 338 | + |
| 339 | + |
| 340 | +def test_hints_neighbors_fuzzy_strategy_neighbors_v2_round_trip(kuzu_graph) -> None: |
| 341 | + mid = _method_id_with_fuzzy_calls(kuzu_graph) |
| 342 | + out = neighbors_v2(mid, direction="out", edge_types=["CALLS"], graph=kuzu_graph, limit=50) |
| 343 | + assert out.success is True |
| 344 | + assert out.results |
| 345 | + strategies = [e.attrs.get("strategy") for e in out.results] |
| 346 | + assert any(s in FUZZY_STRATEGY_SET for s in strategies if isinstance(s, str)) |
| 347 | + assert mcp_hints.TPL_NEIGHBORS_FUZZY_STRATEGY in out.hints |
| 348 | + assert "brownfield/fallback strategy" in out.hints[0] |
| 349 | + |
| 350 | + |
256 | 351 | def test_hints_search_weak_structural_signal_emits(monkeypatch, kuzu_graph) -> None: |
257 | 352 | rows = [ |
258 | 353 | { |
@@ -735,6 +830,7 @@ def test_hints_pagination_none_skips_page_derived_hints() -> None: |
735 | 830 | ), |
736 | 831 | (mcp_hints.TPL_RESOLVE_NONE_TRY_FIND_CLIENT, {"seed": "smartcare-assign-chat"}), |
737 | 832 | (mcp_hints.TPL_RESOLVE_MANY_TIGHTEN, {"n": 10}), |
| 833 | + (mcp_hints.TPL_NEIGHBORS_FUZZY_STRATEGY, {}), |
738 | 834 | ], |
739 | 835 | ) |
740 | 836 | def test_hints_template_rendered_length_leq_120(template: str, fmt: dict[str, Any]) -> None: |
|
0 commit comments