"""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)