from pathlib import Path import pytest from mjr_am_backend import custom_roots as cr def test_normalize_and_canonical_helpers(monkeypatch, tmp_path: Path): monkeypatch.setenv("MJR_ALLOW_SYMLINKS", "1") d = tmp_path / "a" d.mkdir() n = cr._normalize_dir_path(str(d)) assert n is not None assert cr._canonical_path_key(str(d)).lower().endswith("a") assert cr._path_is_relative_to(d, tmp_path) def test_normalize_dir_path_respects_symlink_policy(monkeypatch, tmp_path: Path): target = tmp_path / "target" target.mkdir() symlink = tmp_path / "link" try: symlink.symlink_to(target, target_is_directory=True) except (OSError, NotImplementedError): pytest.skip("Symlinks are not supported in this environment") monkeypatch.delenv("MJR_ALLOW_SYMLINKS", raising=False) assert cr._normalize_dir_path(str(symlink)) is None monkeypatch.setenv("MJR_ALLOW_SYMLINKS", "1") normalized = cr._normalize_dir_path(str(symlink)) assert normalized is not None assert normalized == target.resolve(strict=True) def test_read_write_store_and_normalize_row(monkeypatch, tmp_path: Path): store = tmp_path / "custom_roots.json" monkeypatch.setattr(cr, "_STORE_PATH", store) w = cr._write_store({"version": 1, "roots": [{"id": "r1", "path": str(tmp_path)}]}) assert w.ok data = cr._read_store() assert isinstance(data.get("roots"), list) row = cr._normalize_custom_root_row({"id": "r1", "path": str(tmp_path)}) assert row and row["id"] == "r1" def test_add_list_remove_and_resolve_custom_root(monkeypatch, tmp_path: Path): store = tmp_path / "custom_roots.json" monkeypatch.setattr(cr, "_STORE_PATH", store) monkeypatch.setattr(cr, "OUTPUT_ROOT", str(tmp_path / "output")) out = tmp_path / "out_root" out.mkdir() monkeypatch.setattr(cr, "_resolve_builtin_roots", lambda: (None, None)) add = cr.add_custom_root(str(out), label="X") assert add.ok rid = add.data["id"] listed = cr.list_custom_roots() assert listed.ok and listed.data resolved = cr.resolve_custom_root(rid) assert resolved.ok and isinstance(resolved.data, Path) rem = cr.remove_custom_root(rid) assert rem.ok miss = cr.remove_custom_root(rid) assert miss.code == "NOT_FOUND" def test_overlap_and_invalid_paths(monkeypatch, tmp_path: Path): store = tmp_path / "custom_roots.json" monkeypatch.setattr(cr, "_STORE_PATH", store) root = tmp_path / "root" sub = root / "sub" sub.mkdir(parents=True) monkeypatch.setattr(cr, "_resolve_builtin_roots", lambda: (root, None)) ov = cr.add_custom_root(str(sub)) assert ov.code == "OVERLAP" inv = cr.add_custom_root(str(tmp_path / "missing")) assert inv.code in {"DIR_NOT_FOUND", "INVALID_INPUT"} def test_custom_roots_user_scoped_store(monkeypatch, tmp_path: Path): monkeypatch.setattr(cr, "_STORE_PATH", tmp_path / "custom_roots.json") monkeypatch.setattr(cr, "_USER_STORES_DIR", tmp_path / "users") monkeypatch.setattr(cr, "_resolve_builtin_roots", lambda: (None, None)) u1_root = tmp_path / "u1" u2_root = tmp_path / "u2" u1_root.mkdir() u2_root.mkdir() add1 = cr.add_custom_root(str(u1_root), user_id="user-one") add2 = cr.add_custom_root(str(u2_root), user_id="user-two") assert add1.ok and add2.ok listed_u1 = cr.list_custom_roots(user_id="user-one").data or [] listed_u2 = cr.list_custom_roots(user_id="user-two").data or [] assert len(listed_u1) == 1 and listed_u1[0]["id"] == add1.data["id"] assert len(listed_u2) == 1 and listed_u2[0]["id"] == add2.data["id"] assert cr.list_custom_roots().data == []