gabriel / musehub public
test_musehub_ui_releases_ssr.py python
178 lines 6.3 KB
Raw
sha256:34035d72cef530c1ab9d6a6f53be18d803dde705fc3157617d70352a96d0747b Merge branch 'fix/wire-push-external-parent-manifest' into dev Human 8 days 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 datetime import datetime, timezone
22 from httpx import AsyncClient
23 from sqlalchemy.ext.asyncio import AsyncSession
24
25 from muse.core.types import now_utc_iso
26 from musehub.core.genesis import compute_identity_id, compute_release_id, compute_repo_id
27 from musehub.db.musehub_release_models import MusehubRelease
28 from musehub.db.musehub_repo_models import MusehubRepo
29
30
31 # ---------------------------------------------------------------------------
32 # Seed helpers
33 # ---------------------------------------------------------------------------
34
35
36 async def _make_repo(db: AsyncSession, owner: str = "musician", slug: str = "ssr-album") -> str:
37 """Seed a public repo and return its repo_id string."""
38 created_at = datetime.now(tz=timezone.utc)
39 owner_id = compute_identity_id(owner.encode())
40 repo = MusehubRepo(
41 repo_id=compute_repo_id(owner_id, slug, "code", created_at.isoformat()),
42 name=slug,
43 owner=owner,
44 slug=slug,
45 visibility="public",
46 owner_user_id=owner_id,
47 created_at=created_at,
48 updated_at=created_at,
49 )
50 db.add(repo)
51 await db.commit()
52 await db.refresh(repo)
53 return str(repo.repo_id)
54
55
56 async def _make_release(
57 db: AsyncSession,
58 repo_id: str,
59 *,
60 tag: str = "v1.0",
61 title: str = "Version 1.0",
62 body: str = "Release notes here.",
63 author: str = "musician",
64 channel: str = "stable",
65 is_draft: bool = False,
66 mp3_url: str | None = None,
67 ) -> MusehubRelease:
68 """Seed a release and return the ORM object."""
69 download_urls = {}
70 if mp3_url:
71 download_urls["mp3"] = mp3_url
72 release = MusehubRelease(
73 release_id=compute_release_id(repo_id, tag, now_utc_iso()),
74 repo_id=repo_id,
75 tag=tag,
76 title=title,
77 body=body,
78 author=author,
79 channel=channel,
80 is_draft=is_draft,
81 download_urls=download_urls,
82 )
83 db.add(release)
84 await db.commit()
85 await db.refresh(release)
86 return release
87
88
89 # ---------------------------------------------------------------------------
90 # Releases list SSR tests
91 # ---------------------------------------------------------------------------
92
93
94 async def test_releases_list_renders_tag_server_side(
95 client: AsyncClient,
96 db_session: AsyncSession,
97 ) -> None:
98 """Release tag is in the HTML response server-side (no JS required)."""
99 repo_id = await _make_repo(db_session)
100 await _make_release(db_session, repo_id, tag="v2.5.0", title="Groove update 2.5")
101 response = await client.get("/musician/ssr-album/releases")
102 assert response.status_code == 200
103 assert "text/html" in response.headers["content-type"]
104 assert "v2.5.0" in response.text
105
106
107 async def test_releases_list_shows_prerelease_badge(
108 client: AsyncClient,
109 db_session: AsyncSession,
110 ) -> None:
111 """Pre-release badge renders server-side for releases flagged as pre-release."""
112 repo_id = await _make_repo(db_session)
113 await _make_release(db_session, repo_id, tag="v1.0-beta", channel="beta")
114 response = await client.get("/musician/ssr-album/releases")
115 assert response.status_code == 200
116 assert "Pre-release" in response.text
117 assert "v1.0-beta" in response.text
118
119
120 async def test_releases_list_htmx_fragment_path(
121 client: AsyncClient,
122 db_session: AsyncSession,
123 ) -> None:
124 """HX-Request: true returns a bare HTML fragment without the full page shell."""
125 repo_id = await _make_repo(db_session)
126 await _make_release(db_session, repo_id, tag="v2.0", title="Earlier release")
127 await _make_release(db_session, repo_id, tag="v3.0", title="Major release")
128 response = await client.get(
129 "/musician/ssr-album/releases",
130 headers={"HX-Request": "true"},
131 )
132 assert response.status_code == 200
133 assert "<html" not in response.text
134 assert "<head" not in response.text
135 assert "v2.0" in response.text
136
137
138 async def test_releases_list_empty_state_when_no_releases(
139 client: AsyncClient,
140 db_session: AsyncSession,
141 ) -> None:
142 """Empty release list renders the empty state message server-side."""
143 await _make_repo(db_session)
144 response = await client.get("/musician/ssr-album/releases")
145 assert response.status_code == 200
146 # empty_state macro renders "No releases yet" when the list is empty.
147 assert "No releases yet" in response.text
148
149
150 # ---------------------------------------------------------------------------
151 # Release detail SSR tests
152 # ---------------------------------------------------------------------------
153
154
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
171 async def test_release_detail_unknown_tag_404(
172 client: AsyncClient,
173 db_session: AsyncSession,
174 ) -> None:
175 """Non-existent release tag returns 404."""
176 await _make_repo(db_session)
177 response = await client.get("/musician/ssr-album/releases/vNOPE")
178 assert response.status_code == 404
File History 14 commits
sha256:34035d72cef530c1ab9d6a6f53be18d803dde705fc3157617d70352a96d0747b Merge branch 'fix/wire-push-external-parent-manifest' into dev Human 8 days ago
sha256:fc04e4cae9e1774d6a21b65c45daeed0e6787eb581d13aa1b03bfe9384a34226 Merge branch 'fix/two-column-scroll-layout' into dev Human 8 days ago
sha256:408916fc5973ba59c6e4eebaa80ebdcc801c0a63205651e25009d11548f79454 chore: bump version to 0.2.0.dev2 — nightly.2, matching muse Sonnet 4.6 patch 11 days ago
sha256:d035733f21ccff27735fddebfbbe0ed24565a32a22db8de5885402262671ecd2 chore: bump version to 0.2.0rc15 for musehub#113 fix release Sonnet 4.6 patch 14 days ago
sha256:0032d6cfa33bc3c8367436ad768e7dd0e339b4332153160247da8266cb5fa352 Merge branch 'task/version-tags-phase3-server' into dev Human 17 days ago
sha256:4669620efda9ff41c55bdefd1f7bfe1c239d468428744c84ead9957e5a003a53 merge: rescue snapshot-recovery hardening (c00aa21d) into d… Opus 4.8 minor 29 days ago
sha256:a59da49c4611b970fc4b6ae48678ce4943261c213a07ddbd73ce9201df869b4a fix: remove false-positive proposal_comments index drop fro… Sonnet 4.6 patch 33 days ago
sha256:0a240d6dbff234f07d98a28a4a9a68db702f3f9ff9260196f24219bdb1c0b6f3 feat: render markdown mists as HTML with heading anchor links Sonnet 4.6 patch 34 days ago
sha256:24a7d47486ebc4ebd1832830580e177ec6f877b48dced8c000e198cdec4ce9d6 Merge 'task/bump-version-rc12' into 'dev' — proposal: Bump … Human 35 days ago
sha256:b9ff931d147e0114a1f17060f415b89ed551c170a91ff226c70437aa5c85f9ee Merge 'task/bump-version-rc12' into 'dev' — proposal: Bump … Human 35 days ago
sha256:d1122d21e73471879b460037b22c0b50fded7c423444a176f248428f75dac39c Merge 'task/fix-issue-pagination-cursor' into 'dev' — propo… Human 35 days ago
sha256:01e18975e73d2b3cd5b6db7929c895bef9aa6e0d4391dc5b2adfc548b41318dd Merge 'feat/adding-debug-logs-to-staging' into 'dev' — prop… Human 35 days ago
sha256:6b1949fc2797ca4c1936a637a4cbfec828ef56cf52398a2e74ca3c4f494e728f fix: use wire_bytes not mpack_bytes_raw in compute_object_b… Sonnet 4.6 patch 48 days ago
sha256:b99f2455dc346966d040133f5203297e6e3ef5803a93728a2c30568d0a0f7583 rename: delta_add → delta_upsert across wire format, models… Sonnet 4.6 patch 50 days ago