forked from Ikalus1988/MisakaNet
-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathtest_mcp_fallback.py
More file actions
89 lines (65 loc) 路 2.9 KB
/
Copy pathtest_mcp_fallback.py
File metadata and controls
89 lines (65 loc) 路 2.9 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
#!/usr/bin/env python3
"""Tests for the lessons.json keyword fallback (Issue #913).
When neither SAG-Lite nor BM25 is available, the MCP search tool falls
back to keyword matching over data/lessons.json (implemented in
mcp_server._fallback_search) and reports source "fallback" so callers
can distinguish the three modes. These tests pin the behaviour and
prevent regressions in the sandbox fallback path.
"""
import json
import sys
from pathlib import Path
REPO_ROOT = Path(__file__).resolve().parent.parent
sys.path.insert(0, str(REPO_ROOT))
import pytest
import scripts.mcp_server as mcp
@pytest.fixture(autouse=True)
def no_engines(monkeypatch):
"""Force both SAG-Lite and BM25 to be unavailable."""
monkeypatch.setattr(mcp, "HAS_SAG", False)
monkeypatch.setattr(mcp, "HAS_BM25", False)
yield
def test_fallback_returns_results_instead_of_error():
resp = mcp.handle_search({"query": "MCP", "top": 5})
assert "error" not in resp
assert "results" in resp
assert resp["source"] == "fallback"
def test_fallback_source_is_distinct():
"""The source field must be exactly 'fallback' (not sag-lite/bm25)."""
resp = mcp.handle_search({"query": "MCP", "top": 3})
assert resp["source"] == "fallback"
def test_fallback_results_have_lesson_shape():
"""Fallback results carry the documented shape (title/path/domain)."""
resp = mcp.handle_search({"query": "sandbox", "top": 3})
for r in resp["results"]:
assert isinstance(r, dict)
assert "title" in r
assert "path" in r
assert "domain" in r
def test_fallback_matches_real_content():
"""A query for a real lesson topic must return a relevant hit."""
resp = mcp.handle_search({"query": "release notes", "top": 5})
assert resp["results"], "expected at least one hit for 'release notes'"
top = resp["results"][0]
blob = " ".join(str(v).lower() for v in top.values())
assert "release" in blob or "notes" in blob
def test_fallback_respects_top_limit():
resp = mcp.handle_search({"query": "MCP", "top": 2})
assert len(resp["results"]) <= 2
def test_fallback_empty_query_is_rejected():
resp = mcp.handle_search({"query": "", "top": 5})
assert "error" in resp
def test_fallback_domain_filter():
"""A domain filter narrows the fallback results."""
resp = mcp.handle_search({"query": "MCP", "top": 20, "domain": "core"})
for r in resp["results"]:
assert r.get("domain") == "core"
def test_sag_still_preferred_when_available(monkeypatch):
"""With SAG available, the source must be sag-lite (no fallback)."""
monkeypatch.setattr(mcp, "HAS_SAG", True)
monkeypatch.setattr(mcp, "SAG_DB", Path("/nonexistent/sag.db"))
def fake_sag(db, query, domain=None, top=5):
return [{"id": "x", "title": query}]
monkeypatch.setattr(mcp, "sag_search", fake_sag)
resp = mcp.handle_search({"query": "MCP", "top": 5})
assert resp["source"] == "sag-lite"