gabriel / musehub public
test_musehub_ui_commits_ssr.py python
204 lines 6.5 KB
Raw
sha256:9590cee1e0ccd6c76528f005b95d634d80f5019f0dcb7c371e149adc31d1fb65 refactor: enforce gRPC framing on all MWP wire traffic Sonnet 4.6 minor ⚠ breaking 156 days ago
1 """SSR + HTMX fragment tests for the MuseHub commits list page — issue #570.
2
3 Validates that commit data is rendered server-side into HTML (no JS required)
4 and that HTMX fragment requests return bare HTML without the full page shell.
5
6 Covers GET /{owner}/{repo_slug}/commits:
7
8 - test_commits_page_renders_commit_message_server_side
9 Seed a commit; its message appears in the response HTML.
10
11 - test_commits_page_filter_form_has_hx_get
12 The filter form has hx-get attribute pointing at the commits URL.
13
14 - test_commits_page_fragment_on_htmx_request
15 GET with HX-Request: true returns a bare fragment (no <html>/<head> shell).
16
17 - test_commits_page_author_filter_narrows_results
18 ?author=alice shows only Alice's commits; Bob's are absent.
19
20 - test_commits_page_pagination_renders_next
21 More than per_page commits → "Older →" link present in the response.
22 """
23 from __future__ import annotations
24
25 import uuid
26 from datetime import datetime, timezone
27
28 import pytest
29 from httpx import AsyncClient
30 from sqlalchemy.ext.asyncio import AsyncSession
31
32 from musehub.db.musehub_models import MusehubBranch, MusehubCommit, MusehubRepo
33
34 # ── Constants ──────────────────────────────────────────────────────────────────
35
36 _OWNER = "ssr570owner"
37 _SLUG = "ssr570-commits"
38 _SHA_ALICE = "aa" + "0" * 38
39 _SHA_BOB = "bb" + "0" * 38
40
41
42 # ── Seed helpers ───────────────────────────────────────────────────────────────
43
44
45 async def _seed_repo(db: AsyncSession) -> str:
46 """Seed a public repo and return its repo_id string."""
47 repo = MusehubRepo(
48 repo_id=str(uuid.uuid4()),
49 name=_SLUG,
50 owner=_OWNER,
51 slug=_SLUG,
52 visibility="public",
53 owner_user_id=str(uuid.uuid4()),
54 )
55 db.add(repo)
56 await db.flush()
57 return str(repo.repo_id)
58
59
60 async def _seed_commit(
61 db: AsyncSession,
62 repo_id: str,
63 *,
64 commit_id: str | None = None,
65 author: str = "alice",
66 message: str = "Test commit message",
67 branch: str = "main",
68 timestamp: datetime | None = None,
69 ) -> MusehubCommit:
70 """Seed a commit row and return the ORM object."""
71 cid = commit_id or (uuid.uuid4().hex + uuid.uuid4().hex)[:40]
72 ts = timestamp or datetime.now(timezone.utc)
73 commit = MusehubCommit(
74 commit_id=cid,
75 repo_id=repo_id,
76 branch=branch,
77 parent_ids=[],
78 message=message,
79 author=author,
80 timestamp=ts,
81 snapshot_id=None,
82 )
83 db.add(commit)
84 await db.flush()
85 return commit
86
87
88 async def _seed_branch(db: AsyncSession, repo_id: str, head_id: str, name: str = "main") -> None:
89 """Seed a branch row."""
90 db.add(MusehubBranch(repo_id=repo_id, name=name, head_commit_id=head_id))
91 await db.flush()
92
93
94 # ── Tests ──────────────────────────────────────────────────────────────────────
95
96
97 async def test_commits_page_renders_commit_message_server_side(
98 client: AsyncClient,
99 db_session: AsyncSession,
100 ) -> None:
101 """Commit message is present in the HTML response — no client JS required."""
102 repo_id = await _seed_repo(db_session)
103 await _seed_commit(
104 db_session, repo_id, message="Bassline groove at 120 BPM feels right"
105 )
106 await db_session.commit()
107
108 response = await client.get(f"/{_OWNER}/{_SLUG}/commits")
109
110 assert response.status_code == 200
111 assert "text/html" in response.headers["content-type"]
112 assert "Bassline groove at 120 BPM feels right" in response.text
113
114
115 async def test_commits_page_filter_form_has_hx_get(
116 client: AsyncClient,
117 db_session: AsyncSession,
118 ) -> None:
119 """The filter form carries hx-get so HTMX intercepts submissions."""
120 repo_id = await _seed_repo(db_session)
121 await _seed_commit(db_session, repo_id)
122 await db_session.commit()
123
124 response = await client.get(f"/{_OWNER}/{_SLUG}/commits")
125
126 assert response.status_code == 200
127 assert "hx-get" in response.text
128
129
130 async def test_commits_page_fragment_on_htmx_request(
131 client: AsyncClient,
132 db_session: AsyncSession,
133 ) -> None:
134 """HX-Request: true returns a bare HTML fragment without the full page shell."""
135 repo_id = await _seed_repo(db_session)
136 await _seed_commit(
137 db_session, repo_id, message="Fragment-only commit row"
138 )
139 await db_session.commit()
140
141 response = await client.get(
142 f"/{_OWNER}/{_SLUG}/commits",
143 headers={"HX-Request": "true"},
144 )
145
146 assert response.status_code == 200
147 # No full-page HTML shell in a fragment response.
148 assert "<html" not in response.text
149 assert "<head" not in response.text
150 # The commit content must still be present.
151 assert "Fragment-only commit row" in response.text
152
153
154 async def test_commits_page_author_filter_narrows_results(
155 client: AsyncClient,
156 db_session: AsyncSession,
157 ) -> None:
158 """?author=alice includes only Alice's commits; Bob's message is absent."""
159 repo_id = await _seed_repo(db_session)
160 await _seed_commit(
161 db_session, repo_id,
162 commit_id=_SHA_ALICE,
163 author="alice",
164 message="Alice lays down the bass",
165 )
166 await _seed_commit(
167 db_session, repo_id,
168 commit_id=_SHA_BOB,
169 author="bob",
170 message="Bob adds a reverb tail",
171 )
172 await db_session.commit()
173
174 response = await client.get(
175 f"/{_OWNER}/{_SLUG}/commits?author=alice"
176 )
177
178 assert response.status_code == 200
179 body = response.text
180 assert "Alice lays down the bass" in body
181 assert "Bob adds a reverb tail" not in body
182
183
184 async def test_commits_page_pagination_renders_next(
185 client: AsyncClient,
186 db_session: AsyncSession,
187 ) -> None:
188 """When total commits exceed per_page, the 'Older →' pagination link appears."""
189 repo_id = await _seed_repo(db_session)
190 # Seed 35 commits — more than the default per_page=30.
191 for i in range(35):
192 cid = f"{i:040x}"
193 await _seed_commit(
194 db_session, repo_id,
195 commit_id=cid,
196 message=f"Commit number {i}",
197 )
198 await db_session.commit()
199
200 response = await client.get(f"/{_OWNER}/{_SLUG}/commits")
201
202 assert response.status_code == 200
203 # "Older →" appears as an anchor when there is a next page.
204 assert "Older" in response.text
File History 1 commit
sha256:9590cee1e0ccd6c76528f005b95d634d80f5019f0dcb7c371e149adc31d1fb65 refactor: enforce gRPC framing on all MWP wire traffic Sonnet 4.6 minor ⚠ 156 days ago