gabriel / musehub public
test_musehub_ui_commits_enhanced.py python
367 lines 13.8 KB
Raw
sha256:a34090cc4a394a78bd72cbbe34b08cc59525141e19135b6c0ab154f10611b9ef debug(push/stream): instrument O-frame decode path with INF… Sonnet 4.6 patch 122 days 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 from datetime import datetime, timezone
41
42 import pytest
43 from httpx import AsyncClient
44 from sqlalchemy.ext.asyncio import AsyncSession
45
46 from musehub.core.genesis import compute_branch_id, compute_identity_id, compute_repo_id
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 owner_id = compute_identity_id(_OWNER.encode())
65 created_at = datetime.now(tz=timezone.utc)
66 repo = MusehubRepo(
67 repo_id=compute_repo_id(owner_id, _SLUG, "code", created_at.isoformat()),
68 name=_SLUG,
69 owner=_OWNER,
70 slug=_SLUG,
71 visibility="public",
72 owner_user_id=owner_id,
73 created_at=created_at,
74 updated_at=created_at,
75 )
76 db.add(repo)
77 await db.flush()
78 repo_id = str(repo.repo_id)
79
80 branch = MusehubBranch(branch_id=compute_branch_id(repo_id, "main"), repo_id=repo_id, name="main", head_commit_id=_SHA_MERGE)
81 db.add(branch)
82
83 # Alice: two commits with music metadata in messages
84 db.add(MusehubCommit(
85 commit_id=_SHA_ALICE_1,
86 repo_id=repo_id,
87 branch="main",
88 parent_ids=[],
89 message="Add walking bass line 120 BPM Cm emotion:funky",
90 author="alice",
91 timestamp=datetime(2026, 1, 10, tzinfo=timezone.utc),
92 ))
93 db.add(MusehubCommit(
94 commit_id=_SHA_ALICE_2,
95 repo_id=repo_id,
96 branch="main",
97 parent_ids=[_SHA_ALICE_1],
98 message="Refine rhodes chord voicings stage:chorus",
99 author="alice",
100 timestamp=datetime(2026, 2, 15, tzinfo=timezone.utc),
101 ))
102 # Bob: one commit
103 db.add(MusehubCommit(
104 commit_id=_SHA_BOB_1,
105 repo_id=repo_id,
106 branch="main",
107 parent_ids=[_SHA_ALICE_2],
108 message="Add jazz drums groove 90 BPM Gm",
109 author="bob",
110 timestamp=datetime(2026, 3, 1, tzinfo=timezone.utc),
111 ))
112 # Merge commit
113 db.add(MusehubCommit(
114 commit_id=_SHA_MERGE,
115 repo_id=repo_id,
116 branch="main",
117 parent_ids=[_SHA_ALICE_2, _SHA_BOB_1],
118 message="Merge feat/drums into main",
119 author="alice",
120 timestamp=datetime(2026, 3, 2, tzinfo=timezone.utc),
121 ))
122
123 await db.commit()
124 return repo_id
125
126
127 def _url(path: str = "") -> str:
128 return f"/{_OWNER}/{_SLUG}/commits{path}"
129
130
131 # ── Filter bar HTML ───────────────────────────────────────────────────────────
132
133
134 async def test_commits_enhanced_filter_bar_present(
135 client: AsyncClient, db_session: AsyncSession
136 ) -> None:
137 """filter-bar container is rendered on the commits list page."""
138 await _seed_repo(db_session)
139 resp = await client.get(_url())
140 assert resp.status_code == 200
141 assert "filter-bar" in resp.text
142
143
144 async def test_commits_enhanced_author_dropdown_present(
145 client: AsyncClient, db_session: AsyncSession
146 ) -> None:
147 """Author <select> dropdown is present and includes 'All authors' option."""
148 await _seed_repo(db_session)
149 resp = await client.get(_url())
150 assert resp.status_code == 200
151 assert "All authors" in resp.text
152 # Both authors appear as options
153 assert "alice" in resp.text
154 assert "bob" in resp.text
155
156
157 async def test_commits_enhanced_date_picker_inputs_present(
158 client: AsyncClient, db_session: AsyncSession
159 ) -> None:
160 """dateFrom and dateTo date inputs are present in the filter bar."""
161 await _seed_repo(db_session)
162 resp = await client.get(_url())
163 assert resp.status_code == 200
164 assert 'type="date"' in resp.text
165 assert "dateFrom" in resp.text
166 assert "dateTo" in resp.text
167
168
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 async def test_commits_enhanced_tag_filter_input_present(
181 client: AsyncClient, db_session: AsyncSession
182 ) -> None:
183 """Tag filter input is present in the filter bar."""
184 await _seed_repo(db_session)
185 resp = await client.get(_url())
186 assert resp.status_code == 200
187 assert 'name="tag"' in resp.text
188 assert "emotion:" in resp.text # placeholder hint text
189
190
191 # ── Server-side filtering ─────────────────────────────────────────────────────
192
193
194 async def test_commits_enhanced_author_filter_narrows_results(
195 client: AsyncClient, db_session: AsyncSession
196 ) -> None:
197 """?author=bob returns only bob's commits."""
198 await _seed_repo(db_session)
199 resp = await client.get(f"{_url()}?author=bob")
200 assert resp.status_code == 200
201 assert _SHA_BOB_1[:8] in resp.text
202
203
204 async def test_commits_enhanced_author_filter_excludes_others(
205 client: AsyncClient, db_session: AsyncSession
206 ) -> None:
207 """Commits by authors other than the filtered author do not appear."""
208 await _seed_repo(db_session)
209 resp = await client.get(f"{_url()}?author=bob")
210 assert resp.status_code == 200
211 # Alice's commit SHA should not appear
212 assert _SHA_ALICE_1[:8] not in resp.text
213
214
215 async def test_commits_enhanced_search_filter_matches_message(
216 client: AsyncClient, db_session: AsyncSession
217 ) -> None:
218 """?q=walking+bass returns the commit containing that substring."""
219 await _seed_repo(db_session)
220 resp = await client.get(f"{_url()}?q=walking+bass")
221 assert resp.status_code == 200
222 assert _SHA_ALICE_1[:8] in resp.text
223
224
225 async def test_commits_enhanced_search_filter_excludes_others(
226 client: AsyncClient, db_session: AsyncSession
227 ) -> None:
228 """?q= excludes commits that do not match the search term."""
229 await _seed_repo(db_session)
230 resp = await client.get(f"{_url()}?q=walking+bass")
231 assert resp.status_code == 200
232 # Bob's drums commit should not appear
233 assert _SHA_BOB_1[:8] not in resp.text
234
235
236 async def test_commits_enhanced_date_from_filter(
237 client: AsyncClient, db_session: AsyncSession
238 ) -> None:
239 """?dateFrom=2026-03-01 excludes commits before that date."""
240 await _seed_repo(db_session)
241 resp = await client.get(f"{_url()}?dateFrom=2026-03-01")
242 assert resp.status_code == 200
243 # Only Bob (2026-03-01) and merge (2026-03-02) should appear
244 assert _SHA_BOB_1[:8] in resp.text
245 assert _SHA_MERGE[:8] in resp.text
246 # Alice's January commit should not appear
247 assert _SHA_ALICE_1[:8] not in resp.text
248
249
250 async def test_commits_enhanced_tag_filter_matches_tag(
251 client: AsyncClient, db_session: AsyncSession
252 ) -> None:
253 """?tag=emotion:funky matches the commit containing that tag string."""
254 await _seed_repo(db_session)
255 resp = await client.get(f"{_url()}?tag=emotion%3Afunky")
256 assert resp.status_code == 200
257 assert _SHA_ALICE_1[:8] in resp.text
258 # The commit without the tag should not appear
259 assert _SHA_BOB_1[:8] not in resp.text
260
261
262 # ── Compare mode ──────────────────────────────────────────────────────────────
263
264
265 async def test_commits_enhanced_compare_toggle_btn_present(
266 client: AsyncClient, db_session: AsyncSession
267 ) -> None:
268 """Compare toggle button is present in the toolbar."""
269 await _seed_repo(db_session)
270 resp = await client.get(_url())
271 assert resp.status_code == 200
272 assert "compare-toggle-btn" in resp.text
273
274
275 async def test_commits_enhanced_compare_strip_present(
276 client: AsyncClient, db_session: AsyncSession
277 ) -> None:
278 """compare-strip container is present (initially hidden via CSS/JS)."""
279 await _seed_repo(db_session)
280 resp = await client.get(_url())
281 assert resp.status_code == 200
282 assert "compare-strip" in resp.text
283 assert "compare-link" in resp.text
284
285
286 async def test_commits_enhanced_compare_check_inputs_present(
287 client: AsyncClient, db_session: AsyncSession
288 ) -> None:
289 """Per-row compare checkboxes are rendered for each commit."""
290 await _seed_repo(db_session)
291 resp = await client.get(_url())
292 assert resp.status_code == 200
293 assert "compare-check" in resp.text
294 assert "compare-col" in resp.text
295
296
297 async def test_commits_enhanced_compare_js_function(
298 client: AsyncClient, db_session: AsyncSession
299 ) -> None:
300 """Compare mode uses SSR compare-toggle-btn and compare-strip (JS moved to commits.ts)."""
301 await _seed_repo(db_session)
302 resp = await client.get(_url())
303 assert resp.status_code == 200
304 assert "compare-toggle-btn" in resp.text
305 assert "compare-strip" in resp.text
306
307
308 # ── Metadata badge JS ─────────────────────────────────────────────────────────
309
310
311 async def test_commits_enhanced_meta_badges_container_present(
312 client: AsyncClient, db_session: AsyncSession
313 ) -> None:
314 """meta-badges span is rendered inside each commit row."""
315 await _seed_repo(db_session)
316 resp = await client.get(_url())
317 assert resp.status_code == 200
318 assert "meta-badges" in resp.text
319
320
321 async def test_commits_enhanced_badge_js_extract_function(
322 client: AsyncClient, db_session: AsyncSession
323 ) -> None:
324 """Badge logic (extractBadges/renderBadges) moved to commits.ts; page dispatches commits module."""
325 await _seed_repo(db_session)
326 resp = await client.get(_url())
327 assert resp.status_code == 200
328 assert '"page": "commits"' in resp.text
329
330
331 # ── Mini-lane DAG ─────────────────────────────────────────────────────────────
332
333
334 async def test_commits_enhanced_dag_merge_arm_present(
335 client: AsyncClient, db_session: AsyncSession
336 ) -> None:
337 """dag-merge-arm element is rendered for merge commits."""
338 await _seed_repo(db_session)
339 resp = await client.get(_url())
340 assert resp.status_code == 200
341 assert "dag-merge-arm" in resp.text
342
343
344 async def test_commits_enhanced_mini_lane_dag_col_present(
345 client: AsyncClient, db_session: AsyncSession
346 ) -> None:
347 """dag-col column is rendered for every commit row."""
348 await _seed_repo(db_session)
349 resp = await client.get(_url())
350 assert resp.status_code == 200
351 assert "dag-col" in resp.text
352 assert "dag-node" in resp.text
353
354
355 # ── Pagination preserves filters ──────────────────────────────────────────────
356
357
358 async def test_commits_enhanced_pagination_preserves_filters(
359 client: AsyncClient, db_session: AsyncSession
360 ) -> None:
361 """Pagination links forward active filter params so state persists across pages."""
362 await _seed_repo(db_session)
363 resp = await client.get(f"{_url()}?author=alice&limit=1")
364 assert resp.status_code == 200
365 body = resp.text
366 # Older link should carry author=alice forward via active_filters
367 assert "author=alice" in body
File History 1 commit
sha256:a34090cc4a394a78bd72cbbe34b08cc59525141e19135b6c0ab154f10611b9ef debug(push/stream): instrument O-frame decode path with INF… Sonnet 4.6 patch 122 days ago