Joshua Sundance Bailey
loosecanvas: local AI thought-mapping canvas with a trust-tagged knowledge graph
6d1438c | """Red/green tests for M06 — Import Resolver (``resolve_imports``). | |
| Assumed ``module_map`` shape (the real shape M04 ``walk_repo`` emits): a mapping of | |
| ``{relative_posix_path -> qualified_dotted_module_name}``, e.g. | |
| ``{"pkg/sub/mod_b.py": "pkg.sub.mod_b", "pkg/__init__.py": "pkg"}``. | |
| Target/source node ids are reconstructed in the M05 module-node format | |
| ``{relative_path}::module::{last_dotted_segment}`` (M05 uses the file stem, or the package | |
| name for ``__init__.py``, which is always the final segment of the dotted module name). | |
| """ | |
| from __future__ import annotations | |
| from loosecanvas.contracts import Edge, RawImport | |
| from loosecanvas.extractors.import_resolver import resolve_imports | |
| # A small in-repo package tree: rel_path -> qualified dotted module name (M04 module_map). | |
| MODULE_MAP: dict[str, str] = { | |
| "pkg/__init__.py": "pkg", | |
| "pkg/mod_a.py": "pkg.mod_a", | |
| "pkg/sub/__init__.py": "pkg.sub", | |
| "pkg/sub/mod_b.py": "pkg.sub.mod_b", | |
| "pkg/sub/mod_c.py": "pkg.sub.mod_c", | |
| } | |
| # Module node ids as M05 would have minted them. | |
| NODE_MOD_A = "pkg/mod_a.py::module::mod_a" | |
| NODE_MOD_B = "pkg/sub/mod_b.py::module::mod_b" | |
| NODE_MOD_C = "pkg/sub/mod_c.py::module::mod_c" | |
| def _only(edges: list[Edge]) -> Edge: | |
| assert len(edges) == 1, f"expected exactly one edge, got {edges}" | |
| return edges[0] | |
| def test_absolute_import_resolves_intra_repo() -> None: | |
| """``import pkg.mod_a`` from mod_b -> an ``imports`` edge between the two module nodes.""" | |
| raw = [ | |
| RawImport( | |
| module="pkg.mod_a", names=[], level=0, current_file="pkg/sub/mod_b.py" | |
| ) | |
| ] | |
| edge = _only(resolve_imports(raw, MODULE_MAP, "pkg/sub/mod_b.py")) | |
| assert edge.kind == "imports" | |
| assert edge.source == NODE_MOD_B | |
| assert edge.target == NODE_MOD_A | |
| def test_absolute_import_goes_external() -> None: | |
| """``import os`` -> an ``external_import`` edge to ``external::os``.""" | |
| raw = [RawImport(module="os", names=[], level=0, current_file="pkg/mod_a.py")] | |
| edge = _only(resolve_imports(raw, MODULE_MAP, "pkg/mod_a.py")) | |
| assert edge.kind == "external_import" | |
| assert edge.target == "external::os" | |
| def test_single_level_relative_resolves_sibling() -> None: | |
| """``from .mod_c import thing`` (level=1) in mod_b -> ``imports_from`` edge to mod_c.""" | |
| raw = [ | |
| RawImport( | |
| module="mod_c", names=["thing"], level=1, current_file="pkg/sub/mod_b.py" | |
| ) | |
| ] | |
| edge = _only(resolve_imports(raw, MODULE_MAP, "pkg/sub/mod_b.py")) | |
| assert edge.kind == "imports_from" | |
| assert edge.source == NODE_MOD_B | |
| assert edge.target == NODE_MOD_C | |
| def test_multi_level_relative_resolves_parent_package() -> None: | |
| """``from ..mod_a import x`` (level=2) in mod_b -> ``imports_from`` edge to mod_a.""" | |
| raw = [ | |
| RawImport(module="mod_a", names=["x"], level=2, current_file="pkg/sub/mod_b.py") | |
| ] | |
| edge = _only(resolve_imports(raw, MODULE_MAP, "pkg/sub/mod_b.py")) | |
| assert edge.kind == "imports_from" | |
| assert edge.source == NODE_MOD_B | |
| assert edge.target == NODE_MOD_A | |
| def test_star_import_handled_without_error() -> None: | |
| """``from pkg.mod_a import *`` -> a single ``imports_from`` edge, no exception.""" | |
| raw = [ | |
| RawImport( | |
| module="pkg.mod_a", names=["*"], level=0, current_file="pkg/sub/mod_b.py" | |
| ) | |
| ] | |
| edge = _only(resolve_imports(raw, MODULE_MAP, "pkg/sub/mod_b.py")) | |
| assert edge.kind == "imports_from" | |
| assert edge.target == NODE_MOD_A | |
| def test_unresolved_module_becomes_external_dep() -> None: | |
| """``from somelib.foo import bar`` -> ``external_import`` edge to ``external::somelib.foo``.""" | |
| raw = [ | |
| RawImport( | |
| module="somelib.foo", names=["bar"], level=0, current_file="pkg/mod_a.py" | |
| ) | |
| ] | |
| edge = _only(resolve_imports(raw, MODULE_MAP, "pkg/mod_a.py")) | |
| assert edge.kind == "external_import" | |
| assert edge.target == "external::somelib.foo" | |
| def test_no_dangling_intra_repo_edge_endpoints() -> None: | |
| """Every endpoint of an intra-repo edge is a real module node id from ``module_map``.""" | |
| known_node_ids = { | |
| f"{rel}::module::{dotted.rsplit('.', 1)[-1]}" | |
| for rel, dotted in MODULE_MAP.items() | |
| } | |
| raw = [ | |
| RawImport( | |
| module="pkg.mod_a", names=[], level=0, current_file="pkg/sub/mod_b.py" | |
| ), | |
| RawImport( | |
| module="mod_c", names=["thing"], level=1, current_file="pkg/sub/mod_b.py" | |
| ), | |
| RawImport( | |
| module="mod_a", names=["x"], level=2, current_file="pkg/sub/mod_b.py" | |
| ), | |
| RawImport(module="os", names=[], level=0, current_file="pkg/sub/mod_b.py"), | |
| ] | |
| edges = resolve_imports(raw, MODULE_MAP, "pkg/sub/mod_b.py") | |
| for edge in edges: | |
| if edge.kind == "external_import": | |
| continue | |
| assert edge.source in known_node_ids, f"dangling source: {edge.source}" | |
| assert edge.target in known_node_ids, f"dangling target: {edge.target}" | |