"""SSR tests for MuseHub explore page — issue #576. Validates that repo data is rendered server-side into HTML (not deferred to client JS) and that HTMX fragment requests return bare grid HTML without the full page shell. Covers GET /explore: - test_explore_page_renders_repo_name_server_side — repo name in HTML - test_explore_page_sort_filter_form_has_hx_get — filter form has hx-get - test_explore_page_genre_filter_narrows_repos — ?topic=jazz → jazz-tagged - test_explore_page_htmx_fragment_path — HX-Request → fragment only - test_explore_page_empty_state_when_no_repos — no public repos → empty state """ from __future__ import annotations import pytest from httpx import AsyncClient from sqlalchemy.ext.asyncio import AsyncSession from datetime import datetime, timezone from musehub.core.genesis import compute_identity_id, compute_repo_id from musehub.db.musehub_repo_models import MusehubRepo # --------------------------------------------------------------------------- # Seed helpers # --------------------------------------------------------------------------- async def _make_public_repo( db: AsyncSession, *, owner: str = "artist", slug: str = "cool-album", name: str = "cool-album", tags: list[str] | None = None, ) -> MusehubRepo: """Seed a public repo and return the ORM object.""" created_at = datetime.now(tz=timezone.utc) owner_id = compute_identity_id(owner.encode()) repo = MusehubRepo( repo_id=compute_repo_id(owner_id, slug, "code", created_at.isoformat()), name=name, owner=owner, slug=slug, visibility="public", owner_user_id=owner_id, tags=tags or [], created_at=created_at, updated_at=created_at, ) db.add(repo) await db.commit() await db.refresh(repo) return repo # --------------------------------------------------------------------------- # Explore page SSR tests # --------------------------------------------------------------------------- async def test_explore_page_renders_repo_name_server_side( client: AsyncClient, db_session: AsyncSession, ) -> None: """Repo name is in the HTML response without any client-side JS execution.""" await _make_public_repo(db_session, slug="jazz-sessions", name="jazz-sessions") response = await client.get("/explore") assert response.status_code == 200 assert "text/html" in response.headers["content-type"] assert "jazz-sessions" in response.text async def test_explore_page_sort_filter_form_has_hx_get( client: AsyncClient, db_session: AsyncSession, ) -> None: """Filter form has hx-get attribute enabling HTMX partial swap.""" response = await client.get("/explore") assert response.status_code == 200 assert 'hx-get="/explore"' in response.text async def test_explore_page_genre_filter_narrows_repos( client: AsyncClient, db_session: AsyncSession, ) -> None: """?topic=jazz returns only jazz-tagged repos (genre filter works SSR).""" await _make_public_repo( db_session, slug="jazz-album", name="jazz-album", tags=["jazz", "piano"] ) await _make_public_repo( db_session, slug="rock-album", name="rock-album", tags=["rock", "guitar"] ) response = await client.get("/explore?topic=jazz") assert response.status_code == 200 body = response.text assert "jazz-album" in body assert "rock-album" not in body async def test_explore_page_htmx_fragment_path( client: AsyncClient, db_session: AsyncSession, ) -> None: """HX-Request: true returns a bare HTML fragment without the full page shell.""" await _make_public_repo(db_session, slug="htmx-repo", name="htmx-repo") response = await client.get( "/explore", headers={"HX-Request": "true"}, ) assert response.status_code == 200 assert " None: """When no public repos exist the empty-state message is rendered SSR.""" response = await client.get("/explore") assert response.status_code == 200 assert "No repositories found" in response.text