gabriel / musehub public
test_auth_authorization_section12.py python
134 lines 5.3 KB
Raw
sha256:a10adeeb7a0169cb9900f9806ed7a973047258abb6283724fe55e8eb68ff3f0a init: musehub initial commit Human 171 days ago
1 """Tests for checklist section 1.2 — Authorization guards.
2
3 Covers the three guard helpers that enforce repo-level access control:
4
5 _guard_visibility — 404 on missing repo; 401 on private + unauthenticated
6 _guard_write_access — 403 when non-owner tries to write to a private repo
7 _guard_repo_owner — 403 when non-owner attempts an owner-only action
8
9 All tests are synchronous unit tests — no DB or HTTP fixtures needed.
10 """
11 from __future__ import annotations
12
13 import pytest
14 from fastapi import HTTPException
15 from unittest.mock import MagicMock
16
17
18 # ---------------------------------------------------------------------------
19 # Helpers — build lightweight repo/claims stand-ins without importing ORM
20 # ---------------------------------------------------------------------------
21
22 def _repo(*, visibility: str = "public", owner: str = "alice") -> MagicMock:
23 r = MagicMock()
24 r.visibility = visibility
25 r.owner = owner
26 return r
27
28
29 def _claims(*, handle: str = "alice") -> MagicMock:
30 c = MagicMock()
31 c.handle = handle
32 return c
33
34
35 # ---------------------------------------------------------------------------
36 # _guard_visibility
37 # ---------------------------------------------------------------------------
38
39 class TestGuardVisibility:
40 def setup_method(self) -> None:
41 from musehub.api.routes.musehub.repos import _guard_visibility
42 self.guard = _guard_visibility
43
44 def test_none_repo_raises_404(self) -> None:
45 with pytest.raises(HTTPException) as exc_info:
46 self.guard(None, _claims())
47 assert exc_info.value.status_code == 404
48
49 def test_none_repo_none_claims_raises_404(self) -> None:
50 """404 takes priority over 401 — missing repo is never exposed as 401."""
51 with pytest.raises(HTTPException) as exc_info:
52 self.guard(None, None)
53 assert exc_info.value.status_code == 404
54
55 def test_public_repo_no_claims_allowed(self) -> None:
56 """Public repo with no auth must not raise."""
57 self.guard(_repo(visibility="public"), None)
58
59 def test_public_repo_with_claims_allowed(self) -> None:
60 self.guard(_repo(visibility="public"), _claims())
61
62 def test_private_repo_with_claims_allowed(self) -> None:
63 """Authenticated user may access a private repo."""
64 self.guard(_repo(visibility="private"), _claims())
65
66 def test_private_repo_no_claims_raises_401(self) -> None:
67 with pytest.raises(HTTPException) as exc_info:
68 self.guard(_repo(visibility="private"), None)
69 assert exc_info.value.status_code == 401
70
71 def test_private_repo_401_includes_www_authenticate(self) -> None:
72 with pytest.raises(HTTPException) as exc_info:
73 self.guard(_repo(visibility="private"), None)
74 assert "WWW-Authenticate" in exc_info.value.headers
75 assert "MSign" in exc_info.value.headers["WWW-Authenticate"]
76
77
78 # ---------------------------------------------------------------------------
79 # _guard_write_access
80 # ---------------------------------------------------------------------------
81
82 class TestGuardWriteAccess:
83 def setup_method(self) -> None:
84 from musehub.api.routes.musehub.issues import _guard_write_access
85 self.guard = _guard_write_access
86
87 def test_public_repo_any_user_may_write(self) -> None:
88 """Any authenticated user can write to a public repo."""
89 self.guard(_repo(visibility="public", owner="alice"), "bob")
90
91 def test_public_repo_owner_may_write(self) -> None:
92 self.guard(_repo(visibility="public", owner="alice"), "alice")
93
94 def test_private_repo_owner_may_write(self) -> None:
95 self.guard(_repo(visibility="private", owner="alice"), "alice")
96
97 def test_private_repo_non_owner_raises_403(self) -> None:
98 with pytest.raises(HTTPException) as exc_info:
99 self.guard(_repo(visibility="private", owner="alice"), "bob")
100 assert exc_info.value.status_code == 403
101
102 def test_private_repo_403_detail_message(self) -> None:
103 with pytest.raises(HTTPException) as exc_info:
104 self.guard(_repo(visibility="private", owner="alice"), "bob")
105 assert "private" in exc_info.value.detail.lower()
106
107
108 # ---------------------------------------------------------------------------
109 # _guard_repo_owner
110 # ---------------------------------------------------------------------------
111
112 class TestGuardRepoOwner:
113 def setup_method(self) -> None:
114 from musehub.api.routes.musehub.issues import _guard_repo_owner
115 self.guard = _guard_repo_owner
116
117 def test_owner_allowed(self) -> None:
118 self.guard(_repo(owner="alice"), "alice")
119
120 def test_non_owner_raises_403(self) -> None:
121 with pytest.raises(HTTPException) as exc_info:
122 self.guard(_repo(owner="alice"), "bob")
123 assert exc_info.value.status_code == 403
124
125 def test_non_owner_public_repo_still_raises_403(self) -> None:
126 """Even public repos: only owner may perform owner-only actions."""
127 with pytest.raises(HTTPException) as exc_info:
128 self.guard(_repo(visibility="public", owner="alice"), "bob")
129 assert exc_info.value.status_code == 403
130
131 def test_403_detail_message(self) -> None:
132 with pytest.raises(HTTPException) as exc_info:
133 self.guard(_repo(owner="alice"), "bob")
134 assert "owner" in exc_info.value.detail.lower()
File History 1 commit
sha256:a10adeeb7a0169cb9900f9806ed7a973047258abb6283724fe55e8eb68ff3f0a init: musehub initial commit Human 171 days ago