gabriel / musehub public
test_musehub_ui_settings_ssr.py python
140 lines 4.9 KB
Raw
sha256:9590cee1e0ccd6c76528f005b95d634d80f5019f0dcb7c371e149adc31d1fb65 refactor: enforce gRPC framing on all MWP wire traffic Sonnet 4.6 minor ⚠ breaking 156 days ago
1 """SSR-specific tests for the MuseHub repo settings page.
2
3 Verifies that the settings page uses server-side rendering (Jinja2 templates)
4 rather than a client-side JS shell. The handler passes ``RepoSettingsResponse``
5 into the template context so field values are embedded in the HTML at
6 render-time — no client-side fetch required to display the form.
7
8 Test matrix:
9 - test_settings_page_renders_repo_name_server_side — GET page, assert repo name in form value
10 - test_settings_page_general_form_has_hx_patch — general form has ``hx-patch`` attribute
11 - test_settings_page_danger_zone_has_hx_delete — delete form has ``hx-delete``
12 - test_settings_page_section_nav_present — section nav links present
13 - test_settings_unknown_repo_404 — unknown slug → 404
14 """
15 from __future__ import annotations
16
17 import pytest
18 from httpx import AsyncClient
19 from sqlalchemy.ext.asyncio import AsyncSession
20
21 from musehub.db.musehub_models import MusehubRepo
22
23
24 # ---------------------------------------------------------------------------
25 # Fixtures / helpers
26 # ---------------------------------------------------------------------------
27
28
29 async def _make_repo(
30 db_session: AsyncSession,
31 owner: str = "ssrowner",
32 slug: str = "ssr-settings-repo",
33 visibility: str = "public",
34 name: str | None = None,
35 ) -> MusehubRepo:
36 """Seed a minimal repo for SSR settings tests and return the ORM row."""
37 repo_name = name or slug
38 repo = MusehubRepo(
39 name=repo_name,
40 owner=owner,
41 slug=slug,
42 visibility=visibility,
43 owner_user_id="ssr-settings-uid",
44 )
45 db_session.add(repo)
46 await db_session.commit()
47 await db_session.refresh(repo)
48 return repo
49
50
51 # ---------------------------------------------------------------------------
52 # SSR: repo name embedded in form value at render-time
53 # ---------------------------------------------------------------------------
54
55
56 async def test_settings_page_renders_repo_name_server_side(
57 client: AsyncClient,
58 db_session: AsyncSession,
59 ) -> None:
60 """GET settings page embeds the repo name directly into the HTML form value.
61
62 With SSR, the template renders ``value="{{ s.name }}"`` so the repo name
63 is present in the raw HTML response without any client-side fetch.
64 """
65 repo = await _make_repo(
66 db_session, owner="ssrname", slug="my-ssr-repo", name="my-ssr-repo"
67 )
68 resp = await client.get(f"/{repo.owner}/{repo.slug}/settings")
69 assert resp.status_code == 200
70 assert "my-ssr-repo" in resp.text
71
72
73 # ---------------------------------------------------------------------------
74 # HTMX attributes
75 # ---------------------------------------------------------------------------
76
77
78 async def test_settings_page_general_form_has_hx_patch(
79 client: AsyncClient,
80 db_session: AsyncSession,
81 ) -> None:
82 """General settings form uses ``hx-patch`` for HTMX section-save."""
83 repo = await _make_repo(db_session, owner="htmxpatch", slug="htmx-patch-repo")
84 resp = await client.get(f"/{repo.owner}/{repo.slug}/settings")
85 assert resp.status_code == 200
86 assert "hx-patch" in resp.text
87
88
89 async def test_settings_page_danger_zone_has_hx_delete(
90 client: AsyncClient,
91 db_session: AsyncSession,
92 ) -> None:
93 """Danger Zone delete form uses ``hx-delete`` for HTMX repo deletion."""
94 repo = await _make_repo(db_session, owner="htmxdel", slug="htmx-delete-repo")
95 resp = await client.get(f"/{repo.owner}/{repo.slug}/settings")
96 assert resp.status_code == 200
97 assert "hx-delete" in resp.text
98
99
100 # ---------------------------------------------------------------------------
101 # Section navigation
102 # ---------------------------------------------------------------------------
103
104
105 async def test_settings_page_section_nav_present(
106 client: AsyncClient,
107 db_session: AsyncSession,
108 ) -> None:
109 """Settings page includes Alpine.js-powered section navigation links.
110
111 The nav uses ``x-on:click.prevent`` to switch sections client-side without
112 a server round-trip, and ``:class`` binding to highlight the active link.
113 """
114 repo = await _make_repo(db_session, owner="secnav", slug="sec-nav-repo")
115 resp = await client.get(f"/{repo.owner}/{repo.slug}/settings")
116 assert resp.status_code == 200
117 html = resp.text
118 assert "settings-nav-link" in html
119 # Alpine.js section switching
120 assert "x-data" in html
121 assert "x-show" in html
122 # All four sections present
123 assert "section-general" in html
124 assert "section-merge" in html
125 assert "section-collaboration" in html
126 assert "section-danger" in html
127
128
129 # ---------------------------------------------------------------------------
130 # 404 for unknown repo
131 # ---------------------------------------------------------------------------
132
133
134 async def test_settings_unknown_repo_404(
135 client: AsyncClient,
136 db_session: AsyncSession,
137 ) -> None:
138 """GET settings for an unknown repo/slug returns 404."""
139 resp = await client.get("/nobody/nonexistent-repo-ssr/settings")
140 assert resp.status_code == 404
File History 1 commit
sha256:9590cee1e0ccd6c76528f005b95d634d80f5019f0dcb7c371e149adc31d1fb65 refactor: enforce gRPC framing on all MWP wire traffic Sonnet 4.6 minor ⚠ 156 days ago