Skip to content

test(mcp): sample-project fixture + assertion contract (T3 #650)#677

Open
DvirDukhan wants to merge 4 commits into
stagingfrom
dvirdukhan/mcp-t3-fixture
Open

test(mcp): sample-project fixture + assertion contract (T3 #650)#677
DvirDukhan wants to merge 4 commits into
stagingfrom
dvirdukhan/mcp-t3-fixture

Conversation

@DvirDukhan
Copy link
Copy Markdown
Contributor

@DvirDukhan DvirDukhan commented May 27, 2026

Closes #650.

Stacked on #676 (T2). Adds the shared tests/mcp/fixtures/sample_project/ + expected.yaml + conftest.py that every per-tool ticket from T4 onward reuses.

Multilingual coverage (Java/C#) deferred — see commit message.

Co-authored-by: Copilot 223556219+Copilot@users.noreply.github.com

Summary by CodeRabbit

  • Chores

    • Added PyYAML as a development dependency.
  • Tests

    • Added a reusable integration test fixture that indexes a sample project and conditionally skips if the test DB is unreachable.
    • Added contract-driven tests that assert exact analyzer outputs (counts, calls, paths, search prefixes) and verify the sample project's files are present.
  • Documentation

    • Added README describing the test fixture layout and stability expectations.
  • Chores

    • Updated fixture .gitignore to avoid transient artifacts.

Review Change Stack

New `tests/mcp/fixtures/`:
- `sample_project/python/` — canonical call graph
  `entrypoint -> service -> {UserRepo,OrderRepo}.repo -> db`
  plus a small class hierarchy (BaseRepo <- UserRepo, OrderRepo)
  and inter-file imports so IMPORTS edges exist.
- `expected.yaml` — single source of truth for every per-tool
  ticket's integration assertions: minimum per-label counts, named
  callers / callees, known paths, prefix-search hits.

New `tests/mcp/conftest.py`:
- `expected_contract` (pure-Python, always available) loads the
  YAML once per session.
- `indexed_fixture` (session-scoped) indexes the fixture into a
  unique `code:sample_project:test-<uuid>` graph so parallel CI
  shards don't contend. Self-skips when FalkorDB is unreachable.
  Uses `SourceAnalyzer.analyze_local_folder` directly so the
  fixture doesn't need to be a git repository.

New `tests/mcp/test_fixture_contract.py` — regression-tests the
fixture itself: contract shape, on-disk files, and that the
integration fixture indexes cleanly and meets the minimum count
contract.

Multilingual coverage (Java + C#) was dropped from the spec: both
multilspy analyzers demand a Maven / .NET project layout at the
indexed root, which would force this fixture into an awkward shape.
Deferred to a follow-up ticket (likely T16 which adds languages).

All 4 contract tests pass against FalkorDB on 6390.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai Bot commented May 27, 2026

Warning

Review limit reached

@DvirDukhan, we couldn't start this review because you've reached your PR review rate limit.

More reviews will be available in 7 minutes and 46 seconds. Learn how PR review limits work.

Your organization has run out of usage credits. Purchase more in the billing tab.

⌛ How to resolve this issue?

After more reviews become available, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

We recommend that you space out your commits to avoid hitting the rate limit.

🚦 How do rate limits work?

CodeRabbit enforces hourly rate limits for each developer per organization.

Our paid plans include higher PR review limits than trial, open-source, and free plans. In all cases, reviews become available again over time. During sustained high-volume PR review activity, CodeRabbit may temporarily slow when the next review becomes available.

Please see our Fair Usage Limits Policy for further information.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 32aeb215-ae1f-4a0d-9148-b25910972ada

📥 Commits

Reviewing files that changed from the base of the PR and between 286059b and 42b794b.

📒 Files selected for processing (4)
  • tests/mcp/conftest.py
  • tests/mcp/fixtures/expected.yaml
  • tests/mcp/fixtures/sample_project/python/repo.py
  • tests/mcp/test_fixture_contract.py
📝 Walkthrough

Walkthrough

This PR adds an MCP test fixture: a small Python sample project with a known call chain and class hierarchy, a pinned YAML assertion contract, session-scoped pytest fixtures (optional FalkorDB indexing), and tests that validate the contract against an indexed graph.

Changes

MCP Test Fixture and Contract Infrastructure

Layer / File(s) Summary
Contract definition and documentation
tests/mcp/fixtures/expected.yaml, tests/mcp/fixtures/sample_project/README.md
Defines expected node counts by label, caller/callee relationships, paths, and prefix-search constraints; documents the canonical entrypoint() → service() → repo() → db() call chain and BaseRepo/UserRepo/OrderRepo class hierarchy used by assertions.
Fixture infrastructure and dependencies
pyproject.toml, tests/mcp/conftest.py
Adds pyyaml>=6.0.3 to dev dependencies; implements IndexedFixture dataclass and session-scoped fixtures to load YAML contract, expose sample project path, probe FalkorDB via socket/import, and optionally create a unique indexed graph using SourceAnalyzer and Graph.
Sample Python project with canonical call graph
tests/mcp/fixtures/sample_project/python/..., tests/mcp/fixtures/sample_project/.gitignore
Implements five Python modules (__init__.py, db.py, repo.py, service.py, entrypoint.py) forming the known call chain and class hierarchy; updates .gitignore to exclude virtualenv and bytecode artifacts.
Fixture contract validation tests
tests/mcp/test_fixture_contract.py
Adds tests that verify the YAML contract file exists and matches shape, sample project files are present on disk, and that an indexed FalkorDB graph (when available) returns exact node counts per label matching the contract.

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

Possibly related PRs

  • FalkorDB/code-graph#676: Adds CI workflow that runs pytest against a FalkorDB container when tests/mcp/ or pyproject.toml change—enables CI execution of these new fixture tests.

Suggested reviewers

  • gkorland
  • galshubeli

Poem

🐰 A fixture rises, clean and bright,
With call chains dancing left and right—
From entrypoint to database deep,
A contract watched, its promise to keep.
Now tests may come and find their way,
Through verified graphs, day by day! 🌲

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 71.43% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly summarizes the main change: adding a test fixture and assertion contract for the MCP module, with a reference to the resolved issue.
Linked Issues check ✅ Passed The PR successfully implements the core in-scope objectives from issue #650: Python fixture with known call graph, expected.yaml contract, conftest.py with session fixture, and test assertions. Java/C# multilingual coverage was deferred as noted.
Out of Scope Changes check ✅ Passed All changes are directly related to issue #650 scope: fixture files, expected.yaml, conftest.py with session fixture, and contract validation tests. The pyyaml dependency addition is necessary for YAML fixture loading.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch dvirdukhan/mcp-t3-fixture

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

Comment thread tests/mcp/conftest.py Outdated
Comment on lines +71 to +74
host = os.getenv("FALKORDB_HOST", "localhost")
port = int(os.getenv("FALKORDB_PORT", 6379))
with socket.create_connection((host, port), timeout=1):
return True
Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

use falkordb-py?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done in 286059b — replaced the raw socket probe with FalkorDB(host, port, socket_timeout=1).connection.ping(). Same connection settings as the rest of the suite and surfaces auth/protocol failures, not just "port is open".

Comment thread tests/mcp/test_fixture_contract.py Outdated

counts = expected_contract["counts_min"]
for label in ("File", "Class", "Function"):
assert label in counts and isinstance(counts[label], int) and counts[label] >= 0
Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

should be exact assert for each counter per label - this is a deterministic test

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done in 286059bcounts_min renamed to counts, asserted with ==. Also fixed the contract value: File: 4 → 5 (the analyzer counts __init__.py). Verified locally against a fresh graph (also added ignore=[venv, __pycache__, .venv] to defend against a stray venv polluting the count).

Comment thread tests/mcp/test_fixture_contract.py Outdated
assert sym in calls, f"calls.{sym} missing from expected.yaml"

paths = expected_contract["paths"]
assert isinstance(paths, list) and len(paths) >= 1
Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

exact assert

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done in 286059bmin_pathspaths_count (exact), shape-check uses ==, and the indexed-fixture count test now asserts equality per label.

@DvirDukhan DvirDukhan marked this pull request as ready for review May 28, 2026 21:56
- expected.yaml: counts_min -> counts (exact); File: 4 -> File: 5
  (analyzer counts __init__.py); paths use exact paths_count.
- test_fixture_contract.py: shape check + indexed-fixture test now
  assert equality, not >=. Drops analyzer-availability hedge — the
  fixture is Python-only and the deterministic tree-sitter analyzer
  always covers it.
- conftest.py: replace raw-socket reachability probe with
  falkordb-py's FalkorDB(...).connection.ping(); same connection
  settings as the rest of the suite, surfaces auth/protocol errors
  rather than just "port is open".
- conftest.py: pass ignore=[venv, __pycache__, .venv] to
  analyze_local_folder so a stray venv (jedi/multilspy used to
  auto-create one) can never break the exact-count contract.
- fixtures/sample_project/.gitignore: prevent committing venv/.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 4

🧹 Nitpick comments (2)
tests/mcp/test_fixture_contract.py (2)

16-16: ⚡ Quick win

Remove the CI importability risk; keep only the conftest-import refactor suggestion.

tests and tests/mcp already have __init__.py, and the MCP CI runs pytest tests/mcp/ from the repo root, so from tests.mcp.conftest import EXPECTED_PATH, SAMPLE_PROJECT should resolve reliably during collection. The remaining concern is style/maintenance: importing constants from conftest.py is non-idiomatic—move EXPECTED_PATH/SAMPLE_PROJECT into a small helper module that both tests/mcp/conftest.py and tests/mcp/test_fixture_contract.py import.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@tests/mcp/test_fixture_contract.py` at line 16, The test imports
EXPECTED_PATH and SAMPLE_PROJECT from conftest.py which is non-idiomatic;
extract those constants into a small shared helper module (e.g., a new module
named fixture_helpers or fixture_constants) and update both conftest
(tests/mcp/conftest.py) and the test file (tests/mcp/test_fixture_contract.py)
to import EXPECTED_PATH and SAMPLE_PROJECT from that new helper module instead
of from conftest, keeping conftest itself importing the constants from the
helper so existing fixtures keep working.

63-81: ⚡ Quick win

Graph(project, branch) targets the composed code:{project}:{branch} graph; result_set is compatible with the [0][0] scalar count access.

  • api/graph.py composes self.name = compose_graph_name(self.project, self.branch) (i.e., code:{project}:{branch}) when given a bare project, then does self.g = self.db.select_graph(self.name), so it re-opens the named graph rather than creating a fresh unrelated one.
  • api/graph.py treats query(...).result_set as row tuples/lists (e.g., using result_set[0][0] for scalar aggregates), matching rows[0][0] for RETURN count(n) AS c.

Optional: use Graph(indexed_fixture.graph_name) for readability instead of recomposing from project/branch.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@tests/mcp/test_fixture_contract.py` around lines 63 - 81, The test currently
constructs the Graph with Graph(indexed_fixture.project,
branch=indexed_fixture.branch) which implicitly recomposes the graph name; for
clarity, change the construction to use the precomposed graph name from the
fixture (Graph(indexed_fixture.graph_name)) and leave the scalar count access
using rows[0][0] as-is since Graph.query(...).result_set returns row tuples
compatible with that access; update any variable references accordingly (e.g., g
= Graph(indexed_fixture.graph_name)) so the test reads against the explicit
composed graph name.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@pyproject.toml`:
- Line 52: tests/mcp/conftest.py imports yaml at module import time but pyyaml
is only in the dev dependency group; add "pyyaml>=6.0.3" to the
[project.optional-dependencies].test extra (the ".[test]" install) so pip
install -e ".[test]" provides the yaml module—you may keep the existing entry
under [dependency-groups].dev as well.

In `@tests/mcp/conftest.py`:
- Around line 85-88: The fixture in tests/mcp/conftest.py that creates the
FalkorDB graph should be converted to a yield-style fixture so it performs
teardown; change the fixture that currently returns/ends after creating the
graph to use "yield created_graph" and after the yield call the appropriate
delete/drop API (e.g., graph_client.delete_graph(graph_name) or
created_graph.drop()) to remove the graph; ensure any client/session (e.g.,
graph_client or falkordb_client) used to create the graph is available in the
fixture so the teardown can reliably delete the created graph.
- Around line 72-76: The port parsing can raise ValueError and should be handled
so test collection self-skips instead of erroring: around the code that does
port = int(os.getenv("FALKORDB_PORT", 6379)) (the port variable used with
socket.create_connection), catch ValueError (or validate the env var first) and
return False on invalid values; keep the existing OSError except branch for
connection failures and ensure the function always returns False for any
invalid/malformed FALKORDB_PORT rather than letting the ValueError escape.

In `@tests/mcp/fixtures/sample_project/README.md`:
- Around line 9-11: Two fenced code blocks in the README (the block containing
"entrypoint() -> service() -> {UserRepo,OrderRepo}.repo() -> db()" and the block
containing the ASCII tree starting with "BaseRepo") are missing a fence language
and trigger markdownlint MD040; prepend the opening triple-backticks with "text"
(i.e., change ``` to ```text) for both blocks so the fences declare a language
and resolve the lint warnings.

---

Nitpick comments:
In `@tests/mcp/test_fixture_contract.py`:
- Line 16: The test imports EXPECTED_PATH and SAMPLE_PROJECT from conftest.py
which is non-idiomatic; extract those constants into a small shared helper
module (e.g., a new module named fixture_helpers or fixture_constants) and
update both conftest (tests/mcp/conftest.py) and the test file
(tests/mcp/test_fixture_contract.py) to import EXPECTED_PATH and SAMPLE_PROJECT
from that new helper module instead of from conftest, keeping conftest itself
importing the constants from the helper so existing fixtures keep working.
- Around line 63-81: The test currently constructs the Graph with
Graph(indexed_fixture.project, branch=indexed_fixture.branch) which implicitly
recomposes the graph name; for clarity, change the construction to use the
precomposed graph name from the fixture (Graph(indexed_fixture.graph_name)) and
leave the scalar count access using rows[0][0] as-is since
Graph.query(...).result_set returns row tuples compatible with that access;
update any variable references accordingly (e.g., g =
Graph(indexed_fixture.graph_name)) so the test reads against the explicit
composed graph name.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 5c7520f8-f433-4877-822d-7e30a1950feb

📥 Commits

Reviewing files that changed from the base of the PR and between 471fbc6 and a598a74.

⛔ Files ignored due to path filters (1)
  • uv.lock is excluded by !**/*.lock
📒 Files selected for processing (10)
  • pyproject.toml
  • tests/mcp/conftest.py
  • tests/mcp/fixtures/expected.yaml
  • tests/mcp/fixtures/sample_project/README.md
  • tests/mcp/fixtures/sample_project/python/__init__.py
  • tests/mcp/fixtures/sample_project/python/db.py
  • tests/mcp/fixtures/sample_project/python/entrypoint.py
  • tests/mcp/fixtures/sample_project/python/repo.py
  • tests/mcp/fixtures/sample_project/python/service.py
  • tests/mcp/test_fixture_contract.py

Comment thread pyproject.toml
dev = [
"pytest>=9.0.2",
"pytest-anyio>=0.0.0",
"pyyaml>=6.0.3",
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🔴 Critical | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Verify yaml is imported from tests and absent from test extras.
set -euo pipefail

echo "== yaml imports in tests =="
rg -nP --type=py '\bimport\s+yaml\b|\bfrom\s+yaml\s+import\b' tests

echo
echo "== [project.optional-dependencies].test block =="
awk '
/^\[project.optional-dependencies\]/, /^\[/ {print}
' pyproject.toml | sed -n '1,80p'

echo
echo "== [dependency-groups].dev block =="
awk '
/^\[dependency-groups\]/, /^\[/ {print}
' pyproject.toml | sed -n '1,80p'

Repository: FalkorDB/code-graph

Length of output: 267


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "== Find any pyyaml occurrences in pyproject.toml =="
rg -n "pyyaml" pyproject.toml || true

echo
echo "== Show [project.optional-dependencies] block (with surrounding lines) =="
python3 - <<'PY'
import re
p='pyproject.toml'
lines=open(p,'r',encoding='utf-8').read().splitlines()
def show(start_pat, end_pat='^\\['):
    for i,l in enumerate(lines):
        if re.match(start_pat,l):
            j=i+1
            while j < len(lines) and not re.match(end_pat, lines[j]):
                j+=1
            # include the start line and up to just before next section header
            print(f"-- {start_pat} at line {i+1}")
            for k in range(i, j):
                print(f"{k+1}:{lines[k]}")
            return
    print(f"-- pattern not found: {start_pat}")
show(r'^\\[project\\.optional-dependencies\\]')
PY

echo
echo "== Show dependency-groups.dev entries =="
python3 - <<'PY'
import re
p='pyproject.toml'
lines=open(p,'r',encoding='utf-8').read().splitlines()
# find [dependency-groups] then locate dev subgroup
start=None
for i,l in enumerate(lines):
    if re.match(r'^\\[dependency-groups\\]$', l.strip()):
        start=i
        break
if start is None:
    raise SystemExit("no [dependency-groups]")

# print until next section header
j=start+1
while j < len(lines) and not re.match(r'^\\[', lines[j].strip()):
    j+=1
for k in range(start, j):
    print(f"{k+1}:{lines[k]}")
PY

echo
echo "== Show tests extra mapping if present (mcp or test marker references) =="
rg -n "optional-dependencies|\\[dependency-groups\\]|dependency-groups|\\.test\\]" -S pyproject.toml || true

Repository: FalkorDB/code-graph

Length of output: 1315


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "== Header line numbers =="
rg -n '^\[project\.optional-dependencies\]' pyproject.toml || true
rg -n '^\[project\.optional-dependencies\]\.test\]' pyproject.toml || true
rg -n '^\[dependency-groups\]' pyproject.toml || true
rg -n '^\[dependency-groups\]\.dev\]' pyproject.toml || true

echo
echo "== Context around line 52 (where pyyaml entry is) =="
nl -ba pyproject.toml | sed -n '40,75p'

echo
echo "== Print from [project.optional-dependencies] until next top-level header =="
start=$(rg -n '^\[project\.optional-dependencies\]' pyproject.toml -m1 | cut -d: -f1)
end=$(awk -v s="$start" 'NR>s && $0 ~ /^\[/ {print NR; exit}' pyproject.toml)
[ -z "${end:-}" ] && end=$(wc -l < pyproject.toml)
nl -ba pyproject.toml | sed -n "${start},${end}p"

echo
echo "== Print from [dependency-groups] until next top-level header =="
start2=$(rg -n '^\[dependency-groups\]' pyproject.toml -m1 | cut -d: -f1)
end2=$(awk -v s="$start2" 'NR>s && $0 ~ /^\[/ {print NR; exit}' pyproject.toml)
[ -z "${end2:-}" ] && end2=$(wc -l < pyproject.toml)
nl -ba pyproject.toml | sed -n "${start2},${end2}p"

Repository: FalkorDB/code-graph

Length of output: 263


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "== Lines 30-90 from pyproject.toml (with line numbers) =="
awk 'NR>=30 && NR<=90 {printf "%d:%s\n", NR, $0}' pyproject.toml

echo
echo "== Does pyyaml appear under project optional dependencies test? =="
awk '
  BEGIN {in_project=0; in_test=0}
  /^\[project\.optional-dependencies\]/ {in_project=1; in_test=0; next}
  /^\[/ {if (in_project) {in_project=0; in_test=0}}
  in_project && $0 ~ /^[[:space:]]*test[[:space:]]*=[[:space:]]*\[/ {in_test=1}
  in_test && $0 ~ /pyyaml/ {print FNR ":" $0; found=1}
  in_test && $0 ~ /^\s*\]/ {in_test=0}
  END {if (!found) print "No pyyaml found in [project.optional-dependencies].test"}
' pyproject.toml

echo
echo "== Does pyyaml appear under dependency-groups dev? =="
awk '
  BEGIN {in_groups=0; in_dev=0}
  /^\[dependency-groups\]/ {in_groups=1; in_dev=0; next}
  /^\[/ {if (in_groups) {in_groups=0; in_dev=0}}
  in_groups && $0 ~ /^\s*dev\s*=\s*\[/ {in_dev=1}
  in_dev && $0 ~ /pyyaml/ {print FNR ":" $0; found=1}
  in_dev && $0 ~ /^\s*\]/ {in_dev=0}
  END {if (!found) print "No pyyaml found in [dependency-groups].dev"}
' pyproject.toml

Repository: FalkorDB/code-graph

Length of output: 840


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "== tests/mcp/conftest.py import yaml section =="
sed -n '1,80p' tests/mcp/conftest.py | cat -n

echo
echo "== Any try/except around yaml import? =="
rg -n "import yaml|from yaml import|try:.*yaml|yaml.*except" tests/mcp/conftest.py

Repository: FalkorDB/code-graph

Length of output: 3109


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "== Look for how tests are installed/run (.[test], pip install extras) =="
rg -n "(\.\[test\]|\[test\]\b|pip install.*\.\[test\]|pip install.*\[test\]|poetry run|uv run|hatch|tox)" . \
  -S --glob '!**/node_modules/**' --glob '!**/dist/**' --glob '!**/build/**' --glob '!**/.git/**' || true

echo
echo "== Look for mention of dependency-groups.dev / dev group =="
rg -n "dependency-groups\.dev|\\[dependency-groups\\]|dev\\s*=\\s*\\[|pyyaml" . \
  -S --glob '!**/node_modules/**' --glob '!**/dist/**' --glob '!**/build/**' --glob '!**/.git/**' || true

Repository: FalkorDB/code-graph

Length of output: 9010


Add pyyaml to the .[test] extra to satisfy MCP tests

tests/mcp/conftest.py imports yaml at module import time, but pyyaml>=6.0.3 is only listed under [dependency-groups].dev (not under [project.optional-dependencies].test). Test installs using pip install -e ".[test]" can fail with ModuleNotFoundError: yaml. Add pyyaml>=6.0.3 to [project.optional-dependencies].test (you can keep it in dev too).

Suggested patch
 [project.optional-dependencies]
 test = [
     "pytest>=9.0.2,<10.0.0",
     "ruff>=0.11.0,<1.0.0",
     "httpx>=0.28.0,<1.0.0",
     "anyio>=4.0,<5.0",
+    "pyyaml>=6.0.3",
 ]
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@pyproject.toml` at line 52, tests/mcp/conftest.py imports yaml at module
import time but pyyaml is only in the dev dependency group; add "pyyaml>=6.0.3"
to the [project.optional-dependencies].test extra (the ".[test]" install) so pip
install -e ".[test]" provides the yaml module—you may keep the existing entry
under [dependency-groups].dev as well.

Comment thread tests/mcp/conftest.py
Comment thread tests/mcp/conftest.py Outdated
Comment on lines +9 to +11
```
entrypoint() -> service() -> {UserRepo,OrderRepo}.repo() -> db()
```
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

Add fence languages to satisfy markdownlint.

The two fenced blocks should declare a language (for these, text is enough) to resolve MD040 warnings.

Suggested patch
-```
+```text
 entrypoint() -> service() -> {UserRepo,OrderRepo}.repo() -> db()

@@
- +text
BaseRepo
├── UserRepo
└── OrderRepo

Also applies to: 15-19

🧰 Tools
🪛 markdownlint-cli2 (0.22.1)

[warning] 9-9: Fenced code blocks should have a language specified

(MD040, fenced-code-language)

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@tests/mcp/fixtures/sample_project/README.md` around lines 9 - 11, Two fenced
code blocks in the README (the block containing "entrypoint() -> service() ->
{UserRepo,OrderRepo}.repo() -> db()" and the block containing the ASCII tree
starting with "BaseRepo") are missing a fence language and trigger markdownlint
MD040; prepend the opening triple-backticks with "text" (i.e., change ``` to
```text) for both blocks so the fences declare a language and resolve the lint
warnings.

…fixture

# Conflicts:
#	pyproject.toml
#	uv.lock
Comment thread tests/mcp/test_fixture_contract.py Fixed
Comment thread tests/mcp/test_fixture_contract.py Fixed
@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai Bot commented May 28, 2026

Actionable comments posted: 0

Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Adds the shared MCP test fixture infrastructure (T3) on top of the T2 CI workflow: a small deterministic Python sample project, an expected.yaml assertion contract, a shared conftest.py that boots a uniquely-named FalkorDB graph and indexes the project once per session, and a sanity test validating the contract shape and exact node counts. Java/C# fixtures from the original spec are explicitly deferred. PyYAML is added as a dev dependency.

Changes:

  • New tests/mcp/fixtures/sample_project/ Python tree with entrypoint → service → repo → db call chain and BaseRepo/UserRepo/OrderRepo hierarchy.
  • New tests/mcp/conftest.py exposing expected_contract, sample_project_path, and a self-skipping session-scoped indexed_fixture.
  • New tests/mcp/test_fixture_contract.py validating contract shape, on-disk files, and exact per-label node counts; pyyaml>=6.0.3 added to dev deps and uv.lock.

Reviewed changes

Copilot reviewed 11 out of 12 changed files in this pull request and generated 3 comments.

Show a summary per file
File Description
pyproject.toml Adds pyyaml>=6.0.3 to the dev dependency group for YAML contract loading.
uv.lock Locks the new pyyaml dev dependency.
tests/mcp/conftest.py Defines IndexedFixture, contract loader, FalkorDB-reachability probe, and the session-scoped indexing fixture.
tests/mcp/test_fixture_contract.py Asserts contract shape, fixture file presence, and indexed node counts.
tests/mcp/fixtures/expected.yaml Declares counts, calls, paths, and prefix-search expectations.
tests/mcp/fixtures/sample_project/README.md Documents the canonical call graph and deferral of Java/C#.
tests/mcp/fixtures/sample_project/.gitignore Ignores venv/, __pycache__/, *.pyc to keep counts deterministic.
tests/mcp/fixtures/sample_project/python/{entrypoint,service,repo,db,init}.py Implements the deterministic call chain and class hierarchy.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment thread tests/mcp/conftest.py
Comment on lines +85 to +131
@pytest.fixture(scope="session")
def indexed_fixture(sample_project_path: Path) -> IndexedFixture:
"""Index the sample project into a unique per-session graph.

Each test session creates a new graph named
``code:sample_project:test-<uuid>`` so parallel CI shards never
contend on the same graph. The graph is intentionally **not**
cleaned up — short-lived CI runners discard the FalkorDB volume,
and keeping it around helps post-mortem debugging on developer
machines.

Uses :class:`api.analyzers.SourceAnalyzer` directly (instead of
``Project.from_local_repository``) so the fixture doesn't need to
be a git repository — analyzing a plain directory is exactly the
code path the ``index_repo`` MCP tool exercises for non-git
folders.
"""

if not _falkordb_reachable():
pytest.skip("FalkorDB not reachable on $FALKORDB_HOST:$FALKORDB_PORT")

# Import locally so unit-only tests don't pay the import cost.
from api.analyzers.source_analyzer import SourceAnalyzer
from api.graph import Graph

project_name = sample_project_path.name # "sample_project"
branch = f"test-{uuid.uuid4().hex[:8]}"
graph = Graph(project_name, branch=branch)

analyzer = SourceAnalyzer()
# Belt-and-suspenders: jedi/multilspy used to create venv/ inside the
# fixture during resolution, which polluted the tree with hundreds of
# site-packages files. Tree-sitter doesn't do this, but we still pass
# an explicit ignore list so a stray venv on a contributor's machine
# can never break the exact-count contract.
analyzer.analyze_local_folder(
str(sample_project_path),
graph,
ignore=["venv", "__pycache__", ".venv"],
)

return IndexedFixture(
project=project_name,
branch=branch,
graph_name=graph.name,
path=sample_project_path,
)
Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done in 42b794bindexed_fixture is now a yield fixture that calls graph.delete() on teardown. Verified locally: GRAPH.LIST is empty after a clean run.

Preserved the post-mortem-debugging path via an opt-in env var: set MCP_KEEP_TEST_GRAPHS=1 and the graph survives the session. Also verified that path — the code:sample_project:test-<uuid> graph is left in place for inspection.

Comment on lines +64 to +82
def test_indexed_fixture_loads_exact_counts(indexed_fixture, expected_contract):
"""The fixture indexes cleanly and matches the exact count contract.

Subsequent per-tool tickets (T4+) use ``indexed_fixture`` directly;
this test exists so the fixture itself is regression-tested in
isolation.
"""

from api.graph import Graph

g = Graph(indexed_fixture.project, branch=indexed_fixture.branch)
counts = expected_contract["counts"]

for label, expected in counts.items():
rows = g.g.query(f"MATCH (n:{label}) RETURN count(n) AS c").result_set
actual = rows[0][0] if rows else 0
assert actual == expected, (
f"label {label}: expected exactly {expected}, got {actual}"
)
Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done in 42b794b — added three integration tests:

  • test_indexed_fixture_callers_and_callees — exact sorted CALLS in/out per named symbol.
  • test_indexed_fixture_paths — exact find_paths count per (src, dst).
  • test_indexed_fixture_prefix_search — every must_include name resolves via api.auto_complete.prefix_search.

Probing the live graph surfaced two latent contract drifts that the missing tests had hidden:

  • entrypoint -> db actually has 2 paths, not 1 (one per subclass — UserRepo.repo and OrderRepo.repo both route through super().repo() -> BaseRepo.repo() -> db). Updated paths_count.
  • Prefix Repo matches lowercase repo only (Searchable.name is case-insensitive startswith), not the *Repo class names. Swapped to prefix Base which resolves to BaseRepo.

Also tightened expected_contract_shape to require both callers and callees as exact lists (was: just sym in calls). 10/10 mcp tests green.



class BaseRepo:
"""Base class so the analyzer emits an INHERITS edge."""
Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done in 42b794b — docstring updated to EXTENDS to match SourceAnalyzer.second_pass (api/analyzers/source_analyzer.py:169).

Five comments from @DvirDukhan + bots:

1. (Copilot) indexed_fixture leaked test-<uuid> graphs into FalkorDB
   on every run. Convert to a yield fixture that drops the graph on
   teardown; preserve post-mortem debugging via opt-in
   MCP_KEEP_TEST_GRAPHS=1 env var.

2. (Copilot) T3 acceptance #650 requires asserting at least one named
   caller/callee, named path, and prefix-search hit. The original PR
   only checked node counts; the calls/paths/search_prefixes sections
   of expected.yaml were never exercised. Add three integration tests:
   - test_indexed_fixture_callers_and_callees: exact sorted lists of
     CALLS in/out for each named symbol.
   - test_indexed_fixture_paths: exact path count for each (src,dst).
   - test_indexed_fixture_prefix_search: assert each must_include name
     resolves via api.auto_complete.prefix_search.

   Probing the live graph revealed two contract drifts that the
   missing tests had hidden:
   - entrypoint -> db has 2 paths, not 1 (one per subclass: UserRepo
     and OrderRepo both route through their .repo() override).
   - prefix 'Repo' matches lowercase 'repo' (Searchable.name
     case-insensitive startswith), not BaseRepo/UserRepo/OrderRepo.
     Swap to prefix 'Base' which actually resolves to 'BaseRepo'.

   Also tightened expected_contract_shape to require both callers
   and callees as exact lists (was: just 'sym in calls').

3. (Copilot) fixtures/sample_project/python/repo.py docstring said
   the analyzer emits an INHERITS edge; the analyzer actually emits
   EXTENDS (api/analyzers/source_analyzer.py:169). Update the
   docstring to match so a future contributor writing T6 doesn't
   try to assert against a non-existent edge type.

4. (code-quality bot) unused 'Path' import in
   tests/mcp/test_fixture_contract.py — removed.

5. (code-quality bot) unused 'pytest' import in same file — removed.

All 10 mcp tests pass; teardown verified empty (and verified the
MCP_KEEP_TEST_GRAPHS=1 escape hatch leaves the graph in place).

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[MCP T3] Test fixture project + assertion contract

2 participants