Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
14 changes: 11 additions & 3 deletions tests/test_absence_diagnosis.py
Original file line number Diff line number Diff line change
Expand Up @@ -394,12 +394,20 @@ def _find_http_route_with_handlers(graph) -> str | None:


def _find_symbol_with_edges(graph) -> NodeRef:
"""Find a real Symbol node that has at least one edge."""
"""Find a real, resolved project Symbol node that has at least one edge.

Restricted to ``s.resolved = true``: an unresolved/phantom symbol (external /
JDK / library reference) is classified ``external_dependency`` by diagnose
(external-wins precedence), not ``refine_query``. ``LIMIT 1`` without
``ORDER BY`` is platform-nondeterministic in Kùzu, so the resolved filter
guarantees a genuine project symbol on every platform (was flaky on Windows).
"""
rows = graph._rows( # noqa: SLF001
"MATCH (s:Symbol)--() RETURN s.id AS id, s.name AS name, s.fqn AS fqn, "
"MATCH (s:Symbol)--() WHERE s.resolved = true "
"RETURN s.id AS id, s.name AS name, s.fqn AS fqn, "
"s.kind AS kind LIMIT 1"
)
assert rows, "corpus has no symbol with edges"
assert rows, "corpus has no resolved symbol with edges"
row = rows[0]
return NodeRef(
id=str(row.get("id") or ""),
Expand Down
23 changes: 23 additions & 0 deletions tests/test_absence_mcp_integration.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,13 +5,33 @@
"""
from __future__ import annotations

import importlib.util

import pytest

from mcp_v2 import describe_v2, find_v2, neighbors_v2, search_v2
from resolve_service import resolve_v2
from absence_types import AbsenceVerdict


def _vector_stack_available() -> bool:
"""True when the optional vector stack (torch/sentence-transformers/lancedb) is installed.

The ``search`` tool loads a SentenceTransformer model, so tests that monkeypatch
``run_search`` still need the model importable (the patch makes ``run_search is None``
False, forcing the semantic path). Skip them on graph-only installs (macOS Intel,
where the vector trio is gated off by PEP 508 markers). Mirrors test_mcp_v2.py.
"""
return all(importlib.util.find_spec(m) is not None for m in ("sentence_transformers", "lancedb"))


needs_vectors = pytest.mark.skipif(
not _vector_stack_available(),
reason="vector stack not installed (graph-only install; macOS Intel)",
)


@needs_vectors
def test_search_empty_result_has_absence_diagnosis(ladybug_graph, monkeypatch) -> None:
"""Empty search result should have absence field populated with diagnosis."""
# Monkeypatch run_search to return empty results
Expand All @@ -29,6 +49,7 @@ def test_search_empty_result_has_absence_diagnosis(ladybug_graph, monkeypatch) -
assert out.absence.closest_symbols is not None


@needs_vectors
def test_search_typo_has_absence_diagnosis(ladybug_graph, monkeypatch) -> None:
"""Search with a typo should have refine_query verdict with closest symbols."""
monkeypatch.setattr("mcp_v2.run_search", lambda *args, **kwargs: [])
Expand All @@ -42,6 +63,7 @@ def test_search_typo_has_absence_diagnosis(ladybug_graph, monkeypatch) -> None:
assert out.absence.closest_symbols # should have did-you-mean suggestions


@needs_vectors
def test_search_external_dependency_has_absence_diagnosis(ladybug_graph, monkeypatch) -> None:
"""Search for an external dependency should have external_dependency verdict."""
monkeypatch.setattr("mcp_v2.run_search", lambda *args, **kwargs: [])
Expand All @@ -55,6 +77,7 @@ def test_search_external_dependency_has_absence_diagnosis(ladybug_graph, monkeyp
assert "java.util" in out.absence.external_identity.fqn or "java.util.List" in out.absence.external_identity.fqn


@needs_vectors
def test_search_non_empty_result_has_no_absence(ladybug_graph, monkeypatch) -> None:
"""Non-empty search result should have absence=None."""
# Mock search to return results
Expand Down
Loading