Ctrl K
ComunidadGuías…

tests/test_build_merge_ghost_fold_4955.py

Rama v8 · réplica de lectura del repositorio original.

Archivo original ↗Preguntar a KiBot
"""#4955: build_merge's #479 guard and to_json's write guard must not count
the intentional ghost folds of build_from_json (#1145, #3344, #3705) as drops.
"""
import json

from graphify.build import build_merge
from graphify.export import to_json

EMPTY = {"nodes": [], "edges": [], "hyperedges": []}


def _n(id_, label, sf, loc=None, **kw):
    return {"id": id_, "label": label, "file_type": "code",
            "source_file": sf, "source_location": loc, **kw}


def _save(tmp_path, nodes, links=()):
    path = tmp_path / "graph.json"
    path.write_text(json.dumps({"directed": False, "multigraph": False, "graph": {},
                                "nodes": nodes, "links": list(links)}), encoding="utf-8")
    return path


def _merge(tmp_path, disk, chunk_nodes=(), dedup=False, links=(), **kw):
    path = _save(tmp_path, disk, links)
    chunk = {**EMPTY, "nodes": list(chunk_nodes)}
    return build_merge([chunk], graph_path=str(path), dedup=dedup, **kw)


# --- controls: the guard keeps protecting everything it protected before ---

def test_clean_graph_is_untouched(tmp_path):
    disk = [_n("a", "A", "a.py", "L1"), _n("b", "B", "b.py", "L2")]
    assert set(_merge(tmp_path, disk).nodes) == {"a", "b"}


def test_semantic_duplicates_are_not_folded_when_dedup_off(tmp_path):
    disk = [_n("s1", "Idea", "docs/a.md"), _n("s2", "Idea", "docs/a.md")]
    assert len(_merge(tmp_path, disk).nodes) == 2


def test_ghost_with_its_own_rationale_is_kept(tmp_path):  # #4896
    G = _merge(tmp_path, [
        _n("src_client", "client.py", "src/client.py", "L1"),
        _n("plan_client", "src/client.py", "docs/plan.md", rationale="why"),
    ])
    assert len(G.nodes) == 2


def test_ambiguous_file_label_is_not_folded(tmp_path):
    G = _merge(tmp_path, [
        _n("a", "index.ts", "a/index.ts", "L1"),
        _n("b", "index.ts", "b/index.ts", "L1"),
        _n("m", "index.ts", "docs/m.md"),
    ])
    assert len(G.nodes) == 3


def test_prune_sources_still_removes(tmp_path):
    disk = [_n("a", "A", "a.py", "L1"), _n("b", "B", "b.py", "L2")]
    assert set(_merge(tmp_path, disk, prune_sources=["b.py"]).nodes) == {"a"}


# --- to_json: a ghost fold is not a lost saved node ---

def test_to_json_accepts_a_method_ghost_fold(tmp_path):
    # #3705: Pass 2's method branch folds `run` into `.run()`. to_json's own
    # shrink guard used to read the saved `run` as a lost semantic node.
    path = _save(tmp_path, [_n("c_run", ".run()", "src/c.py", "L3"),
                            _n("run", "run", "src/c.py")])
    G = build_merge([dict(EMPTY)], graph_path=str(path), dedup=True)
    assert set(G.nodes) == {"c_run"}
    assert to_json(G, {}, str(path)) is True
    assert [n["id"] for n in json.loads(path.read_text())["nodes"]] == ["c_run"]


def test_ghost_fold_map_is_not_written_to_graph_json(tmp_path):
    path = _save(tmp_path, [_n("src_client", "client.py", "src/client.py", "L1"),
                            _n("client_py", "client.py", "src/client.py")])
    G = build_merge([dict(EMPTY)], graph_path=str(path), dedup=True)
    assert to_json(G, {}, str(path)) is True
    assert "_dedup_remap" not in json.loads(path.read_text()).get("graph", {})


# --- build_merge(dedup=False): intentional folds are not unexplained drops ---

def test_same_file_same_label_twin_is_not_an_unexplained_drop(tmp_path):
    # #1145, the issue's first case
    G = _merge(tmp_path, [_n("src_client", "client.py", "src/client.py", "L1"),
                          _n("client_py", "client.py", "src/client.py")])
    assert set(G.nodes) == {"src_client"}


def test_document_naming_a_file_is_not_an_unexplained_drop(tmp_path):
    # #3344, the issue's second case
    G = _merge(tmp_path, [_n("src_client", "client.py", "src/client.py", "L1"),
                          _n("plan_client", "src/client.py", "docs/plan.md")])
    assert set(G.nodes) == {"src_client"}


def test_directory_qualified_file_label_folds(tmp_path):
    G = _merge(tmp_path, [
        _n("app_index", "index.ts", "apps/customer-app/index.ts", "L1"),
        _n("memo_idx", "customer-app/index.ts", "docs/memo.md"),
    ])
    assert set(G.nodes) == {"app_index"}


def test_method_ghost_is_not_an_unexplained_drop(tmp_path):
    # #3705: the branch of Pass 2 that has no dedup check of its own
    G = _merge(tmp_path, [_n("c_run", ".run()", "src/c.py", "L3"),
                          _n("run", "run", "src/c.py")])
    assert set(G.nodes) == {"c_run"}


# --- the surrounding paths the fix must also hold ---

def test_edge_into_the_ghost_is_repointed_to_the_survivor(tmp_path):
    G = _merge(
        tmp_path,
        [_n("src_client", "client.py", "src/client.py", "L1"),
         _n("client_py", "client.py", "src/client.py"),
         _n("other", "Other", "src/o.py", "L1")],
        links=[{"source": "other", "target": "client_py",
                "relation": "references", "confidence": "INFERRED"}],
    )
    assert G.has_edge("other", "src_client")


def test_incremental_update_folds_new_ast_node_into_saved_semantic_twin(tmp_path):
    # An ordinary `update`: the file is re-extracted and a saved semantic twin
    # of it exists. This raised before, not only for an empty chunk.
    G = _merge(
        tmp_path,
        [_n("client_py", "client.py", "src/client.py")],
        chunk_nodes=[_n("src_client", "client.py", "src/client.py", "L1", _origin="ast")],
    )
    assert set(G.nodes) == {"src_client"}


def test_merge_then_write_round_trips_each_fold(tmp_path):
    # build_merge(dedup=False) followed by to_json is the persist path.
    cases = {
        "1145": [_n("src_client", "client.py", "src/client.py", "L1"),
                 _n("client_py", "client.py", "src/client.py")],
        "3344": [_n("src_client", "client.py", "src/client.py", "L1"),
                 _n("plan_client", "src/client.py", "docs/plan.md")],
        "3705": [_n("c_run", ".run()", "src/c.py", "L3"),
                 _n("run", "run", "src/c.py")],
    }
    for name, disk in cases.items():
        sub = tmp_path / name
        sub.mkdir()
        path = _save(sub, disk)
        G = build_merge([dict(EMPTY)], graph_path=str(path), dedup=False)
        assert to_json(G, {}, str(path)) is True, name
        assert len(json.loads(path.read_text())["nodes"]) == 1, name