gabriel / musehub public
test_musehub_ui_canvas_ssr.py python
208 lines 6.8 KB
7f1d07e8 feat: domains, MCP expansion, MIDI player, and production hardening (#8) Gabriel Cardona <cgcardona@gmail.com> 4d ago
1 """Tests for SSR scaffolding on the piano roll and score pages (issue #581).
2
3 Verifies that the piano roll and score page handlers populate server-side
4 context — track name, instrument sidebar, transport bar, canvas data
5 attributes, and score metadata — without requiring JavaScript execution.
6
7 Covers:
8 - test_piano_roll_page_renders_track_name_server_side
9 - test_piano_roll_page_renders_instrument_sidebar
10 - test_piano_roll_page_canvas_has_data_midi_url
11 - test_piano_roll_page_transport_bar_present
12 - test_piano_roll_track_page_canvas_has_data_instruments
13 - test_score_page_renders_title_server_side
14 - test_score_page_score_container_has_data_abc_url
15 - test_score_page_no_blank_shell
16 """
17 from __future__ import annotations
18
19 import pytest
20 from httpx import AsyncClient
21 from sqlalchemy.ext.asyncio import AsyncSession
22
23 from musehub.db.musehub_models import MusehubObject, MusehubRepo
24
25
26 # ---------------------------------------------------------------------------
27 # Helpers
28 # ---------------------------------------------------------------------------
29
30
31 async def _make_repo(db_session: AsyncSession) -> str:
32 """Seed a minimal repo and return its repo_id."""
33 repo = MusehubRepo(
34 name="canvas-test-beats",
35 owner="canvasuser",
36 slug="canvas-test-beats",
37 visibility="private",
38 owner_user_id="canvas-owner",
39 )
40 db_session.add(repo)
41 await db_session.commit()
42 await db_session.refresh(repo)
43 return str(repo.repo_id)
44
45
46 async def _seed_midi_object(
47 db_session: AsyncSession,
48 repo_id: str,
49 path: str = "tracks/bass.mid",
50 size_bytes: int = 4096,
51 ) -> MusehubObject:
52 """Seed a MIDI object into the repo and return it."""
53 obj = MusehubObject(
54 object_id=f"sha256:{'a' * 64}_{path.replace('/', '_')}",
55 repo_id=repo_id,
56 path=path,
57 size_bytes=size_bytes,
58 disk_path=f"/data/{path}",
59 )
60 db_session.add(obj)
61 await db_session.commit()
62 await db_session.refresh(obj)
63 return obj
64
65
66 # ---------------------------------------------------------------------------
67 # Piano roll SSR tests
68 # ---------------------------------------------------------------------------
69
70
71 @pytest.mark.anyio
72 async def test_piano_roll_page_renders_track_name_server_side(
73 client: AsyncClient,
74 db_session: AsyncSession,
75 ) -> None:
76 """Piano roll track page renders the track name from the path in SSR HTML."""
77 repo_id = await _make_repo(db_session)
78 await _seed_midi_object(db_session, repo_id, path="tracks/bass.mid")
79 response = await client.get(
80 "/canvasuser/canvas-test-beats/view/main/tracks/bass.mid"
81 )
82 assert response.status_code == 200
83 assert "text/html" in response.headers["content-type"]
84 body = response.text
85 # Track name derived from path stem ("bass" → "Bass")
86 assert "Bass" in body or "bass.mid" in body or "bass" in body.lower()
87
88
89 @pytest.mark.anyio
90 async def test_piano_roll_page_renders_instrument_sidebar(
91 client: AsyncClient,
92 db_session: AsyncSession,
93 ) -> None:
94 """View page renders successfully for a repo with MIDI objects."""
95 repo_id = await _make_repo(db_session)
96 await _seed_midi_object(db_session, repo_id, path="tracks/keys.mid")
97 response = await client.get(
98 "/canvasuser/canvas-test-beats/view/main"
99 )
100 assert response.status_code == 200
101 body = response.text
102 # Domain viewer renders SSR HTML
103 assert "view-container" in body
104
105
106 @pytest.mark.anyio
107 async def test_piano_roll_page_canvas_has_data_midi_url(
108 client: AsyncClient,
109 db_session: AsyncSession,
110 ) -> None:
111 """View page embeds the viewerType in its page config JSON."""
112 await _make_repo(db_session)
113 response = await client.get(
114 "/canvasuser/canvas-test-beats/view/main"
115 )
116 assert response.status_code == 200
117 assert "viewerType" in response.text
118
119
120 @pytest.mark.anyio
121 async def test_piano_roll_page_transport_bar_present(
122 client: AsyncClient,
123 db_session: AsyncSession,
124 ) -> None:
125 """View page renders the domain viewer container for any ref."""
126 await _make_repo(db_session)
127 response = await client.get(
128 "/canvasuser/canvas-test-beats/view/main"
129 )
130 assert response.status_code == 200
131 assert "view-container" in response.text
132
133
134 @pytest.mark.anyio
135 async def test_piano_roll_track_page_canvas_has_data_instruments(
136 client: AsyncClient,
137 db_session: AsyncSession,
138 ) -> None:
139 """File view page embeds the file path in the page config JSON."""
140 repo_id = await _make_repo(db_session)
141 await _seed_midi_object(db_session, repo_id, path="tracks/guitar.mid")
142 response = await client.get(
143 "/canvasuser/canvas-test-beats/view/main/tracks/guitar.mid"
144 )
145 assert response.status_code == 200
146 body = response.text
147 assert "guitar.mid" in body
148
149
150 # ---------------------------------------------------------------------------
151 # Score page SSR tests
152 # ---------------------------------------------------------------------------
153
154
155 @pytest.mark.anyio
156 async def test_score_page_renders_title_server_side(
157 client: AsyncClient,
158 db_session: AsyncSession,
159 ) -> None:
160 """Score part page renders a title derived from the path in SSR HTML."""
161 repo_id = await _make_repo(db_session)
162 await _seed_midi_object(db_session, repo_id, path="tracks/melody.mid")
163 response = await client.get(
164 "/canvasuser/canvas-test-beats/score/main/tracks/melody.mid"
165 )
166 assert response.status_code == 200
167 body = response.text
168 # Title derived from path stem ("melody" → "Melody")
169 assert "Melody" in body or "melody" in body.lower()
170
171
172 @pytest.mark.anyio
173 async def test_score_page_score_container_has_data_abc_url(
174 client: AsyncClient,
175 db_session: AsyncSession,
176 ) -> None:
177 """Score page includes a #score-container with a data-abc-url for JS."""
178 await _make_repo(db_session)
179 response = await client.get(
180 "/canvasuser/canvas-test-beats/score/main"
181 )
182 assert response.status_code == 200
183 body = response.text
184 assert "score-container" in body
185 assert "data-abc-url" in body
186
187
188 @pytest.mark.anyio
189 async def test_score_page_no_blank_shell(
190 client: AsyncClient,
191 db_session: AsyncSession,
192 ) -> None:
193 """Score page renders meaningful content without JS execution.
194
195 The page must include the metadata header and score container in SSR,
196 not just a blank shell that depends entirely on a client fetch.
197 """
198 repo_id = await _make_repo(db_session)
199 await _seed_midi_object(db_session, repo_id, path="tracks/piano.mid")
200 response = await client.get(
201 "/canvasuser/canvas-test-beats/score/main"
202 )
203 assert response.status_code == 200
204 body = response.text
205 # Score header is present — not a blank loading spinner as the only content
206 assert "score-container" in body or "score-meta" in body or "Score" in body
207 # The entire body is not just a loading placeholder
208 assert body.count("Loading") < 5