gabriel / musehub public
test_musehub_ui_commits_enhanced.py python
399 lines 14.4 KB
6b53f1af feat: supercharge all pages, full SOC refactor, and Python 3.14 upgrade (#7) Gabriel Cardona <cgcardona@gmail.com> 5d ago
1 """Regression tests for the enhanced commits list page.
2
3 Covers the four feature areas added to commits_list_page():
4
5 Filter bar
6 - test_commits_enhanced_filter_bar_present — filter-bar HTML element present
7 - test_commits_enhanced_author_dropdown_present — author <select> with 'All authors' default
8 - test_commits_enhanced_date_picker_inputs_present — dateFrom / dateTo date inputs present
9 - test_commits_enhanced_search_input_present — message search <input> present
10 - test_commits_enhanced_tag_filter_input_present — tag filter <input> present
11
12 Server-side filtering
13 - test_commits_enhanced_author_filter_narrows_results — ?author= returns only that author's commits
14 - test_commits_enhanced_author_filter_excludes_others — commits by other authors absent
15 - test_commits_enhanced_search_filter_matches_message — ?q= matches substring in commit message
16 - test_commits_enhanced_search_filter_excludes_others — non-matching commits absent
17 - test_commits_enhanced_date_from_filter — ?dateFrom= excludes older commits
18 - test_commits_enhanced_tag_filter_matches_tag — ?tag=emotion:funky matches message substring
19
20 Compare mode
21 - test_commits_enhanced_compare_toggle_btn_present — compare-toggle-btn button present
22 - test_commits_enhanced_compare_strip_present — compare-strip container present
23 - test_commits_enhanced_compare_check_inputs_present — compare-check checkboxes per row
24 - test_commits_enhanced_compare_js_function — toggleCompareMode() JS function present
25
26 Metadata badges (client-side JS)
27 - test_commits_enhanced_meta_badges_container_present — meta-badges span present per row
28 - test_commits_enhanced_badge_js_extract_function — extractBadges() JS function present
29 - test_commits_enhanced_chip_css_classes_present — chip-tempo / chip-key / chip-emotion CSS defined
30
31 Mini-lane
32 - test_commits_enhanced_dag_merge_arm_present — dag-merge-arm element on merge commits
33 - test_commits_enhanced_mini_lane_dag_col_present — dag-col column present
34
35 Pagination with active filters
36 - test_commits_enhanced_pagination_preserves_filters — page links carry active filter params
37 """
38 from __future__ import annotations
39
40 import uuid
41 from datetime import datetime, timezone
42
43 import pytest
44 from httpx import AsyncClient
45 from sqlalchemy.ext.asyncio import AsyncSession
46
47 from musehub.db.musehub_models import MusehubBranch, MusehubCommit, MusehubRepo
48
49 # ── Constants ─────────────────────────────────────────────────────────────────
50
51 _OWNER = "enhancedowner"
52 _SLUG = "enhanced-commits"
53
54 _SHA_ALICE_1 = "a1" + "0" * 38
55 _SHA_ALICE_2 = "a2" + "0" * 38
56 _SHA_BOB_1 = "b1" + "0" * 38
57 _SHA_MERGE = "cc" + "0" * 38
58
59 # ── Seed helpers ──────────────────────────────────────────────────────────────
60
61
62 async def _seed_repo(db: AsyncSession) -> str:
63 """Seed a public repo with 4 commits from 2 authors and return repo_id."""
64 repo = MusehubRepo(
65 repo_id=str(uuid.uuid4()),
66 name=_SLUG,
67 owner=_OWNER,
68 slug=_SLUG,
69 visibility="public",
70 owner_user_id=str(uuid.uuid4()),
71 )
72 db.add(repo)
73 await db.flush()
74 repo_id = str(repo.repo_id)
75
76 branch = MusehubBranch(repo_id=repo_id, name="main", head_commit_id=_SHA_MERGE)
77 db.add(branch)
78
79 # Alice: two commits with music metadata in messages
80 db.add(MusehubCommit(
81 commit_id=_SHA_ALICE_1,
82 repo_id=repo_id,
83 branch="main",
84 parent_ids=[],
85 message="Add walking bass line 120 BPM Cm emotion:funky",
86 author="alice",
87 timestamp=datetime(2026, 1, 10, tzinfo=timezone.utc),
88 ))
89 db.add(MusehubCommit(
90 commit_id=_SHA_ALICE_2,
91 repo_id=repo_id,
92 branch="main",
93 parent_ids=[_SHA_ALICE_1],
94 message="Refine rhodes chord voicings stage:chorus",
95 author="alice",
96 timestamp=datetime(2026, 2, 15, tzinfo=timezone.utc),
97 ))
98 # Bob: one commit
99 db.add(MusehubCommit(
100 commit_id=_SHA_BOB_1,
101 repo_id=repo_id,
102 branch="main",
103 parent_ids=[_SHA_ALICE_2],
104 message="Add jazz drums groove 90 BPM Gm",
105 author="bob",
106 timestamp=datetime(2026, 3, 1, tzinfo=timezone.utc),
107 ))
108 # Merge commit
109 db.add(MusehubCommit(
110 commit_id=_SHA_MERGE,
111 repo_id=repo_id,
112 branch="main",
113 parent_ids=[_SHA_ALICE_2, _SHA_BOB_1],
114 message="Merge feat/drums into main",
115 author="alice",
116 timestamp=datetime(2026, 3, 2, tzinfo=timezone.utc),
117 ))
118
119 await db.commit()
120 return repo_id
121
122
123 def _url(path: str = "") -> str:
124 return f"/{_OWNER}/{_SLUG}/commits{path}"
125
126
127 # ── Filter bar HTML ───────────────────────────────────────────────────────────
128
129
130 @pytest.mark.anyio
131 async def test_commits_enhanced_filter_bar_present(
132 client: AsyncClient, db_session: AsyncSession
133 ) -> None:
134 """filter-bar container is rendered on the commits list page."""
135 await _seed_repo(db_session)
136 resp = await client.get(_url())
137 assert resp.status_code == 200
138 assert "filter-bar" in resp.text
139
140
141 @pytest.mark.anyio
142 async def test_commits_enhanced_author_dropdown_present(
143 client: AsyncClient, db_session: AsyncSession
144 ) -> None:
145 """Author <select> dropdown is present and includes 'All authors' option."""
146 await _seed_repo(db_session)
147 resp = await client.get(_url())
148 assert resp.status_code == 200
149 assert "All authors" in resp.text
150 # Both authors appear as options
151 assert "alice" in resp.text
152 assert "bob" in resp.text
153
154
155 @pytest.mark.anyio
156 async def test_commits_enhanced_date_picker_inputs_present(
157 client: AsyncClient, db_session: AsyncSession
158 ) -> None:
159 """dateFrom and dateTo date inputs are present in the filter bar."""
160 await _seed_repo(db_session)
161 resp = await client.get(_url())
162 assert resp.status_code == 200
163 assert 'type="date"' in resp.text
164 assert "dateFrom" in resp.text
165 assert "dateTo" in resp.text
166
167
168 @pytest.mark.anyio
169 async def test_commits_enhanced_search_input_present(
170 client: AsyncClient, db_session: AsyncSession
171 ) -> None:
172 """Full-text message search input is present in the filter bar."""
173 await _seed_repo(db_session)
174 resp = await client.get(_url())
175 assert resp.status_code == 200
176 assert 'name="q"' in resp.text
177 assert "keyword in message" in resp.text
178
179
180 @pytest.mark.anyio
181 async def test_commits_enhanced_tag_filter_input_present(
182 client: AsyncClient, db_session: AsyncSession
183 ) -> None:
184 """Tag filter input is present in the filter bar."""
185 await _seed_repo(db_session)
186 resp = await client.get(_url())
187 assert resp.status_code == 200
188 assert 'name="tag"' in resp.text
189 assert "emotion:" in resp.text # placeholder hint text
190
191
192 # ── Server-side filtering ─────────────────────────────────────────────────────
193
194
195 @pytest.mark.anyio
196 async def test_commits_enhanced_author_filter_narrows_results(
197 client: AsyncClient, db_session: AsyncSession
198 ) -> None:
199 """?author=bob returns only bob's commits."""
200 await _seed_repo(db_session)
201 resp = await client.get(_url() + "?author=bob")
202 assert resp.status_code == 200
203 assert _SHA_BOB_1[:8] in resp.text
204
205
206 @pytest.mark.anyio
207 async def test_commits_enhanced_author_filter_excludes_others(
208 client: AsyncClient, db_session: AsyncSession
209 ) -> None:
210 """Commits by authors other than the filtered author do not appear."""
211 await _seed_repo(db_session)
212 resp = await client.get(_url() + "?author=bob")
213 assert resp.status_code == 200
214 # Alice's commit SHA should not appear
215 assert _SHA_ALICE_1[:8] not in resp.text
216
217
218 @pytest.mark.anyio
219 async def test_commits_enhanced_search_filter_matches_message(
220 client: AsyncClient, db_session: AsyncSession
221 ) -> None:
222 """?q=walking+bass returns the commit containing that substring."""
223 await _seed_repo(db_session)
224 resp = await client.get(_url() + "?q=walking+bass")
225 assert resp.status_code == 200
226 assert _SHA_ALICE_1[:8] in resp.text
227
228
229 @pytest.mark.anyio
230 async def test_commits_enhanced_search_filter_excludes_others(
231 client: AsyncClient, db_session: AsyncSession
232 ) -> None:
233 """?q= excludes commits that do not match the search term."""
234 await _seed_repo(db_session)
235 resp = await client.get(_url() + "?q=walking+bass")
236 assert resp.status_code == 200
237 # Bob's drums commit should not appear
238 assert _SHA_BOB_1[:8] not in resp.text
239
240
241 @pytest.mark.anyio
242 async def test_commits_enhanced_date_from_filter(
243 client: AsyncClient, db_session: AsyncSession
244 ) -> None:
245 """?dateFrom=2026-03-01 excludes commits before that date."""
246 await _seed_repo(db_session)
247 resp = await client.get(_url() + "?dateFrom=2026-03-01")
248 assert resp.status_code == 200
249 # Only Bob (2026-03-01) and merge (2026-03-02) should appear
250 assert _SHA_BOB_1[:8] in resp.text
251 assert _SHA_MERGE[:8] in resp.text
252 # Alice's January commit should not appear
253 assert _SHA_ALICE_1[:8] not in resp.text
254
255
256 @pytest.mark.anyio
257 async def test_commits_enhanced_tag_filter_matches_tag(
258 client: AsyncClient, db_session: AsyncSession
259 ) -> None:
260 """?tag=emotion:funky matches the commit containing that tag string."""
261 await _seed_repo(db_session)
262 resp = await client.get(_url() + "?tag=emotion%3Afunky")
263 assert resp.status_code == 200
264 assert _SHA_ALICE_1[:8] in resp.text
265 # The commit without the tag should not appear
266 assert _SHA_BOB_1[:8] not in resp.text
267
268
269 # ── Compare mode ──────────────────────────────────────────────────────────────
270
271
272 @pytest.mark.anyio
273 async def test_commits_enhanced_compare_toggle_btn_present(
274 client: AsyncClient, db_session: AsyncSession
275 ) -> None:
276 """Compare toggle button is present in the toolbar."""
277 await _seed_repo(db_session)
278 resp = await client.get(_url())
279 assert resp.status_code == 200
280 assert "compare-toggle-btn" in resp.text
281
282
283 @pytest.mark.anyio
284 async def test_commits_enhanced_compare_strip_present(
285 client: AsyncClient, db_session: AsyncSession
286 ) -> None:
287 """compare-strip container is present (initially hidden via CSS/JS)."""
288 await _seed_repo(db_session)
289 resp = await client.get(_url())
290 assert resp.status_code == 200
291 assert "compare-strip" in resp.text
292 assert "compare-link" in resp.text
293
294
295 @pytest.mark.anyio
296 async def test_commits_enhanced_compare_check_inputs_present(
297 client: AsyncClient, db_session: AsyncSession
298 ) -> None:
299 """Per-row compare checkboxes are rendered for each commit."""
300 await _seed_repo(db_session)
301 resp = await client.get(_url())
302 assert resp.status_code == 200
303 assert "compare-check" in resp.text
304 assert "compare-col" in resp.text
305
306
307 @pytest.mark.anyio
308 async def test_commits_enhanced_compare_js_function(
309 client: AsyncClient, db_session: AsyncSession
310 ) -> None:
311 """Compare mode uses SSR compare-toggle-btn and compare-strip (JS moved to commits.ts)."""
312 await _seed_repo(db_session)
313 resp = await client.get(_url())
314 assert resp.status_code == 200
315 assert "compare-toggle-btn" in resp.text
316 assert "compare-strip" in resp.text
317
318
319 # ── Metadata badge JS ─────────────────────────────────────────────────────────
320
321
322 @pytest.mark.anyio
323 async def test_commits_enhanced_meta_badges_container_present(
324 client: AsyncClient, db_session: AsyncSession
325 ) -> None:
326 """meta-badges span is rendered inside each commit row."""
327 await _seed_repo(db_session)
328 resp = await client.get(_url())
329 assert resp.status_code == 200
330 assert "meta-badges" in resp.text
331
332
333 @pytest.mark.anyio
334 async def test_commits_enhanced_badge_js_extract_function(
335 client: AsyncClient, db_session: AsyncSession
336 ) -> None:
337 """Badge logic (extractBadges/renderBadges) moved to commits.ts; page dispatches commits module."""
338 await _seed_repo(db_session)
339 resp = await client.get(_url())
340 assert resp.status_code == 200
341 assert '"page": "commits"' in resp.text
342
343
344 @pytest.mark.anyio
345 async def test_commits_enhanced_chip_css_classes_present(
346 client: AsyncClient, db_session: AsyncSession
347 ) -> None:
348 """CSS classes for metadata chips are defined in the page <style>."""
349 await _seed_repo(db_session)
350 resp = await client.get(_url())
351 assert resp.status_code == 200
352 assert "chip-tempo" in resp.text
353 assert "chip-key" in resp.text
354 assert "chip-emotion" in resp.text
355 assert "chip-stage" in resp.text
356 assert "chip-instr" in resp.text
357
358
359 # ── Mini-lane DAG ─────────────────────────────────────────────────────────────
360
361
362 @pytest.mark.anyio
363 async def test_commits_enhanced_dag_merge_arm_present(
364 client: AsyncClient, db_session: AsyncSession
365 ) -> None:
366 """dag-merge-arm element is rendered for merge commits."""
367 await _seed_repo(db_session)
368 resp = await client.get(_url())
369 assert resp.status_code == 200
370 assert "dag-merge-arm" in resp.text
371
372
373 @pytest.mark.anyio
374 async def test_commits_enhanced_mini_lane_dag_col_present(
375 client: AsyncClient, db_session: AsyncSession
376 ) -> None:
377 """dag-col column is rendered for every commit row."""
378 await _seed_repo(db_session)
379 resp = await client.get(_url())
380 assert resp.status_code == 200
381 assert "dag-col" in resp.text
382 assert "dag-node" in resp.text
383
384
385 # ── Pagination preserves filters ──────────────────────────────────────────────
386
387
388 @pytest.mark.anyio
389 async def test_commits_enhanced_pagination_preserves_filters(
390 client: AsyncClient, db_session: AsyncSession
391 ) -> None:
392 """Pagination links forward active filter params so state persists across pages."""
393 await _seed_repo(db_session)
394 resp = await client.get(_url() + "?author=alice&per_page=1&page=1")
395 assert resp.status_code == 200
396 body = resp.text
397 # Older link should carry author=alice
398 assert "author=alice" in body
399 assert "page=2" in body