gabriel / musehub public
test_musehub_ui_new_repo.py python
293 lines 9.0 KB
Raw
sha256:a10adeeb7a0169cb9900f9806ed7a973047258abb6283724fe55e8eb68ff3f0a init: musehub initial commit Human 171 days ago
1 """Tests for the MuseHub new-repo creation wizard.
2
3 Covers ``musehub/api/routes/musehub/ui_new_repo.py``:
4
5 GET /new — redirects to /domains (repos require a domain context)
6 POST /new — create repo (JSON body, auth required)
7 GET /new/check — name availability check
8
9 Test matrix:
10 test_new_repo_page_redirects_to_domains — GET /new → 302 to /domains
11 test_new_repo_page_redirect_no_auth_required — redirect happens without authentication
12 test_check_available_returns_true — GET /new/check → available=true
13 test_check_taken_returns_false — GET /new/check → available=false
14 test_check_requires_owner_and_slug — GET /new/check → 422 when missing params
15 test_create_repo_requires_auth — POST without token → 401/403
16 test_create_repo_success — POST with valid body → 201 + redirect
17 test_create_repo_409_on_duplicate — POST duplicate → 409
18 test_create_repo_redirect_url_format — redirect URL contains /{owner}/{slug}?welcome=1
19 test_create_repo_private_default — POST without visibility → defaults to private
20 test_create_repo_initializes_repo — POST with initialize=true creates the repo
21 test_create_repo_with_license — POST with license field stored correctly
22 test_create_repo_with_topics — POST with topics stored as tags
23 """
24 from __future__ import annotations
25
26 import pytest
27 from httpx import AsyncClient
28 from sqlalchemy.ext.asyncio import AsyncSession
29
30 from musehub.db.musehub_models import MusehubRepo
31 from musehub.muse_contracts.json_types import StrDict
32
33
34 # ---------------------------------------------------------------------------
35 # Helpers
36 # ---------------------------------------------------------------------------
37
38 async def _seed_repo(
39 db_session: AsyncSession,
40 owner: str = "wizowner",
41 slug: str = "existing-repo",
42 ) -> MusehubRepo:
43 """Seed a repo with a known owner/slug for uniqueness-check tests."""
44 repo = MusehubRepo(
45 name=slug,
46 owner=owner,
47 slug=slug,
48 visibility="public",
49 owner_user_id="seed-uid",
50 )
51 db_session.add(repo)
52 await db_session.commit()
53 await db_session.refresh(repo)
54 return repo
55
56
57 # ---------------------------------------------------------------------------
58 # GET /new — redirect (repos require a domain context)
59 # ---------------------------------------------------------------------------
60
61
62 @pytest.mark.anyio
63 async def test_new_repo_page_redirects_to_domains(client: AsyncClient) -> None:
64 """GET /new → 302 redirect to /domains.
65
66 Repository creation is now domain-scoped; the standalone /new wizard
67 no longer exists. Users are directed to pick a domain first.
68 """
69 resp = await client.get("/new", follow_redirects=False)
70 assert resp.status_code == 302
71 assert resp.headers["location"].endswith("/domains")
72
73
74 @pytest.mark.anyio
75 async def test_new_repo_page_redirect_no_auth_required(client: AsyncClient) -> None:
76 """The redirect from /new does not require authentication."""
77 resp = await client.get("/new", follow_redirects=False)
78 assert resp.status_code == 302
79
80
81 # ---------------------------------------------------------------------------
82 # GET /new/check — name availability
83 # ---------------------------------------------------------------------------
84
85
86 @pytest.mark.anyio
87 async def test_check_available_returns_true(
88 client: AsyncClient,
89 db_session: AsyncSession,
90 ) -> None:
91 """GET /new/check → available=true when no repo exists with that owner+slug."""
92 resp = await client.get(
93 "/new/check",
94 params={"owner": "nobody", "slug": "no-such-repo"},
95 )
96 assert resp.status_code == 200
97 assert resp.json()["available"] is True
98
99
100 @pytest.mark.anyio
101 async def test_check_taken_returns_false(
102 client: AsyncClient,
103 db_session: AsyncSession,
104 ) -> None:
105 """GET /new/check → available=false when the owner+slug is already taken."""
106 await _seed_repo(db_session, owner="wizowner", slug="existing-repo")
107 resp = await client.get(
108 "/new/check",
109 params={"owner": "wizowner", "slug": "existing-repo"},
110 )
111 assert resp.status_code == 200
112 assert resp.json()["available"] is False
113
114
115 @pytest.mark.anyio
116 async def test_check_requires_owner_and_slug(client: AsyncClient) -> None:
117 """GET /new/check without required params returns 422."""
118 resp = await client.get("/new/check")
119 assert resp.status_code == 422
120
121
122 # ---------------------------------------------------------------------------
123 # POST /new — repo creation
124 # ---------------------------------------------------------------------------
125
126
127 @pytest.mark.anyio
128 async def test_create_repo_requires_auth(client: AsyncClient) -> None:
129 """POST /new without Authorization header returns 401 or 403."""
130 resp = await client.post(
131 "/new",
132 json={
133 "name": "test-repo",
134 "owner": "someowner",
135 "visibility": "private",
136 },
137 )
138 assert resp.status_code in (401, 403)
139
140
141 @pytest.mark.anyio
142 async def test_create_repo_success(
143 client: AsyncClient,
144 db_session: AsyncSession,
145 auth_headers: StrDict,
146 ) -> None:
147 """POST /new with valid body returns 201 and a redirect URL."""
148 resp = await client.post(
149 "/new",
150 json={
151 "name": "New Composition",
152 "owner": "testowner",
153 "visibility": "public",
154 "description": "A new jazz piece",
155 "tags": [],
156 "topics": ["jazz", "piano"],
157 "initialize": True,
158 "defaultBranch": "main",
159 },
160 headers=auth_headers,
161 )
162 assert resp.status_code == 201
163 data = resp.json()
164 assert "redirect" in data
165 assert "welcome=1" in data["redirect"]
166
167
168 @pytest.mark.anyio
169 async def test_create_repo_409_on_duplicate(
170 client: AsyncClient,
171 db_session: AsyncSession,
172 auth_headers: StrDict,
173 ) -> None:
174 """POST /new with a duplicate owner+name returns 409."""
175 await _seed_repo(db_session, owner="dupowner", slug="dup-repo")
176 # 'dup-repo' is the slug generated from the name 'dup-repo'
177 resp = await client.post(
178 "/new",
179 json={
180 "name": "dup-repo",
181 "owner": "dupowner",
182 "visibility": "private",
183 },
184 headers=auth_headers,
185 )
186 assert resp.status_code == 409
187
188
189 @pytest.mark.anyio
190 async def test_create_repo_redirect_url_format(
191 client: AsyncClient,
192 db_session: AsyncSession,
193 auth_headers: StrDict,
194 ) -> None:
195 """The redirect URL contains owner/slug path and ?welcome=1 query param."""
196 resp = await client.post(
197 "/new",
198 json={
199 "name": "redirect-test",
200 "owner": "urlowner",
201 "visibility": "private",
202 },
203 headers=auth_headers,
204 )
205 assert resp.status_code == 201
206 redirect = resp.json()["redirect"]
207 assert "urlowner" in redirect
208 assert "welcome=1" in redirect
209 assert redirect.startswith("/")
210
211
212 @pytest.mark.anyio
213 async def test_create_repo_private_default(
214 client: AsyncClient,
215 db_session: AsyncSession,
216 auth_headers: StrDict,
217 ) -> None:
218 """POST without specifying visibility defaults to 'private'."""
219 resp = await client.post(
220 "/new",
221 json={
222 "name": "private-default-test",
223 "owner": "privowner",
224 },
225 headers=auth_headers,
226 )
227 assert resp.status_code == 201
228 # Confirm the slug and owner are in the redirect — repo was created.
229 assert "privowner" in resp.json()["redirect"]
230
231
232 @pytest.mark.anyio
233 async def test_create_repo_initializes_repo(
234 client: AsyncClient,
235 db_session: AsyncSession,
236 auth_headers: StrDict,
237 ) -> None:
238 """POST with initialize=true creates the repo successfully."""
239 resp = await client.post(
240 "/new",
241 json={
242 "name": "init-repo-test",
243 "owner": "initowner",
244 "visibility": "public",
245 "initialize": True,
246 "defaultBranch": "trunk",
247 },
248 headers=auth_headers,
249 )
250 assert resp.status_code == 201
251 data = resp.json()
252 assert "repoId" in data
253 assert data["slug"] == "init-repo-test"
254
255
256 @pytest.mark.anyio
257 async def test_create_repo_with_license(
258 client: AsyncClient,
259 db_session: AsyncSession,
260 auth_headers: StrDict,
261 ) -> None:
262 """POST with a license value is accepted and reflected in the response."""
263 resp = await client.post(
264 "/new",
265 json={
266 "name": "licensed-repo",
267 "owner": "licowner",
268 "visibility": "public",
269 "license": "CC BY",
270 },
271 headers=auth_headers,
272 )
273 assert resp.status_code == 201
274
275
276 @pytest.mark.anyio
277 async def test_create_repo_with_topics(
278 client: AsyncClient,
279 db_session: AsyncSession,
280 auth_headers: StrDict,
281 ) -> None:
282 """POST with topics results in a 201 and stores tags on the new repo."""
283 resp = await client.post(
284 "/new",
285 json={
286 "name": "topical-repo",
287 "owner": "topicowner",
288 "visibility": "public",
289 "topics": ["jazz", "piano", "neosoul"],
290 },
291 headers=auth_headers,
292 )
293 assert resp.status_code == 201
File History 1 commit
sha256:a10adeeb7a0169cb9900f9806ed7a973047258abb6283724fe55e8eb68ff3f0a init: musehub initial commit Human 171 days ago