pytest-clerk-mock 0.0.2__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- pytest_clerk_mock/__init__.py +39 -0
- pytest_clerk_mock/client.py +189 -0
- pytest_clerk_mock/helpers.py +148 -0
- pytest_clerk_mock/models/__init__.py +18 -0
- pytest_clerk_mock/models/auth.py +50 -0
- pytest_clerk_mock/models/organization.py +30 -0
- pytest_clerk_mock/models/user.py +92 -0
- pytest_clerk_mock/plugin.py +215 -0
- pytest_clerk_mock/services/__init__.py +14 -0
- pytest_clerk_mock/services/auth.py +70 -0
- pytest_clerk_mock/services/users.py +495 -0
- pytest_clerk_mock-0.0.2.dist-info/METADATA +251 -0
- pytest_clerk_mock-0.0.2.dist-info/RECORD +16 -0
- pytest_clerk_mock-0.0.2.dist-info/WHEEL +4 -0
- pytest_clerk_mock-0.0.2.dist-info/entry_points.txt +3 -0
- pytest_clerk_mock-0.0.2.dist-info/licenses/LICENSE +21 -0
|
@@ -0,0 +1,215 @@
|
|
|
1
|
+
from collections.abc import Generator
|
|
2
|
+
from contextvars import ContextVar
|
|
3
|
+
from contextlib import ExitStack, contextmanager
|
|
4
|
+
from typing import Any
|
|
5
|
+
from unittest.mock import patch
|
|
6
|
+
|
|
7
|
+
import pytest
|
|
8
|
+
|
|
9
|
+
from pytest_clerk_mock.client import MockClerkClient
|
|
10
|
+
|
|
11
|
+
_current_mock_client: ContextVar[MockClerkClient | None] = ContextVar(
|
|
12
|
+
"_current_mock_client", default=None
|
|
13
|
+
)
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
def _get_current_client() -> MockClerkClient:
|
|
17
|
+
"""Get the current MockClerkClient from context."""
|
|
18
|
+
|
|
19
|
+
client = _current_mock_client.get()
|
|
20
|
+
|
|
21
|
+
if client is None:
|
|
22
|
+
raise RuntimeError("No MockClerkClient is currently active")
|
|
23
|
+
|
|
24
|
+
return client
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
def _mock_authenticate_request(request: Any, options: Any) -> Any:
|
|
28
|
+
"""Mock authenticate_request function that delegates to the current mock client."""
|
|
29
|
+
|
|
30
|
+
return _get_current_client().authenticate_request(request, options)
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
class _MockUsersProxy:
|
|
34
|
+
"""Proxy that delegates all calls to the current mock client's users."""
|
|
35
|
+
|
|
36
|
+
def __getattr__(self, name: str) -> Any:
|
|
37
|
+
return getattr(_get_current_client().users, name)
|
|
38
|
+
|
|
39
|
+
|
|
40
|
+
_users_proxy = _MockUsersProxy()
|
|
41
|
+
|
|
42
|
+
|
|
43
|
+
def _mock_users_class(*args: Any, **kwargs: Any) -> _MockUsersProxy:
|
|
44
|
+
"""Mock Users class that returns the proxy."""
|
|
45
|
+
|
|
46
|
+
return _users_proxy
|
|
47
|
+
|
|
48
|
+
|
|
49
|
+
def _apply_sdk_patches(stack: ExitStack) -> None:
|
|
50
|
+
"""Apply patches to clerk_backend_api SDK internals.
|
|
51
|
+
|
|
52
|
+
This patches at the SDK level so it works regardless of when/how
|
|
53
|
+
the Clerk client was instantiated.
|
|
54
|
+
"""
|
|
55
|
+
|
|
56
|
+
stack.enter_context(
|
|
57
|
+
patch(
|
|
58
|
+
"clerk_backend_api.security.authenticaterequest.authenticate_request",
|
|
59
|
+
_mock_authenticate_request,
|
|
60
|
+
)
|
|
61
|
+
)
|
|
62
|
+
|
|
63
|
+
stack.enter_context(
|
|
64
|
+
patch(
|
|
65
|
+
"clerk_backend_api.security.authenticate_request",
|
|
66
|
+
_mock_authenticate_request,
|
|
67
|
+
)
|
|
68
|
+
)
|
|
69
|
+
|
|
70
|
+
stack.enter_context(
|
|
71
|
+
patch(
|
|
72
|
+
"clerk_backend_api.sdk.authenticate_request",
|
|
73
|
+
_mock_authenticate_request,
|
|
74
|
+
)
|
|
75
|
+
)
|
|
76
|
+
|
|
77
|
+
stack.enter_context(
|
|
78
|
+
patch(
|
|
79
|
+
"clerk_backend_api.users.Users",
|
|
80
|
+
_mock_users_class,
|
|
81
|
+
)
|
|
82
|
+
)
|
|
83
|
+
|
|
84
|
+
|
|
85
|
+
@pytest.fixture
|
|
86
|
+
def mock_clerk() -> Generator[MockClerkClient, None, None]:
|
|
87
|
+
"""Fixture that provides a mock Clerk client.
|
|
88
|
+
|
|
89
|
+
The client is reset after each test to ensure isolation.
|
|
90
|
+
Patches clerk_backend_api SDK internals to intercept all Clerk operations.
|
|
91
|
+
|
|
92
|
+
Yields:
|
|
93
|
+
MockClerkClient instance configured with default auth state.
|
|
94
|
+
|
|
95
|
+
Example:
|
|
96
|
+
def test_something(mock_clerk):
|
|
97
|
+
# Configure auth state
|
|
98
|
+
mock_clerk.configure_auth("user_123", "org_456")
|
|
99
|
+
|
|
100
|
+
# Or use context manager for temporary user switch
|
|
101
|
+
with mock_clerk.as_user("user_456", "org_789"):
|
|
102
|
+
# Test as different user
|
|
103
|
+
pass
|
|
104
|
+
"""
|
|
105
|
+
|
|
106
|
+
client = MockClerkClient()
|
|
107
|
+
token = _current_mock_client.set(client)
|
|
108
|
+
|
|
109
|
+
with ExitStack() as stack:
|
|
110
|
+
_apply_sdk_patches(stack)
|
|
111
|
+
stack.enter_context(patch("clerk_backend_api.Clerk", return_value=client))
|
|
112
|
+
|
|
113
|
+
yield client
|
|
114
|
+
|
|
115
|
+
_current_mock_client.reset(token)
|
|
116
|
+
client.reset()
|
|
117
|
+
|
|
118
|
+
|
|
119
|
+
@contextmanager
|
|
120
|
+
def mock_clerk_backend(
|
|
121
|
+
patch_targets: list[str] | None = None,
|
|
122
|
+
default_user_id: str | None = "user_test_owner",
|
|
123
|
+
default_org_id: str | None = "org_test_123",
|
|
124
|
+
default_org_role: str = "org:admin",
|
|
125
|
+
) -> Generator[MockClerkClient, None, None]:
|
|
126
|
+
"""Context manager for mocking Clerk backend.
|
|
127
|
+
|
|
128
|
+
This provides a moto-like API for mocking Clerk in tests without using fixtures.
|
|
129
|
+
Patches clerk_backend_api SDK internals so it works regardless of when the
|
|
130
|
+
Clerk client was instantiated.
|
|
131
|
+
|
|
132
|
+
Args:
|
|
133
|
+
patch_targets: Deprecated. No longer needed - SDK is patched at the internal level.
|
|
134
|
+
default_user_id: Default user ID for authentication (None for unauthenticated)
|
|
135
|
+
default_org_id: Default organization ID
|
|
136
|
+
default_org_role: Default organization role
|
|
137
|
+
|
|
138
|
+
Yields:
|
|
139
|
+
MockClerkClient instance
|
|
140
|
+
|
|
141
|
+
Example:
|
|
142
|
+
with mock_clerk_backend() as mock:
|
|
143
|
+
mock.configure_auth("user_123", "org_456")
|
|
144
|
+
# Your test code here
|
|
145
|
+
"""
|
|
146
|
+
|
|
147
|
+
del patch_targets
|
|
148
|
+
|
|
149
|
+
client = MockClerkClient(
|
|
150
|
+
default_user_id=default_user_id,
|
|
151
|
+
default_org_id=default_org_id,
|
|
152
|
+
default_org_role=default_org_role,
|
|
153
|
+
)
|
|
154
|
+
token = _current_mock_client.set(client)
|
|
155
|
+
|
|
156
|
+
with ExitStack() as stack:
|
|
157
|
+
_apply_sdk_patches(stack)
|
|
158
|
+
stack.enter_context(patch("clerk_backend_api.Clerk", return_value=client))
|
|
159
|
+
|
|
160
|
+
yield client
|
|
161
|
+
|
|
162
|
+
_current_mock_client.reset(token)
|
|
163
|
+
client.reset()
|
|
164
|
+
|
|
165
|
+
|
|
166
|
+
def create_mock_clerk_fixture(
|
|
167
|
+
patch_targets: list[str] | None = None,
|
|
168
|
+
default_user_id: str | None = "user_test_owner",
|
|
169
|
+
default_org_id: str | None = "org_test_123",
|
|
170
|
+
default_org_role: str = "org:admin",
|
|
171
|
+
autouse: bool = False,
|
|
172
|
+
):
|
|
173
|
+
"""Factory function to create a mock_clerk fixture with custom configuration.
|
|
174
|
+
|
|
175
|
+
Patches clerk_backend_api SDK internals so it works regardless of when the
|
|
176
|
+
Clerk client was instantiated.
|
|
177
|
+
|
|
178
|
+
Args:
|
|
179
|
+
patch_targets: Deprecated. No longer needed - SDK is patched at the internal level.
|
|
180
|
+
default_user_id: Default user ID for authentication
|
|
181
|
+
default_org_id: Default organization ID
|
|
182
|
+
default_org_role: Default organization role
|
|
183
|
+
autouse: Whether to automatically use the fixture in all tests
|
|
184
|
+
|
|
185
|
+
Returns:
|
|
186
|
+
A pytest fixture function
|
|
187
|
+
|
|
188
|
+
Example:
|
|
189
|
+
# In conftest.py
|
|
190
|
+
from pytest_clerk_mock import create_mock_clerk_fixture
|
|
191
|
+
|
|
192
|
+
mock_clerk = create_mock_clerk_fixture(autouse=True)
|
|
193
|
+
"""
|
|
194
|
+
|
|
195
|
+
del patch_targets
|
|
196
|
+
|
|
197
|
+
@pytest.fixture(autouse=autouse)
|
|
198
|
+
def custom_mock_clerk() -> Generator[MockClerkClient, None, None]:
|
|
199
|
+
client = MockClerkClient(
|
|
200
|
+
default_user_id=default_user_id,
|
|
201
|
+
default_org_id=default_org_id,
|
|
202
|
+
default_org_role=default_org_role,
|
|
203
|
+
)
|
|
204
|
+
token = _current_mock_client.set(client)
|
|
205
|
+
|
|
206
|
+
with ExitStack() as stack:
|
|
207
|
+
_apply_sdk_patches(stack)
|
|
208
|
+
stack.enter_context(patch("clerk_backend_api.Clerk", return_value=client))
|
|
209
|
+
|
|
210
|
+
yield client
|
|
211
|
+
|
|
212
|
+
_current_mock_client.reset(token)
|
|
213
|
+
client.reset()
|
|
214
|
+
|
|
215
|
+
return custom_mock_clerk
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
from pytest_clerk_mock.services.auth import AuthSnapshot, MockAuthState
|
|
2
|
+
from pytest_clerk_mock.services.users import (
|
|
3
|
+
MockListResponse,
|
|
4
|
+
MockUsersClient,
|
|
5
|
+
UserNotFoundError,
|
|
6
|
+
)
|
|
7
|
+
|
|
8
|
+
__all__ = [
|
|
9
|
+
"AuthSnapshot",
|
|
10
|
+
"MockAuthState",
|
|
11
|
+
"MockListResponse",
|
|
12
|
+
"MockUsersClient",
|
|
13
|
+
"UserNotFoundError",
|
|
14
|
+
]
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
from typing import Any
|
|
2
|
+
|
|
3
|
+
from pydantic import BaseModel
|
|
4
|
+
|
|
5
|
+
from pytest_clerk_mock.models.auth import MockAuthResult
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
class AuthSnapshot(BaseModel):
|
|
9
|
+
"""Snapshot of authentication state for restoration."""
|
|
10
|
+
|
|
11
|
+
user_id: str | None
|
|
12
|
+
org_id: str | None
|
|
13
|
+
org_role: str
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
class MockAuthState:
|
|
17
|
+
"""Manages authentication state for mock Clerk client."""
|
|
18
|
+
|
|
19
|
+
def __init__(self) -> None:
|
|
20
|
+
self._user_id: str | None = None
|
|
21
|
+
self._org_id: str | None = None
|
|
22
|
+
self._org_role: str = "org:admin"
|
|
23
|
+
|
|
24
|
+
def configure(
|
|
25
|
+
self,
|
|
26
|
+
user_id: str | None,
|
|
27
|
+
org_id: str | None = None,
|
|
28
|
+
org_role: str = "org:admin",
|
|
29
|
+
) -> None:
|
|
30
|
+
"""Configure the authentication state."""
|
|
31
|
+
|
|
32
|
+
self._user_id = user_id
|
|
33
|
+
self._org_id = org_id
|
|
34
|
+
self._org_role = org_role
|
|
35
|
+
|
|
36
|
+
def get_result(self) -> MockAuthResult:
|
|
37
|
+
"""Get the current authentication result."""
|
|
38
|
+
|
|
39
|
+
if self._user_id is None:
|
|
40
|
+
return MockAuthResult.signed_out()
|
|
41
|
+
|
|
42
|
+
return MockAuthResult.signed_in(
|
|
43
|
+
user_id=self._user_id,
|
|
44
|
+
org_id=self._org_id,
|
|
45
|
+
org_role=self._org_role,
|
|
46
|
+
)
|
|
47
|
+
|
|
48
|
+
def snapshot(self) -> AuthSnapshot:
|
|
49
|
+
"""Take a snapshot of the current state."""
|
|
50
|
+
|
|
51
|
+
return AuthSnapshot(
|
|
52
|
+
user_id=self._user_id,
|
|
53
|
+
org_id=self._org_id,
|
|
54
|
+
org_role=self._org_role,
|
|
55
|
+
)
|
|
56
|
+
|
|
57
|
+
def restore(self, snapshot: AuthSnapshot) -> None:
|
|
58
|
+
"""Restore state from a snapshot."""
|
|
59
|
+
|
|
60
|
+
self._user_id = snapshot.user_id
|
|
61
|
+
self._org_id = snapshot.org_id
|
|
62
|
+
self._org_role = snapshot.org_role
|
|
63
|
+
|
|
64
|
+
def reset(self) -> None:
|
|
65
|
+
"""Reset authentication state to defaults."""
|
|
66
|
+
|
|
67
|
+
self._user_id = None
|
|
68
|
+
self._org_id = None
|
|
69
|
+
self._org_role = "org:admin"
|
|
70
|
+
|