cgcardona / muse public
test_artifact_resolver.py python
183 lines 5.6 KB
12901c5a Initial extraction from tellurstori/maestro cgcardona <gabriel@tellurstori.com> 4d ago
1 """Tests for ``maestro.muse_cli.artifact_resolver``.
2
3 Covers:
4 - ``test_resolve_artifact_working_tree`` — existing file path resolves directly.
5 - ``test_resolve_artifact_from_commit`` — commit-ID prefix resolution via DB.
6 - ``test_resolve_artifact_file_not_found_exits_1`` — non-existent path exits 1.
7 - ``test_resolve_artifact_ambiguous_prefix_exits_1`` — multiple commit matches.
8 """
9 from __future__ import annotations
10
11 import datetime
12 import json
13 import pathlib
14 import uuid
15
16 import pytest
17 import typer
18 from sqlalchemy.ext.asyncio import AsyncSession
19
20 from maestro.muse_cli.artifact_resolver import resolve_artifact_async
21 from maestro.muse_cli.commands.commit import _commit_async
22 from maestro.muse_cli.errors import ExitCode
23 from maestro.muse_cli.models import MuseCliCommit, MuseCliSnapshot
24
25
26 # ---------------------------------------------------------------------------
27 # Helpers
28 # ---------------------------------------------------------------------------
29
30
31 def _init_muse_repo(root: pathlib.Path, repo_id: str | None = None) -> str:
32 rid = repo_id or str(uuid.uuid4())
33 muse = root / ".muse"
34 (muse / "refs" / "heads").mkdir(parents=True)
35 (muse / "repo.json").write_text(
36 json.dumps({"repo_id": rid, "schema_version": "1"})
37 )
38 (muse / "HEAD").write_text("refs/heads/main")
39 (muse / "refs" / "heads" / "main").write_text("")
40 return rid
41
42
43 def _populate_workdir(root: pathlib.Path, files: dict[str, bytes] | None = None) -> None:
44 workdir = root / "muse-work"
45 workdir.mkdir(exist_ok=True)
46 if files is None:
47 files = {"beat.mid": b"MIDI-DATA", "lead.mp3": b"MP3-DATA"}
48 for name, content in files.items():
49 (workdir / name).write_bytes(content)
50
51
52 # ---------------------------------------------------------------------------
53 # Tests
54 # ---------------------------------------------------------------------------
55
56
57 @pytest.mark.anyio
58 async def test_resolve_artifact_working_tree(
59 tmp_path: pathlib.Path,
60 muse_cli_db_session: AsyncSession,
61 ) -> None:
62 """An existing filesystem path resolves without touching the DB."""
63 _init_muse_repo(tmp_path)
64 target = tmp_path / "muse-work" / "beat.mid"
65 target.parent.mkdir(parents=True, exist_ok=True)
66 target.write_bytes(b"MIDI")
67
68 resolved = await resolve_artifact_async(
69 str(target), root=tmp_path, session=muse_cli_db_session
70 )
71
72 assert resolved == target.resolve()
73
74
75 @pytest.mark.anyio
76 async def test_resolve_artifact_working_tree_relative_path(
77 tmp_path: pathlib.Path,
78 muse_cli_db_session: AsyncSession,
79 ) -> None:
80 """A filename relative to muse-work/ resolves correctly."""
81 _init_muse_repo(tmp_path)
82 (tmp_path / "muse-work").mkdir(parents=True, exist_ok=True)
83 (tmp_path / "muse-work" / "lead.mp3").write_bytes(b"MP3")
84
85 resolved = await resolve_artifact_async(
86 "lead.mp3", root=tmp_path, session=muse_cli_db_session
87 )
88
89 assert resolved == (tmp_path / "muse-work" / "lead.mp3").resolve()
90
91
92 @pytest.mark.anyio
93 async def test_resolve_artifact_from_commit(
94 tmp_path: pathlib.Path,
95 muse_cli_db_session: AsyncSession,
96 ) -> None:
97 """Commit-ID prefix resolves to the correct working-tree file."""
98 _init_muse_repo(tmp_path)
99 _populate_workdir(tmp_path, {"solo.mid": b"MIDI-SOLO"})
100
101 commit_id = await _commit_async(
102 message="test take",
103 root=tmp_path,
104 session=muse_cli_db_session,
105 )
106
107 resolved = await resolve_artifact_async(
108 commit_id[:8], root=tmp_path, session=muse_cli_db_session
109 )
110
111 assert resolved == (tmp_path / "muse-work" / "solo.mid").resolve()
112
113
114 @pytest.mark.anyio
115 async def test_resolve_artifact_file_not_found_exits_1(
116 tmp_path: pathlib.Path,
117 muse_cli_db_session: AsyncSession,
118 ) -> None:
119 """A non-existent path that is not a hex prefix exits with USER_ERROR."""
120 _init_muse_repo(tmp_path)
121
122 with pytest.raises(typer.Exit) as exc_info:
123 await resolve_artifact_async(
124 "no_such_file.mid", root=tmp_path, session=muse_cli_db_session
125 )
126
127 assert exc_info.value.exit_code == ExitCode.USER_ERROR
128
129
130 @pytest.mark.anyio
131 async def test_resolve_artifact_ambiguous_prefix_exits_1(
132 tmp_path: pathlib.Path,
133 muse_cli_db_session: AsyncSession,
134 ) -> None:
135 """An ambiguous commit prefix (matches > 1 commit) exits with USER_ERROR."""
136 common_prefix = "aaaa"
137 snap_id_1 = common_prefix + "b" * 60
138 snap_id_2 = common_prefix + "c" * 60
139 commit_id_1 = common_prefix + "d" * 60
140 commit_id_2 = common_prefix + "e" * 60
141 repo_id = str(uuid.uuid4())
142 now = datetime.datetime.now(datetime.timezone.utc)
143
144 muse_cli_db_session.add(
145 MuseCliSnapshot(snapshot_id=snap_id_1, manifest={"a.mid": "x" * 64})
146 )
147 muse_cli_db_session.add(
148 MuseCliSnapshot(snapshot_id=snap_id_2, manifest={"b.mid": "y" * 64})
149 )
150 await muse_cli_db_session.flush()
151
152 muse_cli_db_session.add(
153 MuseCliCommit(
154 commit_id=commit_id_1,
155 repo_id=repo_id,
156 branch="main",
157 parent_commit_id=None,
158 snapshot_id=snap_id_1,
159 message="first",
160 author="",
161 committed_at=now,
162 )
163 )
164 muse_cli_db_session.add(
165 MuseCliCommit(
166 commit_id=commit_id_2,
167 repo_id=repo_id,
168 branch="main",
169 parent_commit_id=None,
170 snapshot_id=snap_id_2,
171 message="second",
172 author="",
173 committed_at=now,
174 )
175 )
176 await muse_cli_db_session.flush()
177
178 with pytest.raises(typer.Exit) as exc_info:
179 await resolve_artifact_async(
180 common_prefix, root=tmp_path, session=muse_cli_db_session
181 )
182
183 assert exc_info.value.exit_code == ExitCode.USER_ERROR