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.
@@ -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
+