gabriel / musehub public
test_musehub_ui_analysis_harmony_ssr.py python
137 lines 4.7 KB
7f1d07e8 feat: domains, MCP expansion, MIDI player, and production hardening (#8) Gabriel Cardona <cgcardona@gmail.com> 4d ago
1 """SSR tests for the harmony analysis page migration (issue #585).
2
3 Verifies that harmony_analysis_page renders Roman-numeral chord events,
4 cadences, and modulations server-side using a Jinja2 template rather than
5 the deleted _render_harmony_html() inline Python HTML builder.
6
7 Tests
8 -----
9 - test_harmony_page_uses_jinja2_template
10 GET page → assert Jinja2 template (not inline HTML builder) renders it.
11 - test_harmony_page_renders_chord_frequency_server_side
12 GET page → assert at least one Roman numeral chord symbol in HTML.
13 - test_harmony_page_no_python_html_builder_in_module
14 Assert _render_harmony_html no longer exists in ui.py source.
15 - test_harmony_page_htmx_fragment_path
16 GET with HX-Request:true → fragment only (no <html>).
17 - test_harmony_page_renders_functional_categories
18 GET page → assert a tonal function label appears in the HTML.
19 """
20 from __future__ import annotations
21
22 import inspect
23
24 import pytest
25 from httpx import AsyncClient
26 from sqlalchemy.ext.asyncio import AsyncSession
27
28 from musehub.db.musehub_models import MusehubRepo
29
30
31 # ---------------------------------------------------------------------------
32 # Helpers
33 # ---------------------------------------------------------------------------
34
35 _ANALYSIS_REF = "cafebeef00112233"
36 _OWNER = "harmonyuser"
37 _SLUG = "harmony-test-repo"
38 _BASE = f"/{_OWNER}/{_SLUG}"
39 _HARMONY_URL = f"{_BASE}/insights/{_ANALYSIS_REF}/harmony"
40
41
42 async def _make_repo(db_session: AsyncSession) -> str:
43 """Seed a minimal repo and return its repo_id."""
44 repo = MusehubRepo(
45 name=_SLUG,
46 owner=_OWNER,
47 slug=_SLUG,
48 visibility="private",
49 owner_user_id="harmony-owner",
50 )
51 db_session.add(repo)
52 await db_session.commit()
53 await db_session.refresh(repo)
54 return str(repo.repo_id)
55
56
57 # ---------------------------------------------------------------------------
58 # Tests
59 # ---------------------------------------------------------------------------
60
61
62 @pytest.mark.anyio
63 async def test_harmony_page_uses_jinja2_template(
64 client: AsyncClient,
65 db_session: AsyncSession,
66 ) -> None:
67 """GET harmony page returns 200 HTML rendered by Jinja2, not the inline builder."""
68 await _make_repo(db_session)
69 response = await client.get(_HARMONY_URL)
70 assert response.status_code == 200
71 assert "text/html" in response.headers["content-type"]
72 body = response.text
73 # Jinja2-rendered pages include the base layout's <html> tag
74 assert "<html" in body
75 # Harmony analysis heading must be present (rendered by template, not by JS)
76 assert "Harmony Analysis" in body
77
78
79 @pytest.mark.anyio
80 async def test_harmony_page_renders_chord_frequency_server_side(
81 client: AsyncClient,
82 db_session: AsyncSession,
83 ) -> None:
84 """GET harmony page renders at least one Roman numeral chord symbol in HTML."""
85 await _make_repo(db_session)
86 response = await client.get(_HARMONY_URL)
87 assert response.status_code == 200
88 body = response.text
89 # Roman numerals are rendered as chord labels; at minimum "I" must appear
90 roman_symbols = ["I", "II", "III", "IV", "V", "VI", "VII"]
91 assert any(sym in body for sym in roman_symbols), (
92 "Expected at least one Roman numeral chord symbol in server-rendered harmony page"
93 )
94
95
96 @pytest.mark.anyio
97 async def test_harmony_page_no_python_html_builder_in_module(
98 client: AsyncClient,
99 db_session: AsyncSession,
100 ) -> None:
101 """_render_harmony_html must not exist in the musehub ui module."""
102 import musehub.api.routes.musehub.ui as ui_module
103
104 source = inspect.getsource(ui_module)
105 assert "_render_harmony_html" not in source, (
106 "_render_harmony_html still exists in ui.py — the inline HTML builder was not removed"
107 )
108
109
110 @pytest.mark.anyio
111 async def test_harmony_page_htmx_fragment_path(
112 client: AsyncClient,
113 db_session: AsyncSession,
114 ) -> None:
115 """GET harmony page with HX-Request:true returns fragment (no <html> wrapper)."""
116 await _make_repo(db_session)
117 response = await client.get(_HARMONY_URL, headers={"HX-Request": "true"})
118 assert response.status_code == 200
119 body = response.text
120 assert "<html" not in body
121 assert "Harmony Analysis" in body
122
123
124 @pytest.mark.anyio
125 async def test_harmony_page_renders_functional_categories(
126 client: AsyncClient,
127 db_session: AsyncSession,
128 ) -> None:
129 """GET harmony page renders at least one tonal function label server-side."""
130 await _make_repo(db_session)
131 response = await client.get(_HARMONY_URL)
132 assert response.status_code == 200
133 body = response.text
134 tonal_functions = ["tonic", "dominant", "subdominant", "pre-dominant", "secondary-dominant"]
135 assert any(fn in body for fn in tonal_functions), (
136 "Expected at least one tonal function label in server-rendered harmony page"
137 )