|
1 | 1 | from pathlib import Path |
| 2 | + |
2 | 3 | from ccarch.resolver import resolve |
3 | 4 |
|
4 | 5 | REPO = Path(__file__).resolve().parents[3] |
@@ -26,5 +27,55 @@ def test_genuinely_missing_is_missing(): |
26 | 27 | assert r.status == "missing" |
27 | 28 |
|
28 | 29 | def test_never_raises_on_junk(): |
| 30 | + # Every entry below is parser garbage or a fabricated/escaping path — |
| 31 | + # none of them names a real, in-repo file or dir. resolve() must not |
| 32 | + # raise, and must not mislabel any of them as "ok"/"moved". |
29 | 33 | for junk in ["", " ", "///", "src/foo.ts", "../../etc/passwd"]: |
30 | | - assert resolve(REPO, junk).status in {"ok", "moved", "missing"} |
| 34 | + assert resolve(REPO, junk).status == "missing" |
| 35 | + |
| 36 | + |
| 37 | +def test_degenerate_slash_and_dot_input_is_missing(): |
| 38 | + # q = raw.rstrip("/") can collapse to "", and repo_root / "" is just |
| 39 | + # repo_root — which must never be reported as a legitimate directory hit. |
| 40 | + for junk in ["", " ", "///", ".", "./"]: |
| 41 | + r = resolve(REPO, junk) |
| 42 | + assert r.status == "missing", f"{junk!r} -> {r.status}" |
| 43 | + |
| 44 | + |
| 45 | +def test_symlink_escaping_repo_is_missing(tmp_path): |
| 46 | + # Reviewed/confirmed behavior: a symlink inside the repo pointing outside |
| 47 | + # it must still resolve to missing. Guard against regressing this while |
| 48 | + # fixing the symlink-loop RuntimeError below. Built under tmp_path so we |
| 49 | + # never write symlinks into the real repo tree. |
| 50 | + outside = tmp_path / "outside" |
| 51 | + outside.mkdir() |
| 52 | + (outside / "passwd").write_text("secret") |
| 53 | + |
| 54 | + repo = tmp_path / "repo" |
| 55 | + (repo / "src").mkdir(parents=True) |
| 56 | + (repo / "src" / "escaped").symlink_to(outside, target_is_directory=True) |
| 57 | + |
| 58 | + r = resolve(repo, "src/escaped/passwd") |
| 59 | + assert r.status == "missing" |
| 60 | + |
| 61 | + |
| 62 | +def test_symlink_loop_does_not_raise(tmp_path): |
| 63 | + # Path.resolve() raises RuntimeError("Symlink loop from ...") on a |
| 64 | + # circular symlink — not OSError/ValueError. resolve() must catch it |
| 65 | + # and report missing, never propagate. |
| 66 | + (tmp_path / "docs").mkdir() |
| 67 | + src = tmp_path / "src" |
| 68 | + src.mkdir() |
| 69 | + (src / "loopa").symlink_to(src / "loopb") |
| 70 | + (src / "loopb").symlink_to(src / "loopa") |
| 71 | + |
| 72 | + r = resolve(tmp_path, "src/loopa/file.ts") |
| 73 | + assert r.status == "missing" |
| 74 | + |
| 75 | + |
| 76 | +def test_broken_symlink_does_not_raise(tmp_path): |
| 77 | + (tmp_path / "src").mkdir() |
| 78 | + (tmp_path / "src" / "ghost").symlink_to(tmp_path / "src" / "nonexistent") |
| 79 | + |
| 80 | + r = resolve(tmp_path, "src/ghost") |
| 81 | + assert r.status == "missing" |
0 commit comments