storycode / tests /test_analyzer.py
Claude
Scaffold StoryCode: grounded code-story explainer for vibe coders
71d239c
Raw
History Blame Contribute Delete
4.4 kB
"""No-GPU correctness tests for the static-analysis engine.
These run with pydantic (+ tree-sitter if present) only — no model, no network.
They lock down the *facts* every story is grounded in: import edges, fan-in/out,
roles, entry points, and the safe-to-edit verdicts. If these pass, the narration
layer has a trustworthy backbone.
Run: python -m pytest tests/ -q (or) python tests/test_analyzer.py
"""
from __future__ import annotations
import os
import sys
sys.path.insert(0, os.path.dirname(os.path.dirname(os.path.abspath(__file__))))
import config # noqa: E402
from analyzer import analyze_project # noqa: E402
from analyzer import deps as deps_mod # noqa: E402
from ingest import from_folder, redact_secrets # noqa: E402
SAMPLE = os.path.join(os.path.dirname(os.path.abspath(__file__)),
"..", "scripts", "sample_project")
def _model():
return analyze_project(from_folder(SAMPLE, name="doc-qa"))
def test_all_files_found():
m = _model()
paths = {f.path for f in m.files}
expected = {"app.py", "config.py", "embed.py", "ingest.py",
"retrieve.py", "generate.py"}
assert expected <= paths, f"missing files: {expected - paths}"
def test_edges_resolved():
m = _model()
edges = {f.path: set(f.depends_on) for f in m.files}
assert edges["embed.py"] == {"config.py"}
assert edges["ingest.py"] == {"config.py", "embed.py"}
assert edges["retrieve.py"] == {"config.py", "embed.py", "ingest.py"}
assert edges["generate.py"] == {"config.py", "retrieve.py"}
assert edges["app.py"] == {"generate.py", "ingest.py"}
assert edges["config.py"] == set() # leaf, depends on nothing internal
def test_fan_in():
m = _model()
fan_in = {f.path: f.fan_in for f in m.files}
assert fan_in["config.py"] == 4 # embed, ingest, retrieve, generate
assert fan_in["embed.py"] == 2 # ingest, retrieve
assert fan_in["ingest.py"] == 2 # retrieve, app
assert fan_in["retrieve.py"] == 1 # generate
assert fan_in["generate.py"] == 1 # app
assert fan_in["app.py"] == 0
def test_entry_point():
m = _model()
assert m.entry_points == ["app.py"]
assert m.by_path("app.py").is_entry is True
def test_roles():
m = _model()
role = {f.path: f.role for f in m.files}
assert role["app.py"] == "entry"
assert role["config.py"] == "config"
assert role["embed.py"] == "data" # 'embed' is a data hint
assert role["ingest.py"] == "data" # 'ingest' is a data hint
def test_safe_to_edit():
m = _model()
safety = {f.path: f.safety for f in m.files}
assert safety["app.py"] == config.DANGER # entry point
assert safety["config.py"] == config.DANGER # config + high fan-in
assert safety["embed.py"] == config.CAREFUL # fan-in 2
assert safety["retrieve.py"] == config.CAREFUL # fan-in 1
# every file carries a plain-English reason
assert all(f.safety_reason for f in m.files)
def test_symbols_extracted():
m = _model()
embed = m.by_path("embed.py")
names = {s.name for s in embed.symbols}
assert {"embed_text", "embed_many"} <= names
def test_dependencies_explained():
m = _model()
by_name = {d.name.lower(): d for d in m.deps}
assert "openai" in by_name
assert "chromadb" in by_name
assert by_name["chromadb"].plain # has a plain-English blurb
assert "memory" in by_name["chromadb"].plain.lower()
def test_secret_redaction():
clean, hits = redact_secrets('API_KEY = "sk-abc123def456ghi789jkl012"')
assert "sk-abc123" not in clean
assert hits # at least one rule fired
def test_deps_known_map_direct():
out = deps_mod.parse_requirements("gradio==4.44\nopenai\n# comment\n")
names = {d.name for d in out}
assert names == {"gradio", "openai"}
if __name__ == "__main__":
fns = [v for k, v in sorted(globals().items()) if k.startswith("test_") and callable(v)]
passed = 0
for fn in fns:
try:
fn()
except AssertionError as exc:
print(f"FAIL {fn.__name__}: {exc}")
except Exception as exc: # noqa: BLE001
print(f"ERROR {fn.__name__}: {type(exc).__name__}: {exc}")
else:
print(f"ok {fn.__name__}")
passed += 1
print(f"\n{passed}/{len(fns)} passed")
sys.exit(0 if passed == len(fns) else 1)