File size: 4,395 Bytes
71d239c
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
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
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
"""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)