gabriel / musehub public
test_musehub_ui_releases_ssr.py python
193 lines 6.4 KB
0bfb4569 add agent rules: Muse-only VCS, no Git/GitHub gabriel 9h 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 channel: str = "stable",
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 channel=channel,
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 """Channel badge renders server-side for non-stable releases."""
107 repo_id = await _make_repo(db_session)
108 await _make_release(db_session, repo_id, tag="v1.0-beta", channel="beta")
109 response = await client.get("/musician/ssr-album/releases")
110 assert response.status_code == 200
111 assert "v1.0-beta" in response.text
112
113
114 @pytest.mark.anyio
115 async def test_releases_list_htmx_fragment_path(
116 client: AsyncClient,
117 db_session: AsyncSession,
118 ) -> None:
119 """HX-Request: true returns a bare HTML fragment without the full page shell."""
120 repo_id = await _make_repo(db_session)
121 await _make_release(db_session, repo_id, tag="v2.0", title="Earlier release")
122 await _make_release(db_session, repo_id, tag="v3.0", title="Major release")
123 response = await client.get(
124 "/musician/ssr-album/releases",
125 headers={"HX-Request": "true"},
126 )
127 assert response.status_code == 200
128 assert "<html" not in response.text
129 assert "<head" not in response.text
130 assert "v2.0" in response.text
131
132
133 @pytest.mark.anyio
134 async def test_releases_list_empty_state_when_no_releases(
135 client: AsyncClient,
136 db_session: AsyncSession,
137 ) -> None:
138 """Empty release list renders the empty state message server-side."""
139 await _make_repo(db_session)
140 response = await client.get("/musician/ssr-album/releases")
141 assert response.status_code == 200
142 # empty_state macro renders "No releases yet" when the list is empty.
143 assert "No releases yet" in response.text
144
145
146 # ---------------------------------------------------------------------------
147 # Release detail SSR tests
148 # ---------------------------------------------------------------------------
149
150
151 @pytest.mark.anyio
152 async def test_release_detail_renders_tag_server_side(
153 client: AsyncClient,
154 db_session: AsyncSession,
155 ) -> None:
156 """Release tag appears in the detail page HTML server-side."""
157 repo_id = await _make_repo(db_session)
158 await _make_release(db_session, repo_id, tag="v1.2.3", title="Polished mix")
159 response = await client.get("/musician/ssr-album/releases/v1.2.3")
160 assert response.status_code == 200
161 assert "text/html" in response.headers["content-type"]
162 assert "v1.2.3" in response.text
163 # Title should appear too.
164 assert "Polished mix" in response.text
165
166
167 @pytest.mark.anyio
168 async def test_release_detail_shows_header_card(
169 client: AsyncClient,
170 db_session: AsyncSession,
171 ) -> None:
172 """Release detail page renders the header card with tag and channel."""
173 repo_id = await _make_repo(db_session)
174 await _make_release(
175 db_session,
176 repo_id,
177 tag="v1.0-audio",
178 )
179 response = await client.get("/musician/ssr-album/releases/v1.0-audio")
180 assert response.status_code == 200
181 assert "rd-header" in response.text
182 assert "v1.0-audio" in response.text
183
184
185 @pytest.mark.anyio
186 async def test_release_detail_unknown_tag_404(
187 client: AsyncClient,
188 db_session: AsyncSession,
189 ) -> None:
190 """Non-existent release tag returns 404."""
191 await _make_repo(db_session)
192 response = await client.get("/musician/ssr-album/releases/vNOPE")
193 assert response.status_code == 404