gabriel / musehub public
test_musehub_ui_releases_ssr.py python
198 lines 6.7 KB
c0f0b481 release: merge dev → main (#5) 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 flag renders the 'tag-prerelease' CSS class server-side."""
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 # The fragment uses the tag-prerelease CSS class for pre-release badges.
112 assert "tag-prerelease" in response.text
113 assert "v1.0-beta" in response.text
114
115
116 @pytest.mark.anyio
117 async def test_releases_list_htmx_fragment_path(
118 client: AsyncClient,
119 db_session: AsyncSession,
120 ) -> None:
121 """HX-Request: true returns a bare HTML fragment without the full page shell."""
122 repo_id = await _make_repo(db_session)
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 # Fragment must NOT include the full HTML shell.
130 assert "<html" not in response.text
131 assert "<head" not in response.text
132 # But must include release content.
133 assert "v3.0" in response.text
134
135
136 @pytest.mark.anyio
137 async def test_releases_list_empty_state_when_no_releases(
138 client: AsyncClient,
139 db_session: AsyncSession,
140 ) -> None:
141 """Empty release list renders the empty state message server-side."""
142 await _make_repo(db_session)
143 response = await client.get("/musician/ssr-album/releases")
144 assert response.status_code == 200
145 # empty_state macro renders "No releases yet" when the list is empty.
146 assert "No releases yet" in response.text
147
148
149 # ---------------------------------------------------------------------------
150 # Release detail SSR tests
151 # ---------------------------------------------------------------------------
152
153
154 @pytest.mark.anyio
155 async def test_release_detail_renders_tag_server_side(
156 client: AsyncClient,
157 db_session: AsyncSession,
158 ) -> None:
159 """Release tag appears in the detail page HTML server-side."""
160 repo_id = await _make_repo(db_session)
161 await _make_release(db_session, repo_id, tag="v1.2.3", title="Polished mix")
162 response = await client.get("/musician/ssr-album/releases/v1.2.3")
163 assert response.status_code == 200
164 assert "text/html" in response.headers["content-type"]
165 assert "v1.2.3" in response.text
166 # Title should appear too.
167 assert "Polished mix" in response.text
168
169
170 @pytest.mark.anyio
171 async def test_release_detail_shows_audio_player_container(
172 client: AsyncClient,
173 db_session: AsyncSession,
174 ) -> None:
175 """When MP3 URL is set, the audio player container div is rendered server-side."""
176 repo_id = await _make_repo(db_session)
177 await _make_release(
178 db_session,
179 repo_id,
180 tag="v1.0-audio",
181 mp3_url="https://cdn.example.com/album-v1.0.mp3",
182 )
183 response = await client.get("/musician/ssr-album/releases/v1.0-audio")
184 assert response.status_code == 200
185 # The audio player container div must be present in the HTML.
186 assert 'id="release-audio-player"' in response.text
187 assert "cdn.example.com/album-v1.0.mp3" in response.text
188
189
190 @pytest.mark.anyio
191 async def test_release_detail_unknown_tag_404(
192 client: AsyncClient,
193 db_session: AsyncSession,
194 ) -> None:
195 """Non-existent release tag returns 404."""
196 await _make_repo(db_session)
197 response = await client.get("/musician/ssr-album/releases/vNOPE")
198 assert response.status_code == 404