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