Copies source (no node_modules, dist, .tasks, .wiki, __pycache__) for: - projects-meta-mcp v2.25.0 (TypeScript/Node) - wiki-graph v0.3.1 (TypeScript/Node) - interns-mcp v0.3.3 (Python/FastMCP) .gitignore: exclude lib build artefacts (node_modules, dist, .venv, __pycache__, *.pyc) bootstrap.ps1: add MCP build step — npm install+build for TS servers, venv+pip for Python Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
238 lines
6.8 KiB
Python
238 lines
6.8 KiB
Python
"""Tests for grep_audit intern — deterministic, LLM-free."""
|
|
|
|
import json
|
|
|
|
from interns_mcp.interns.base import Intern, InternResponse
|
|
from interns_mcp.interns.grep_audit import GrepAudit
|
|
|
|
|
|
def test_substring_case_sensitive(tmp_path):
|
|
f = tmp_path / "claude.md"
|
|
f.write_text("follow project discipline\nuse superpowers\n", encoding="utf-8")
|
|
|
|
intern = GrepAudit()
|
|
result = intern.run(
|
|
paths=[str(f)],
|
|
patterns=["follow project discipline", "Follow Project Discipline"],
|
|
output="json",
|
|
)
|
|
|
|
assert isinstance(result, InternResponse)
|
|
data = json.loads(result.text)
|
|
matches = data["rows"][0]["matches"]
|
|
assert matches["follow project discipline"] is True
|
|
assert matches["Follow Project Discipline"] is False
|
|
|
|
|
|
def test_substring_case_insensitive(tmp_path):
|
|
f = tmp_path / "claude.md"
|
|
f.write_text("follow project discipline\n", encoding="utf-8")
|
|
|
|
intern = GrepAudit()
|
|
result = intern.run(
|
|
paths=[str(f)],
|
|
patterns=["FOLLOW PROJECT DISCIPLINE"],
|
|
output="json",
|
|
case_sensitive=False,
|
|
)
|
|
|
|
data = json.loads(result.text)
|
|
assert data["rows"][0]["matches"]["FOLLOW PROJECT DISCIPLINE"] is True
|
|
|
|
|
|
def test_regex_named(tmp_path):
|
|
f = tmp_path / "pyproject.toml"
|
|
f.write_text('version = "0.3.0"\n', encoding="utf-8")
|
|
|
|
intern = GrepAudit()
|
|
result = intern.run(
|
|
paths=[str(f)],
|
|
patterns=[{"pattern": r'version\s*=\s*"\d+\.\d+\.\d+"', "name": "version-line", "regex": True}],
|
|
output="json",
|
|
)
|
|
|
|
data = json.loads(result.text)
|
|
assert data["rows"][0]["matches"]["version-line"] is True
|
|
|
|
|
|
def test_dict_substring(tmp_path):
|
|
f = tmp_path / "skill.md"
|
|
f.write_text("trigger string here\n", encoding="utf-8")
|
|
|
|
intern = GrepAudit()
|
|
result = intern.run(
|
|
paths=[str(f)],
|
|
patterns=[{"pattern": "trigger string", "name": "trigger"}],
|
|
output="json",
|
|
)
|
|
|
|
data = json.loads(result.text)
|
|
assert data["rows"][0]["matches"]["trigger"] is True
|
|
|
|
|
|
def test_output_table(tmp_path):
|
|
f1 = tmp_path / "a.md"
|
|
f1.write_text("alpha beta\n", encoding="utf-8")
|
|
f2 = tmp_path / "b.md"
|
|
f2.write_text("gamma\n", encoding="utf-8")
|
|
|
|
intern = GrepAudit()
|
|
result = intern.run(
|
|
paths=[str(f1), str(f2)],
|
|
patterns=["alpha", "gamma"],
|
|
output="table",
|
|
)
|
|
|
|
text = result.text
|
|
assert text.splitlines()[0].startswith("| Path |")
|
|
assert "alpha" in text.splitlines()[0]
|
|
assert "gamma" in text.splitlines()[0]
|
|
# f1 has alpha but not gamma
|
|
f1_line = [l for l in text.splitlines() if str(f1) in l][0]
|
|
assert "✅" in f1_line
|
|
assert "❌" in f1_line
|
|
# f2 has gamma but not alpha
|
|
f2_line = [l for l in text.splitlines() if str(f2) in l][0]
|
|
assert "✅" in f2_line
|
|
assert "❌" in f2_line
|
|
|
|
|
|
def test_output_json(tmp_path):
|
|
f = tmp_path / "a.md"
|
|
f.write_text("alpha\n", encoding="utf-8")
|
|
|
|
intern = GrepAudit()
|
|
result = intern.run(
|
|
paths=[str(f)],
|
|
patterns=["alpha", "missing"],
|
|
output="json",
|
|
)
|
|
|
|
data = json.loads(result.text)
|
|
assert "rows" in data
|
|
assert len(data["rows"]) == 1
|
|
assert data["rows"][0]["path"] == str(f)
|
|
assert data["rows"][0]["matches"]["alpha"] is True
|
|
assert data["rows"][0]["matches"]["missing"] is False
|
|
|
|
|
|
def test_file_not_found_partial_result(tmp_path):
|
|
f_real = tmp_path / "exists.md"
|
|
f_real.write_text("hello\n", encoding="utf-8")
|
|
f_ghost = tmp_path / "ghost.md" # never created
|
|
|
|
intern = GrepAudit()
|
|
result = intern.run(
|
|
paths=[str(f_real), str(f_ghost)],
|
|
patterns=["hello"],
|
|
output="json",
|
|
)
|
|
|
|
data = json.loads(result.text)
|
|
assert len(data["rows"]) == 2 # partial result, no abort
|
|
rows_by_path = {r["path"]: r for r in data["rows"]}
|
|
assert rows_by_path[str(f_real)]["matches"]["hello"] is True
|
|
# Ghost row: match is None (unreadable cell), error field present
|
|
assert rows_by_path[str(f_ghost)]["matches"]["hello"] is None
|
|
assert "error" in rows_by_path[str(f_ghost)]
|
|
|
|
|
|
def test_file_not_found_table_renders_warning(tmp_path):
|
|
f_ghost = tmp_path / "ghost.md" # never created
|
|
|
|
intern = GrepAudit()
|
|
result = intern.run(
|
|
paths=[str(f_ghost)],
|
|
patterns=["x"],
|
|
output="table",
|
|
)
|
|
|
|
assert "⚠️" in result.text
|
|
|
|
|
|
def test_empty_paths():
|
|
intern = GrepAudit()
|
|
result = intern.run(paths=[], patterns=["x"], output="json")
|
|
data = json.loads(result.text)
|
|
assert data["rows"] == []
|
|
assert result.usage["files_scanned"] == 0
|
|
assert result.usage["matches_total"] == 0
|
|
|
|
|
|
def test_empty_patterns(tmp_path):
|
|
f = tmp_path / "a.md"
|
|
f.write_text("alpha\n", encoding="utf-8")
|
|
|
|
intern = GrepAudit()
|
|
result = intern.run(paths=[str(f)], patterns=[], output="json")
|
|
data = json.loads(result.text)
|
|
assert len(data["rows"]) == 1
|
|
assert data["rows"][0]["matches"] == {}
|
|
assert result.usage["patterns_evaluated"] == 0
|
|
|
|
|
|
def test_unicode_content_utf8(tmp_path):
|
|
f = tmp_path / "ru.md"
|
|
f.write_text("русский текст\n", encoding="utf-8")
|
|
|
|
intern = GrepAudit()
|
|
result = intern.run(
|
|
paths=[str(f)],
|
|
patterns=["русский"],
|
|
output="json",
|
|
)
|
|
|
|
data = json.loads(result.text)
|
|
assert data["rows"][0]["matches"]["русский"] is True
|
|
|
|
|
|
def test_unicode_content_binary_garbage_does_not_crash(tmp_path):
|
|
f = tmp_path / "binary.bin"
|
|
f.write_bytes(b"\xff\xfe\x00\x01\x02 hello \xff")
|
|
|
|
intern = GrepAudit()
|
|
# errors="replace" should let us still grep for "hello"
|
|
result = intern.run(
|
|
paths=[str(f)],
|
|
patterns=["hello"],
|
|
output="json",
|
|
)
|
|
|
|
data = json.loads(result.text)
|
|
assert data["rows"][0]["matches"]["hello"] is True
|
|
|
|
|
|
def test_usage_counts(tmp_path):
|
|
f1 = tmp_path / "a.md"
|
|
f1.write_text("alpha\n", encoding="utf-8")
|
|
f2 = tmp_path / "b.md"
|
|
f2.write_text("alpha beta\n", encoding="utf-8")
|
|
|
|
intern = GrepAudit()
|
|
result = intern.run(
|
|
paths=[str(f1), str(f2)],
|
|
patterns=["alpha", "beta"],
|
|
output="json",
|
|
)
|
|
|
|
assert result.usage["files_scanned"] == 2
|
|
assert result.usage["patterns_evaluated"] == 2
|
|
# f1: alpha ✓, beta ✗ → 1
|
|
# f2: alpha ✓, beta ✓ → 2
|
|
assert result.usage["matches_total"] == 3
|
|
|
|
|
|
def test_endpoint_null_no_client_no_crash():
|
|
"""LLM-free intern must not require client wiring."""
|
|
intern = GrepAudit(config={"endpoint": None})
|
|
assert intern.client is None
|
|
assert intern.endpoint is None
|
|
|
|
|
|
def test_base_intern_accepts_endpoint_null_config():
|
|
"""Base class survives config with explicit endpoint=null (no LLM init triggered)."""
|
|
intern = Intern(config={"endpoint": None, "model": None})
|
|
assert intern.endpoint is None
|
|
assert intern.model is None
|
|
assert intern.client is None
|