gabriel / musehub public
test_musehub_ui_explore_ssr.py python
149 lines 5.0 KB
cd448303 Initial extraction of MuseHub from maestro monorepo. Gabriel Cardona <gabriel@tellurstori.com> 7d ago
1 """SSR tests for Muse Hub explore + trending pages — issue #576.
2
3 Validates that repo data is rendered server-side into HTML (not deferred to
4 client JS) and that HTMX fragment requests return bare grid HTML without the
5 full page shell.
6
7 Covers GET /musehub/ui/explore:
8 - test_explore_page_renders_repo_name_server_side — repo name in HTML
9 - test_explore_page_sort_filter_form_has_hx_get — filter form has hx-get
10 - test_explore_page_genre_filter_narrows_repos — ?topic=jazz → jazz-tagged
11 - test_explore_page_htmx_fragment_path — HX-Request → fragment only
12 - test_explore_page_empty_state_when_no_repos — no public repos → empty state
13
14 Covers GET /musehub/ui/trending:
15 - test_trending_page_renders_repo_server_side — repo name in HTML
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 MusehubRepo
24
25
26 # ---------------------------------------------------------------------------
27 # Seed helpers
28 # ---------------------------------------------------------------------------
29
30
31 async def _make_public_repo(
32 db: AsyncSession,
33 *,
34 owner: str = "artist",
35 slug: str = "cool-album",
36 name: str = "cool-album",
37 tags: list[str] | None = None,
38 star_count: int = 0,
39 ) -> MusehubRepo:
40 """Seed a public repo and return the ORM object."""
41 repo = MusehubRepo(
42 name=name,
43 owner=owner,
44 slug=slug,
45 visibility="public",
46 owner_user_id=f"uid-{slug}",
47 tags=tags or [],
48 )
49 db.add(repo)
50 await db.commit()
51 await db.refresh(repo)
52 return repo
53
54
55 # ---------------------------------------------------------------------------
56 # Explore page SSR tests
57 # ---------------------------------------------------------------------------
58
59
60 @pytest.mark.anyio
61 async def test_explore_page_renders_repo_name_server_side(
62 client: AsyncClient,
63 db_session: AsyncSession,
64 ) -> None:
65 """Repo name is in the HTML response without any client-side JS execution."""
66 await _make_public_repo(db_session, slug="jazz-sessions", name="jazz-sessions")
67 response = await client.get("/musehub/ui/explore")
68 assert response.status_code == 200
69 assert "text/html" in response.headers["content-type"]
70 assert "jazz-sessions" in response.text
71
72
73 @pytest.mark.anyio
74 async def test_explore_page_sort_filter_form_has_hx_get(
75 client: AsyncClient,
76 db_session: AsyncSession,
77 ) -> None:
78 """Filter form has hx-get attribute enabling HTMX partial swap."""
79 response = await client.get("/musehub/ui/explore")
80 assert response.status_code == 200
81 assert 'hx-get="/musehub/ui/explore"' in response.text
82
83
84 @pytest.mark.anyio
85 async def test_explore_page_genre_filter_narrows_repos(
86 client: AsyncClient,
87 db_session: AsyncSession,
88 ) -> None:
89 """?topic=jazz returns only jazz-tagged repos (genre filter works SSR)."""
90 await _make_public_repo(
91 db_session, slug="jazz-album", name="jazz-album", tags=["jazz", "piano"]
92 )
93 await _make_public_repo(
94 db_session, slug="rock-album", name="rock-album", tags=["rock", "guitar"]
95 )
96 response = await client.get("/musehub/ui/explore?topic=jazz")
97 assert response.status_code == 200
98 body = response.text
99 assert "jazz-album" in body
100 assert "rock-album" not in body
101
102
103 @pytest.mark.anyio
104 async def test_explore_page_htmx_fragment_path(
105 client: AsyncClient,
106 db_session: AsyncSession,
107 ) -> None:
108 """HX-Request: true returns a bare HTML fragment without the full page shell."""
109 await _make_public_repo(db_session, slug="htmx-repo", name="htmx-repo")
110 response = await client.get(
111 "/musehub/ui/explore",
112 headers={"HX-Request": "true"},
113 )
114 assert response.status_code == 200
115 assert "<html" not in response.text
116 assert "<head" not in response.text
117 # Fragment should contain the repo or empty-state markup.
118 assert "htmx-repo" in response.text or "No repositories found" in response.text
119
120
121 @pytest.mark.anyio
122 async def test_explore_page_empty_state_when_no_repos(
123 client: AsyncClient,
124 db_session: AsyncSession,
125 ) -> None:
126 """When no public repos exist the empty-state message is rendered SSR."""
127 response = await client.get("/musehub/ui/explore")
128 assert response.status_code == 200
129 assert "No repositories found" in response.text
130
131
132 # ---------------------------------------------------------------------------
133 # Trending page SSR tests
134 # ---------------------------------------------------------------------------
135
136
137 @pytest.mark.anyio
138 async def test_trending_page_renders_repo_server_side(
139 client: AsyncClient,
140 db_session: AsyncSession,
141 ) -> None:
142 """Trending page renders public repo name in HTML without client-side JS."""
143 await _make_public_repo(
144 db_session, slug="trending-hit", name="trending-hit", star_count=100
145 )
146 response = await client.get("/musehub/ui/trending")
147 assert response.status_code == 200
148 assert "text/html" in response.headers["content-type"]
149 assert "trending-hit" in response.text