gabriel / musehub public
test_musehub_ui_labels_ssr.py python
185 lines 6.1 KB
Raw
sha256:a10adeeb7a0169cb9900f9806ed7a973047258abb6283724fe55e8eb68ff3f0a init: musehub initial commit Human 171 days ago
1 """SSR tests for MuseHub labels management UI — issue #557.
2
3 Verifies that the labels page renders data server-side in Jinja2 templates
4 without requiring JavaScript execution. Tests assert on HTML content directly
5 returned by the server, not on JavaScript rendering logic.
6
7 Covers GET /{owner}/{repo_slug}/labels:
8 - test_labels_page_renders_label_name_server_side
9 - test_labels_page_shows_issue_count_server_side
10 - test_labels_page_fragment_on_htmx_request
11
12 Covers POST mutations with HX-Request header:
13 - test_labels_htmx_create_returns_fragment
14 - test_labels_htmx_delete_returns_fragment
15 - test_labels_htmx_reset_returns_10_defaults
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_label_models import MusehubLabel
24 from musehub.db.musehub_models import MusehubRepo
25 from musehub.muse_contracts.json_types import StrDict
26
27
28 # ---------------------------------------------------------------------------
29 # Helpers
30 # ---------------------------------------------------------------------------
31
32
33 async def _make_repo(
34 db: AsyncSession,
35 owner: str = "label_ssr_artist",
36 slug: str = "label-ssr-album",
37 ) -> str:
38 """Seed a public repo and return its repo_id string."""
39 repo = MusehubRepo(
40 name=slug,
41 owner=owner,
42 slug=slug,
43 visibility="public",
44 owner_user_id="uid-label-ssr-artist",
45 )
46 db.add(repo)
47 await db.commit()
48 await db.refresh(repo)
49 return str(repo.repo_id)
50
51
52 async def _make_label(
53 db: AsyncSession,
54 repo_id: str,
55 *,
56 name: str = "bug",
57 color: str = "#d73a4a",
58 description: str | None = "Something isn't working",
59 ) -> MusehubLabel:
60 """Seed a label and return the ORM instance."""
61 label = MusehubLabel(
62 repo_id=repo_id,
63 name=name,
64 color=color,
65 description=description,
66 )
67 db.add(label)
68 await db.commit()
69 await db.refresh(label)
70 return label
71
72
73 # ---------------------------------------------------------------------------
74 # GET — SSR assertions
75 # ---------------------------------------------------------------------------
76
77
78 @pytest.mark.anyio
79 async def test_labels_page_renders_label_name_server_side(
80 client: AsyncClient,
81 db_session: AsyncSession,
82 ) -> None:
83 """Label name is present in the HTML returned by the server, not injected by JS."""
84 repo_id = await _make_repo(db_session)
85 await _make_label(db_session, repo_id, name="needs-arrangement")
86 response = await client.get("/label_ssr_artist/label-ssr-album/labels")
87 assert response.status_code == 200
88 assert "text/html" in response.headers["content-type"]
89 assert "needs-arrangement" in response.text
90
91
92 @pytest.mark.anyio
93 async def test_labels_page_shows_issue_count_server_side(
94 client: AsyncClient,
95 db_session: AsyncSession,
96 ) -> None:
97 """Issue count is rendered in the label list HTML by the server."""
98 repo_id = await _make_repo(db_session)
99 await _make_label(db_session, repo_id, name="enhancement", color="#a2eeef")
100 response = await client.get("/label_ssr_artist/label-ssr-album/labels")
101 assert response.status_code == 200
102 # Label row with zero issues: "0 issues" should appear
103 assert "0 issues" in response.text or "issues" in response.text
104
105
106 @pytest.mark.anyio
107 async def test_labels_page_fragment_on_htmx_request(
108 client: AsyncClient,
109 db_session: AsyncSession,
110 ) -> None:
111 """HX-Request: true causes the handler to return only the fragment — no <html> shell."""
112 repo_id = await _make_repo(db_session)
113 await _make_label(db_session, repo_id, name="htmx-fragment-label")
114 response = await client.get(
115 "/label_ssr_artist/label-ssr-album/labels",
116 headers={"HX-Request": "true"},
117 )
118 assert response.status_code == 200
119 assert "<html" not in response.text
120 assert "htmx-fragment-label" in response.text
121
122
123 # ---------------------------------------------------------------------------
124 # POST mutations — HTMX fragment returns
125 # ---------------------------------------------------------------------------
126
127
128 @pytest.mark.anyio
129 async def test_labels_htmx_create_returns_fragment(
130 client: AsyncClient,
131 db_session: AsyncSession,
132 auth_headers: StrDict,
133 ) -> None:
134 """POST create with HX-Request returns updated label list fragment.
135
136 Sends JSON body (handled by _parse_label_create_body) with HX-Request header.
137 """
138 await _make_repo(db_session)
139 htmx_headers = {**auth_headers, "HX-Request": "true"}
140 response = await client.post(
141 "/label_ssr_artist/label-ssr-album/labels",
142 json={"name": "htmx-created-label", "color": "#ff0000"},
143 headers=htmx_headers,
144 )
145 assert response.status_code == 201
146 assert "<html" not in response.text
147 assert "htmx-created-label" in response.text
148
149
150 @pytest.mark.anyio
151 async def test_labels_htmx_delete_returns_fragment(
152 client: AsyncClient,
153 db_session: AsyncSession,
154 auth_headers: StrDict,
155 ) -> None:
156 """POST delete with HX-Request returns fragment without the deleted label."""
157 repo_id = await _make_repo(db_session)
158 label = await _make_label(db_session, repo_id, name="to-be-deleted")
159 htmx_headers = {**auth_headers, "HX-Request": "true"}
160 response = await client.post(
161 f"/label_ssr_artist/label-ssr-album/labels/{label.id}/delete",
162 headers=htmx_headers,
163 )
164 assert response.status_code == 200
165 assert "<html" not in response.text
166 assert "to-be-deleted" not in response.text
167
168
169 @pytest.mark.anyio
170 async def test_labels_htmx_reset_returns_10_defaults(
171 client: AsyncClient,
172 db_session: AsyncSession,
173 auth_headers: StrDict,
174 ) -> None:
175 """POST reset with HX-Request returns fragment containing all 10 default labels."""
176 await _make_repo(db_session)
177 htmx_headers = {**auth_headers, "HX-Request": "true"}
178 response = await client.post(
179 "/label_ssr_artist/label-ssr-album/labels/reset",
180 headers=htmx_headers,
181 )
182 assert response.status_code == 200
183 assert "<html" not in response.text
184 # 10 default labels — count the unique per-row IDs to avoid matching class="label-row-actions"
185 assert response.text.count('id="label-row-') == 10
File History 1 commit
sha256:a10adeeb7a0169cb9900f9806ed7a973047258abb6283724fe55e8eb68ff3f0a init: musehub initial commit Human 171 days ago