gabriel / muse public
test_core_repo.py python
153 lines 5.6 KB
Raw
sha256:660fcac1df3ab28f61862e961890bd2ca8b754fa0242079d93ca1e25037ec8a6 chore(tests): add docstring to tests/__init__.py so rc14 tr… Human 27 days ago
1 """Tests for muse/core/repo.py — find_repo_root, require_repo, read_repo_id."""
2
3 from __future__ import annotations
4
5 import json
6 import pathlib
7 import tempfile
8
9 import pytest
10
11 from muse.core.errors import ExitCode
12 from muse.core.repo import find_repo_root, read_repo_id, require_repo
13
14
15 # ---------------------------------------------------------------------------
16 # Helpers
17 # ---------------------------------------------------------------------------
18
19
20 def _make_muse_dir(root: pathlib.Path, repo_id: str = "test-repo-id") -> pathlib.Path:
21 muse = root / ".muse"
22 muse.mkdir(parents=True)
23 (muse / "repo.json").write_text(json.dumps({"repo_id": repo_id}))
24 return muse
25
26
27 # ---------------------------------------------------------------------------
28 # find_repo_root
29 # ---------------------------------------------------------------------------
30
31
32 class TestFindRepoRoot:
33 def test_returns_none_outside_repo(self, tmp_path: pathlib.Path) -> None:
34 assert find_repo_root(tmp_path) is None
35
36 def test_finds_muse_in_cwd(self, tmp_path: pathlib.Path) -> None:
37 _make_muse_dir(tmp_path)
38 assert find_repo_root(tmp_path) == tmp_path
39
40 def test_finds_muse_in_ancestor(self, tmp_path: pathlib.Path) -> None:
41 _make_muse_dir(tmp_path)
42 subdir = tmp_path / "src" / "pkg"
43 subdir.mkdir(parents=True)
44 assert find_repo_root(subdir) == tmp_path
45
46
47 # ---------------------------------------------------------------------------
48 # require_repo
49 # ---------------------------------------------------------------------------
50
51
52 class TestRequireRepo:
53 def test_exits_outside_repo(self, tmp_path: pathlib.Path) -> None:
54 with pytest.raises(SystemExit) as exc:
55 require_repo(tmp_path)
56 assert exc.value.code == ExitCode.REPO_NOT_FOUND
57
58 def test_returns_root_inside_repo(self, tmp_path: pathlib.Path) -> None:
59 _make_muse_dir(tmp_path)
60 assert require_repo(tmp_path) == tmp_path
61
62
63 # ---------------------------------------------------------------------------
64 # read_repo_id — 4 belt-and-suspenders tests covering every exit path
65 # ---------------------------------------------------------------------------
66
67
68 class TestReadRepoId:
69 def test_returns_repo_id_from_valid_file(self, tmp_path: pathlib.Path) -> None:
70 """Happy path: valid repo.json returns the repo_id string."""
71 _make_muse_dir(tmp_path, repo_id="my-special-repo")
72 assert read_repo_id(tmp_path) == "my-special-repo"
73
74 def test_missing_repo_json_exits_repo_not_found(
75 self, tmp_path: pathlib.Path
76 ) -> None:
77 """FileNotFoundError → SystemExit(REPO_NOT_FOUND)."""
78 (tmp_path / ".muse").mkdir()
79 # repo.json intentionally absent.
80 with pytest.raises(SystemExit) as exc:
81 read_repo_id(tmp_path)
82 assert exc.value.code == ExitCode.REPO_NOT_FOUND
83
84 def test_malformed_json_exits_internal_error(
85 self, tmp_path: pathlib.Path
86 ) -> None:
87 """JSONDecodeError → SystemExit(INTERNAL_ERROR)."""
88 muse = tmp_path / ".muse"
89 muse.mkdir()
90 (muse / "repo.json").write_text("this is not json{{{")
91 with pytest.raises(SystemExit) as exc:
92 read_repo_id(tmp_path)
93 assert exc.value.code == ExitCode.INTERNAL_ERROR
94
95 def test_missing_key_exits_internal_error(self, tmp_path: pathlib.Path) -> None:
96 """KeyError (no 'repo_id' key) → SystemExit(INTERNAL_ERROR)."""
97 muse = tmp_path / ".muse"
98 muse.mkdir()
99 (muse / "repo.json").write_text(json.dumps({"wrong_key": "value"}))
100 with pytest.raises(SystemExit) as exc:
101 read_repo_id(tmp_path)
102 assert exc.value.code == ExitCode.INTERNAL_ERROR
103
104
105 # ---------------------------------------------------------------------------
106 # parse_date_arg — 6 belt-and-suspenders tests
107 # ---------------------------------------------------------------------------
108
109
110 class TestParseDateArg:
111 """parse_date_arg handles YYYY-MM-DD and YYYY-MM-DDTHH:MM:SS; rejects bad input."""
112
113 def test_parses_date_only(self) -> None:
114 from muse.core.repo import parse_date_arg
115 import datetime as dt
116 result = parse_date_arg("2026-03-25", "--since")
117 assert result == dt.datetime(2026, 3, 25, tzinfo=dt.timezone.utc)
118
119 def test_parses_full_datetime(self) -> None:
120 from muse.core.repo import parse_date_arg
121 import datetime as dt
122 result = parse_date_arg("2026-03-25T14:30:00", "--until")
123 assert result == dt.datetime(2026, 3, 25, 14, 30, 0, tzinfo=dt.timezone.utc)
124
125 def test_invalid_value_exits_1(self) -> None:
126 from muse.core.repo import parse_date_arg
127 with pytest.raises(SystemExit) as exc:
128 parse_date_arg("not-a-date", "--since")
129 assert exc.value.code == 1
130
131 def test_since_flag_name_in_error_message(
132 self, capsys: pytest.CaptureFixture[str]
133 ) -> None:
134 from muse.core.repo import parse_date_arg
135 with pytest.raises(SystemExit):
136 parse_date_arg("bad", "--since")
137 err = capsys.readouterr().err
138 assert "--since" in err
139
140 def test_until_flag_name_in_error_message(
141 self, capsys: pytest.CaptureFixture[str]
142 ) -> None:
143 from muse.core.repo import parse_date_arg
144 with pytest.raises(SystemExit):
145 parse_date_arg("bad", "--until")
146 err = capsys.readouterr().err
147 assert "--until" in err
148
149 def test_result_is_utc_aware(self) -> None:
150 from muse.core.repo import parse_date_arg
151 import datetime as dt
152 result = parse_date_arg("2026-01-01", "--since")
153 assert result.tzinfo == dt.timezone.utc
File History 5 commits
sha256:660fcac1df3ab28f61862e961890bd2ca8b754fa0242079d93ca1e25037ec8a6 chore(tests): add docstring to tests/__init__.py so rc14 tr… Human 27 days ago
sha256:d8316ffae901be06347e16ab55be11868eb519dd16ade3e8aa16a99e662f7e62 baseline: rc14 re-baseline after rc3 store corruption recovery Human patch 27 days ago
sha256:50d413a635f57761c3fce1f70251b957b6423821525bf330747ef4007339222e Merge branch 'dev' into main Human 30 days ago
sha256:fb67fed5a4d3e40de84bdd163de94ef1386570bef1dd1a020a732c8a038962ce Merge branch 'dev' into main Human 49 days ago
sha256:1c4b3e3a9a1f300774c3ee662b572a698d5fd405bf765a71e6011a2e9c3eaaaa feat: Muse — version control for the agent era Human 102 days ago