"""TDD: symbol anchor parser — same-repo, cross-repo, and ref-pinned anchors. Covers musehub.services.symbol_anchor: parse_symbol_anchor(raw: str) -> SymbolAnchorRef Anchor grammar -------------- anchor := cross_repo_anchor | same_repo_anchor cross_anchor := owner '/' repo '::' file_path ('::' symbol)? ('@' ref)? same_anchor := file_path ('::' symbol)? ('@' ref)? owner/repo — exactly one '/', no dots, no sub-slashes file_path — any path (may contain '/', must include extension or multiple path components distinguishing it from an owner/repo slug) symbol — function/class name, may contain dots (e.g. 'Cls.method') ref — branch name OR 'sha256:<64hex>' blob_url() contract ------------------- same-repo anchor → /{default_owner}/{default_repo}/blob/{ref}/{file}#{S:sym} cross-repo anchor → /{owner}/{repo}/blob/{ref}/{file}#{S:sym} ref defaults to 'main' when not specified Unit tests (pure function, no DB): test_same_repo_file_only test_same_repo_file_and_symbol test_same_repo_with_branch_ref test_same_repo_with_commit_ref test_cross_repo_file_only test_cross_repo_file_and_symbol test_cross_repo_with_branch_ref test_cross_repo_with_commit_ref test_cross_repo_deeply_nested_file test_symbol_with_dots_preserved test_file_with_extension_not_mistaken_for_owner_repo test_deeply_nested_path_not_mistaken_for_owner_repo test_is_cross_repo_flag blob_url tests: test_blob_url_same_repo_file_only test_blob_url_same_repo_with_symbol test_blob_url_cross_repo_with_symbol test_blob_url_uses_explicit_ref test_blob_url_cross_repo_uses_own_ref test_blob_url_default_ref_is_main display_label tests: test_display_label_same_repo test_display_label_cross_repo_shows_owner_repo_prefix test_display_label_cross_repo_with_ref """ from __future__ import annotations import pytest from musehub.services.symbol_anchor import SymbolAnchorRef, parse_symbol_anchor # --------------------------------------------------------------------------- # Helpers # --------------------------------------------------------------------------- BASE = "https://staging.musehub.ai" OWN_OWNER = "gabriel" OWN_REPO = "musehub" def _url(raw: str, ref: str = "main") -> str: return parse_symbol_anchor(raw).blob_url(BASE, OWN_OWNER, OWN_REPO, default_ref=ref) # --------------------------------------------------------------------------- # Parsing — same-repo anchors # --------------------------------------------------------------------------- def test_same_repo_file_only() -> None: a = parse_symbol_anchor("musehub/services/billing.py") assert a.owner is None assert a.repo is None assert a.file_path == "musehub/services/billing.py" assert a.symbol_name is None assert a.ref is None def test_same_repo_file_and_symbol() -> None: a = parse_symbol_anchor("musehub/services/billing.py::compute_total") assert a.owner is None assert a.file_path == "musehub/services/billing.py" assert a.symbol_name == "compute_total" assert a.ref is None def test_same_repo_with_branch_ref() -> None: a = parse_symbol_anchor("musehub/services/billing.py::compute_total@dev") assert a.owner is None assert a.file_path == "musehub/services/billing.py" assert a.symbol_name == "compute_total" assert a.ref == "dev" def test_same_repo_with_commit_ref() -> None: ref = "sha256:fa0025329d55487de25035b1122b17517bf88e234f5a8f697111087fc785e81c" a = parse_symbol_anchor(f"muse/cli/commands/bridge.py::GitExporter._has_shebang@{ref}") assert a.owner is None assert a.file_path == "muse/cli/commands/bridge.py" assert a.symbol_name == "GitExporter._has_shebang" assert a.ref == ref def test_same_repo_file_only_with_ref() -> None: a = parse_symbol_anchor("src/main.py@feat/my-branch") assert a.file_path == "src/main.py" assert a.symbol_name is None assert a.ref == "feat/my-branch" # --------------------------------------------------------------------------- # Parsing — cross-repo anchors # --------------------------------------------------------------------------- def test_cross_repo_file_only() -> None: a = parse_symbol_anchor("gabriel/muse::muse/core/snapshot.py") assert a.owner == "gabriel" assert a.repo == "muse" assert a.file_path == "muse/core/snapshot.py" assert a.symbol_name is None assert a.ref is None def test_cross_repo_file_and_symbol() -> None: a = parse_symbol_anchor("gabriel/muse::muse/cli/commands/bridge.py::GitExporter.fix_file_modes") assert a.owner == "gabriel" assert a.repo == "muse" assert a.file_path == "muse/cli/commands/bridge.py" assert a.symbol_name == "GitExporter.fix_file_modes" assert a.ref is None def test_cross_repo_with_branch_ref() -> None: a = parse_symbol_anchor("gabriel/muse::muse/cli/commands/bridge.py::GitExporter._has_shebang@dev") assert a.owner == "gabriel" assert a.repo == "muse" assert a.file_path == "muse/cli/commands/bridge.py" assert a.symbol_name == "GitExporter._has_shebang" assert a.ref == "dev" def test_cross_repo_with_commit_ref() -> None: sha = "sha256:fa0025329d55487de25035b1122b17517bf88e234f5a8f697111087fc785e81c" a = parse_symbol_anchor(f"gabriel/muse::muse/cli/commands/bridge.py::GitExporter.fix_file_modes@{sha}") assert a.owner == "gabriel" assert a.ref == sha def test_cross_repo_deeply_nested_file() -> None: a = parse_symbol_anchor("acme/infra::deploy/paperclip/scripts/install.sh::main") assert a.owner == "acme" assert a.repo == "infra" assert a.file_path == "deploy/paperclip/scripts/install.sh" assert a.symbol_name == "main" # --------------------------------------------------------------------------- # Disambiguation — file paths must not be confused with owner/repo slugs # --------------------------------------------------------------------------- def test_file_with_extension_not_mistaken_for_owner_repo() -> None: """'src/billing.py' has a dot in the second segment → same-repo file.""" a = parse_symbol_anchor("src/billing.py::compute_total") assert a.owner is None assert a.file_path == "src/billing.py" def test_deeply_nested_path_not_mistaken_for_owner_repo() -> None: """'muse/cli/commands/bridge.py' has multiple slashes → same-repo file.""" a = parse_symbol_anchor("muse/cli/commands/bridge.py::GitExporter.fix_file_modes") assert a.owner is None assert a.file_path == "muse/cli/commands/bridge.py" def test_single_segment_file_is_same_repo() -> None: a = parse_symbol_anchor("README.md") assert a.owner is None assert a.file_path == "README.md" def test_symbol_with_dots_preserved() -> None: """Method names like 'GitExporter.fix_file_modes' survive the split.""" a = parse_symbol_anchor("gabriel/muse::muse/cli/commands/bridge.py::GitExporter.fix_file_modes") assert a.symbol_name == "GitExporter.fix_file_modes" # --------------------------------------------------------------------------- # is_cross_repo flag # --------------------------------------------------------------------------- def test_is_cross_repo_true_for_cross_repo_anchor() -> None: a = parse_symbol_anchor("gabriel/muse::muse/cli/commands/bridge.py::GitExporter.fix_file_modes") assert a.is_cross_repo is True def test_is_cross_repo_false_for_same_repo_anchor() -> None: a = parse_symbol_anchor("musehub/services/billing.py::compute_total") assert a.is_cross_repo is False # --------------------------------------------------------------------------- # blob_url() # --------------------------------------------------------------------------- def test_blob_url_same_repo_file_only() -> None: url = _url("src/main.py") assert url == f"{BASE}/{OWN_OWNER}/{OWN_REPO}/blob/main/src/main.py" def test_blob_url_same_repo_with_symbol() -> None: url = _url("musehub/services/billing.py::compute_total") assert url == f"{BASE}/{OWN_OWNER}/{OWN_REPO}/blob/main/musehub/services/billing.py#S:compute_total" def test_blob_url_cross_repo_with_symbol() -> None: url = _url("gabriel/muse::muse/cli/commands/bridge.py::GitExporter.fix_file_modes") assert url == f"{BASE}/gabriel/muse/blob/main/muse/cli/commands/bridge.py#S:GitExporter.fix_file_modes" def test_blob_url_uses_explicit_ref_same_repo() -> None: url = _url("src/main.py::Fn@dev") assert url == f"{BASE}/{OWN_OWNER}/{OWN_REPO}/blob/dev/src/main.py#S:Fn" def test_blob_url_cross_repo_uses_own_ref() -> None: url = _url("gabriel/muse::muse/cli/commands/bridge.py::GitExporter._has_shebang@dev") assert url == f"{BASE}/gabriel/muse/blob/dev/muse/cli/commands/bridge.py#S:GitExporter._has_shebang" def test_blob_url_default_ref_is_main() -> None: url = parse_symbol_anchor("src/x.py::Fn").blob_url(BASE, OWN_OWNER, OWN_REPO) assert "/blob/main/" in url def test_blob_url_commit_ref_in_url() -> None: sha = "sha256:fa0025329d55487de25035b1122b17517bf88e234f5a8f697111087fc785e81c" url = _url(f"gabriel/muse::muse/cli/commands/bridge.py::GitExporter.fix_file_modes@{sha}") assert f"/blob/{sha}/" in url # --------------------------------------------------------------------------- # display_label() # --------------------------------------------------------------------------- def test_display_label_same_repo_file_and_symbol() -> None: a = parse_symbol_anchor("musehub/services/billing.py::compute_total") assert a.display_label() == "musehub/services/billing.py::compute_total" def test_display_label_same_repo_file_only() -> None: a = parse_symbol_anchor("src/main.py") assert a.display_label() == "src/main.py" def test_display_label_cross_repo_shows_prefix() -> None: a = parse_symbol_anchor("gabriel/muse::muse/cli/commands/bridge.py::GitExporter.fix_file_modes") label = a.display_label() assert label.startswith("gabriel/muse::") assert "GitExporter.fix_file_modes" in label def test_display_label_cross_repo_with_ref() -> None: a = parse_symbol_anchor("gabriel/muse::muse/cli/commands/bridge.py::GitExporter._has_shebang@dev") label = a.display_label() assert "@dev" in label