gabriel / musehub public
test_musehub_ui_commit_detail_ssr.py python
264 lines 7.6 KB
cd448303 Initial extraction of MuseHub from maestro monorepo. Gabriel Cardona <gabriel@tellurstori.com> 7d ago
1 """Tests for the SSR commit detail page — HTMX SSR + comment threading (issue #583).
2
3 Covers server-side rendering of commit metadata, comment thread, HTMX fragment
4 responses, audio shell, and 404 handling.
5
6 Test areas:
7 Basic rendering
8 - test_commit_detail_renders_message_server_side
9 - test_commit_detail_unknown_sha_404
10
11 SSR content
12 - test_commit_detail_renders_diff_stats (author + branch metadata)
13 - test_commit_detail_renders_comment_server_side
14 - test_commit_detail_audio_shell_when_audio_url
15
16 No-audio
17 - test_commit_detail_no_audio_shell_when_no_url
18
19 HTMX fragment
20 - test_commit_detail_htmx_fragment_returns_comments
21 """
22 from __future__ import annotations
23
24 import uuid
25 from datetime import datetime, timezone
26
27 import pytest
28 from httpx import AsyncClient
29 from sqlalchemy.ext.asyncio import AsyncSession
30
31 from musehub.db.musehub_models import MusehubComment, MusehubCommit, MusehubRepo
32
33
34 # ---------------------------------------------------------------------------
35 # Helpers
36 # ---------------------------------------------------------------------------
37
38
39 async def _make_repo(
40 db: AsyncSession,
41 owner: str = "beatmaker",
42 slug: str = "jazz-project",
43 ) -> str:
44 """Seed a public repo and return its repo_id string."""
45 repo = MusehubRepo(
46 name=slug,
47 owner=owner,
48 slug=slug,
49 visibility="public",
50 owner_user_id="uid-beatmaker",
51 )
52 db.add(repo)
53 await db.commit()
54 await db.refresh(repo)
55 return str(repo.repo_id)
56
57
58 async def _make_commit(
59 db: AsyncSession,
60 repo_id: str,
61 *,
62 commit_id: str | None = None,
63 message: str = "Add jazz bridge section",
64 author: str = "beatmaker",
65 branch: str = "main",
66 parent_ids: list[str] | None = None,
67 snapshot_id: str | None = None,
68 ) -> MusehubCommit:
69 """Seed a commit and return it."""
70 cid = commit_id or f"abc{uuid.uuid4().hex[:10]}"
71 commit = MusehubCommit(
72 commit_id=cid,
73 repo_id=repo_id,
74 branch=branch,
75 parent_ids=parent_ids or [],
76 message=message,
77 author=author,
78 timestamp=datetime.now(tz=timezone.utc),
79 snapshot_id=snapshot_id,
80 )
81 db.add(commit)
82 await db.commit()
83 await db.refresh(commit)
84 return commit
85
86
87 async def _make_commit_comment(
88 db: AsyncSession,
89 repo_id: str,
90 commit_id: str,
91 *,
92 author: str = "producer",
93 body: str = "Nice groove!",
94 parent_id: str | None = None,
95 ) -> MusehubComment:
96 """Seed a commit comment and return it."""
97 comment = MusehubComment(
98 comment_id=str(uuid.uuid4()),
99 repo_id=repo_id,
100 target_type="commit",
101 target_id=commit_id,
102 author=author,
103 body=body,
104 parent_id=parent_id,
105 created_at=datetime.now(tz=timezone.utc),
106 updated_at=datetime.now(tz=timezone.utc),
107 )
108 db.add(comment)
109 await db.commit()
110 await db.refresh(comment)
111 return comment
112
113
114 async def _get_detail(
115 client: AsyncClient,
116 commit_id: str,
117 owner: str = "beatmaker",
118 slug: str = "jazz-project",
119 headers: dict[str, str] | None = None,
120 ) -> tuple[int, str]:
121 """Fetch the commit detail page; return (status_code, body_text)."""
122 resp = await client.get(
123 f"/musehub/ui/{owner}/{slug}/commits/{commit_id}",
124 headers=headers or {},
125 )
126 return resp.status_code, resp.text
127
128
129 # ---------------------------------------------------------------------------
130 # Basic rendering
131 # ---------------------------------------------------------------------------
132
133
134 @pytest.mark.anyio
135 async def test_commit_detail_renders_message_server_side(
136 client: AsyncClient,
137 db_session: AsyncSession,
138 ) -> None:
139 """Commit message appears in the HTML rendered on the server."""
140 repo_id = await _make_repo(db_session)
141 commit = await _make_commit(db_session, repo_id, message="Add jazz bridge section")
142
143 status, body = await _get_detail(client, commit.commit_id)
144
145 assert status == 200
146 assert "Add jazz bridge section" in body
147
148
149 @pytest.mark.anyio
150 async def test_commit_detail_unknown_sha_404(
151 client: AsyncClient,
152 db_session: AsyncSession,
153 ) -> None:
154 """A non-existent commit SHA returns 404."""
155 await _make_repo(db_session)
156
157 resp = await client.get("/musehub/ui/beatmaker/jazz-project/commits/deadbeef00000000")
158 assert resp.status_code == 404
159
160
161 # ---------------------------------------------------------------------------
162 # SSR content
163 # ---------------------------------------------------------------------------
164
165
166 @pytest.mark.anyio
167 async def test_commit_detail_renders_diff_stats(
168 client: AsyncClient,
169 db_session: AsyncSession,
170 ) -> None:
171 """Commit author and branch appear in the server-rendered metadata grid."""
172 repo_id = await _make_repo(db_session)
173 commit = await _make_commit(
174 db_session, repo_id, author="jazzman", branch="feat/bridge"
175 )
176
177 status, body = await _get_detail(client, commit.commit_id)
178
179 assert status == 200
180 assert "jazzman" in body
181 assert "feat/bridge" in body
182
183
184 @pytest.mark.anyio
185 async def test_commit_detail_renders_comment_server_side(
186 client: AsyncClient,
187 db_session: AsyncSession,
188 ) -> None:
189 """A seeded commit comment body appears in the rendered HTML."""
190 repo_id = await _make_repo(db_session)
191 commit = await _make_commit(db_session, repo_id)
192 await _make_commit_comment(
193 db_session, repo_id, commit.commit_id, body="Great chord progression!"
194 )
195
196 status, body = await _get_detail(client, commit.commit_id)
197
198 assert status == 200
199 assert "Great chord progression!" in body
200
201
202 @pytest.mark.anyio
203 async def test_commit_detail_audio_shell_when_audio_url(
204 client: AsyncClient,
205 db_session: AsyncSession,
206 ) -> None:
207 """When a commit has a snapshot_id, the waveform div is rendered with data-url."""
208 repo_id = await _make_repo(db_session)
209 snap_id = f"sha256:{uuid.uuid4().hex}"
210 commit = await _make_commit(db_session, repo_id, snapshot_id=snap_id)
211
212 status, body = await _get_detail(client, commit.commit_id)
213
214 assert status == 200
215 assert "commit-waveform" in body
216 assert snap_id in body
217
218
219 # ---------------------------------------------------------------------------
220 # No-audio path
221 # ---------------------------------------------------------------------------
222
223
224 @pytest.mark.anyio
225 async def test_commit_detail_no_audio_shell_when_no_url(
226 client: AsyncClient,
227 db_session: AsyncSession,
228 ) -> None:
229 """When a commit has no snapshot_id, the waveform div is not rendered."""
230 repo_id = await _make_repo(db_session)
231 commit = await _make_commit(db_session, repo_id, snapshot_id=None)
232
233 status, body = await _get_detail(client, commit.commit_id)
234
235 assert status == 200
236 assert "commit-waveform" not in body
237
238
239 # ---------------------------------------------------------------------------
240 # HTMX fragment
241 # ---------------------------------------------------------------------------
242
243
244 @pytest.mark.anyio
245 async def test_commit_detail_htmx_fragment_returns_comments(
246 client: AsyncClient,
247 db_session: AsyncSession,
248 ) -> None:
249 """GET with HX-Request: true returns the comment fragment (no full page shell)."""
250 repo_id = await _make_repo(db_session)
251 commit = await _make_commit(db_session, repo_id)
252 await _make_commit_comment(
253 db_session, repo_id, commit.commit_id, body="Fragment visible here."
254 )
255
256 status, body = await _get_detail(
257 client, commit.commit_id, headers={"HX-Request": "true"}
258 )
259
260 assert status == 200
261 assert "Fragment visible here." in body
262 # Fragment must not include the full page chrome
263 assert "<html" not in body
264 assert "<!DOCTYPE" not in body