gabriel / musehub public
test_musehub_ui_releases_ssr.py python
196 lines 6.6 KB
04faf0e3 feat: supercharge all repo pages, enforce separation of concerns Gabriel Cardona <cgcardona@gmail.com> 5d ago
1 """SSR tests for MuseHub releases UI pages — issue #572.
2
3 Validates that release data is rendered server-side into HTML (not deferred
4 to client JS) and that HTMX fragment requests return bare HTML without the
5 full page shell.
6
7 Covers GET /{owner}/{repo_slug}/releases:
8 - test_releases_list_renders_tag_server_side — release tag in HTML
9 - test_releases_list_shows_prerelease_badge — pre-release badge in HTML
10 - test_releases_list_htmx_fragment_path — HX-Request: true → bare fragment
11 - test_releases_list_empty_state_when_no_releases — no releases → empty state
12
13 Covers GET /{owner}/{repo_slug}/releases/{tag}:
14 - test_release_detail_renders_tag_server_side — tag in HTML
15 - test_release_detail_shows_audio_player_container — audioUrl → #release-audio-player div
16 - test_release_detail_unknown_tag_404 — unknown tag → 404
17 """
18 from __future__ import annotations
19
20 import pytest
21 from httpx import AsyncClient
22 from sqlalchemy.ext.asyncio import AsyncSession
23
24 from musehub.db.musehub_models import (
25 MusehubRelease,
26 MusehubRepo,
27 )
28
29
30 # ---------------------------------------------------------------------------
31 # Seed helpers
32 # ---------------------------------------------------------------------------
33
34
35 async def _make_repo(db: AsyncSession, owner: str = "musician", slug: str = "ssr-album") -> str:
36 """Seed a public repo and return its repo_id string."""
37 repo = MusehubRepo(
38 name=slug,
39 owner=owner,
40 slug=slug,
41 visibility="public",
42 owner_user_id="uid-ssr-musician",
43 )
44 db.add(repo)
45 await db.commit()
46 await db.refresh(repo)
47 return str(repo.repo_id)
48
49
50 async def _make_release(
51 db: AsyncSession,
52 repo_id: str,
53 *,
54 tag: str = "v1.0",
55 title: str = "Version 1.0",
56 body: str = "Release notes here.",
57 author: str = "musician",
58 is_prerelease: bool = False,
59 is_draft: bool = False,
60 mp3_url: str | None = None,
61 ) -> MusehubRelease:
62 """Seed a release and return the ORM object."""
63 download_urls: dict[str, str] = {}
64 if mp3_url:
65 download_urls["mp3"] = mp3_url
66 release = MusehubRelease(
67 repo_id=repo_id,
68 tag=tag,
69 title=title,
70 body=body,
71 author=author,
72 is_prerelease=is_prerelease,
73 is_draft=is_draft,
74 download_urls=download_urls,
75 )
76 db.add(release)
77 await db.commit()
78 await db.refresh(release)
79 return release
80
81
82 # ---------------------------------------------------------------------------
83 # Releases list SSR tests
84 # ---------------------------------------------------------------------------
85
86
87 @pytest.mark.anyio
88 async def test_releases_list_renders_tag_server_side(
89 client: AsyncClient,
90 db_session: AsyncSession,
91 ) -> None:
92 """Release tag is in the HTML response server-side (no JS required)."""
93 repo_id = await _make_repo(db_session)
94 await _make_release(db_session, repo_id, tag="v2.5.0", title="Groove update 2.5")
95 response = await client.get("/musician/ssr-album/releases")
96 assert response.status_code == 200
97 assert "text/html" in response.headers["content-type"]
98 assert "v2.5.0" in response.text
99
100
101 @pytest.mark.anyio
102 async def test_releases_list_shows_prerelease_badge(
103 client: AsyncClient,
104 db_session: AsyncSession,
105 ) -> None:
106 """Pre-release badge renders server-side for releases flagged as pre-release."""
107 repo_id = await _make_repo(db_session)
108 await _make_release(db_session, repo_id, tag="v1.0-beta", is_prerelease=True)
109 response = await client.get("/musician/ssr-album/releases")
110 assert response.status_code == 200
111 assert "Pre-release" in response.text
112 assert "v1.0-beta" in response.text
113
114
115 @pytest.mark.anyio
116 async def test_releases_list_htmx_fragment_path(
117 client: AsyncClient,
118 db_session: AsyncSession,
119 ) -> None:
120 """HX-Request: true returns a bare HTML fragment without the full page shell."""
121 repo_id = await _make_repo(db_session)
122 await _make_release(db_session, repo_id, tag="v2.0", title="Earlier release")
123 await _make_release(db_session, repo_id, tag="v3.0", title="Major release")
124 response = await client.get(
125 "/musician/ssr-album/releases",
126 headers={"HX-Request": "true"},
127 )
128 assert response.status_code == 200
129 assert "<html" not in response.text
130 assert "<head" not in response.text
131 assert "v2.0" in response.text
132
133
134 @pytest.mark.anyio
135 async def test_releases_list_empty_state_when_no_releases(
136 client: AsyncClient,
137 db_session: AsyncSession,
138 ) -> None:
139 """Empty release list renders the empty state message server-side."""
140 await _make_repo(db_session)
141 response = await client.get("/musician/ssr-album/releases")
142 assert response.status_code == 200
143 # empty_state macro renders "No releases yet" when the list is empty.
144 assert "No releases yet" in response.text
145
146
147 # ---------------------------------------------------------------------------
148 # Release detail SSR tests
149 # ---------------------------------------------------------------------------
150
151
152 @pytest.mark.anyio
153 async def test_release_detail_renders_tag_server_side(
154 client: AsyncClient,
155 db_session: AsyncSession,
156 ) -> None:
157 """Release tag appears in the detail page HTML server-side."""
158 repo_id = await _make_repo(db_session)
159 await _make_release(db_session, repo_id, tag="v1.2.3", title="Polished mix")
160 response = await client.get("/musician/ssr-album/releases/v1.2.3")
161 assert response.status_code == 200
162 assert "text/html" in response.headers["content-type"]
163 assert "v1.2.3" in response.text
164 # Title should appear too.
165 assert "Polished mix" in response.text
166
167
168 @pytest.mark.anyio
169 async def test_release_detail_shows_audio_player_container(
170 client: AsyncClient,
171 db_session: AsyncSession,
172 ) -> None:
173 """When MP3 URL is set, the audio player container div is rendered server-side."""
174 repo_id = await _make_repo(db_session)
175 await _make_release(
176 db_session,
177 repo_id,
178 tag="v1.0-audio",
179 mp3_url="https://cdn.example.com/album-v1.0.mp3",
180 )
181 response = await client.get("/musician/ssr-album/releases/v1.0-audio")
182 assert response.status_code == 200
183 # The audio player container div must be present in the HTML.
184 assert 'id="release-audio-player"' in response.text
185 assert "cdn.example.com/album-v1.0.mp3" in response.text
186
187
188 @pytest.mark.anyio
189 async def test_release_detail_unknown_tag_404(
190 client: AsyncClient,
191 db_session: AsyncSession,
192 ) -> None:
193 """Non-existent release tag returns 404."""
194 await _make_repo(db_session)
195 response = await client.get("/musician/ssr-album/releases/vNOPE")
196 assert response.status_code == 404