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,39 @@
|
|
|
1
|
+
from pytest_clerk_mock.client import MockClerkClient
|
|
2
|
+
from pytest_clerk_mock.helpers import (
|
|
3
|
+
mock_clerk_user_creation,
|
|
4
|
+
mock_clerk_user_creation_failure,
|
|
5
|
+
mock_clerk_user_exists,
|
|
6
|
+
)
|
|
7
|
+
from pytest_clerk_mock.models.auth import MockAuthResult, MockClerkUser
|
|
8
|
+
from pytest_clerk_mock.models.organization import (
|
|
9
|
+
MockOrganization,
|
|
10
|
+
MockOrganizationMembership,
|
|
11
|
+
MockOrganizationMembershipsResponse,
|
|
12
|
+
)
|
|
13
|
+
from pytest_clerk_mock.models.user import MockEmailAddress, MockPhoneNumber, MockUser
|
|
14
|
+
from pytest_clerk_mock.plugin import (
|
|
15
|
+
create_mock_clerk_fixture,
|
|
16
|
+
mock_clerk,
|
|
17
|
+
mock_clerk_backend,
|
|
18
|
+
)
|
|
19
|
+
from pytest_clerk_mock.services.users import MockListResponse, UserNotFoundError
|
|
20
|
+
|
|
21
|
+
__all__ = [
|
|
22
|
+
"create_mock_clerk_fixture",
|
|
23
|
+
"mock_clerk",
|
|
24
|
+
"mock_clerk_backend",
|
|
25
|
+
"mock_clerk_user_creation",
|
|
26
|
+
"mock_clerk_user_creation_failure",
|
|
27
|
+
"mock_clerk_user_exists",
|
|
28
|
+
"MockAuthResult",
|
|
29
|
+
"MockClerkClient",
|
|
30
|
+
"MockClerkUser",
|
|
31
|
+
"MockEmailAddress",
|
|
32
|
+
"MockListResponse",
|
|
33
|
+
"MockOrganization",
|
|
34
|
+
"MockOrganizationMembership",
|
|
35
|
+
"MockOrganizationMembershipsResponse",
|
|
36
|
+
"MockPhoneNumber",
|
|
37
|
+
"MockUser",
|
|
38
|
+
"UserNotFoundError",
|
|
39
|
+
]
|
|
@@ -0,0 +1,189 @@
|
|
|
1
|
+
from contextlib import contextmanager
|
|
2
|
+
from typing import Any, Generator
|
|
3
|
+
|
|
4
|
+
from pytest_clerk_mock.models.auth import MockAuthResult, MockClerkUser
|
|
5
|
+
from pytest_clerk_mock.models.organization import (
|
|
6
|
+
MockOrganization,
|
|
7
|
+
MockOrganizationMembership,
|
|
8
|
+
MockOrganizationMembershipsResponse,
|
|
9
|
+
)
|
|
10
|
+
from pytest_clerk_mock.services.auth import MockAuthState
|
|
11
|
+
from pytest_clerk_mock.services.users import MockUsersClient
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
class MockClerkClient:
|
|
15
|
+
"""Mock implementation of Clerk's SDK client."""
|
|
16
|
+
|
|
17
|
+
def __init__(
|
|
18
|
+
self,
|
|
19
|
+
default_user_id: str | None = "user_test_owner",
|
|
20
|
+
default_org_id: str | None = "org_test_123",
|
|
21
|
+
default_org_role: str = "org:admin",
|
|
22
|
+
) -> None:
|
|
23
|
+
self._users = MockUsersClient()
|
|
24
|
+
self._auth_state = MockAuthState()
|
|
25
|
+
self._memberships: dict[str, list[MockOrganizationMembership]] = {}
|
|
26
|
+
|
|
27
|
+
if default_user_id is not None:
|
|
28
|
+
self._auth_state.configure(default_user_id, default_org_id, default_org_role)
|
|
29
|
+
|
|
30
|
+
@property
|
|
31
|
+
def users(self) -> MockUsersClient:
|
|
32
|
+
"""Access the Users API."""
|
|
33
|
+
|
|
34
|
+
return self._users
|
|
35
|
+
|
|
36
|
+
def reset(self) -> None:
|
|
37
|
+
"""Reset all mock services."""
|
|
38
|
+
|
|
39
|
+
self._users.reset()
|
|
40
|
+
self._auth_state.reset()
|
|
41
|
+
self._memberships.clear()
|
|
42
|
+
|
|
43
|
+
def authenticate_request(
|
|
44
|
+
self,
|
|
45
|
+
request: Any,
|
|
46
|
+
options: Any = None,
|
|
47
|
+
) -> MockAuthResult:
|
|
48
|
+
"""Mock implementation of Clerk's authenticate_request.
|
|
49
|
+
|
|
50
|
+
Args:
|
|
51
|
+
request: The FastAPI/Starlette request object (ignored in mock)
|
|
52
|
+
options: AuthenticateRequestOptions (ignored in mock)
|
|
53
|
+
|
|
54
|
+
Returns:
|
|
55
|
+
MockAuthResult with current auth state
|
|
56
|
+
"""
|
|
57
|
+
|
|
58
|
+
return self._auth_state.get_result()
|
|
59
|
+
|
|
60
|
+
def configure_auth(
|
|
61
|
+
self,
|
|
62
|
+
user_id: str | None,
|
|
63
|
+
org_id: str | None = None,
|
|
64
|
+
org_role: str = "org:admin",
|
|
65
|
+
) -> None:
|
|
66
|
+
"""Configure the authentication state.
|
|
67
|
+
|
|
68
|
+
Args:
|
|
69
|
+
user_id: The user ID to return in auth results (None for unauthenticated)
|
|
70
|
+
org_id: The organization ID for the authenticated user
|
|
71
|
+
org_role: The role of the user in the organization
|
|
72
|
+
"""
|
|
73
|
+
|
|
74
|
+
self._auth_state.configure(user_id, org_id, org_role)
|
|
75
|
+
|
|
76
|
+
def configure_auth_from_user(
|
|
77
|
+
self,
|
|
78
|
+
user: MockClerkUser,
|
|
79
|
+
org_id: str | None = None,
|
|
80
|
+
org_role: str = "org:admin",
|
|
81
|
+
) -> None:
|
|
82
|
+
"""Configure auth state using a predefined MockClerkUser.
|
|
83
|
+
|
|
84
|
+
Args:
|
|
85
|
+
user: The MockClerkUser enum value
|
|
86
|
+
org_id: The organization ID for the authenticated user
|
|
87
|
+
org_role: The role of the user in the organization
|
|
88
|
+
"""
|
|
89
|
+
|
|
90
|
+
self._auth_state.configure(user.value, org_id, org_role)
|
|
91
|
+
|
|
92
|
+
@contextmanager
|
|
93
|
+
def as_user(
|
|
94
|
+
self,
|
|
95
|
+
user_id: str | None,
|
|
96
|
+
org_id: str | None = None,
|
|
97
|
+
org_role: str = "org:admin",
|
|
98
|
+
) -> Generator[None, None, None]:
|
|
99
|
+
"""Context manager to temporarily switch to a different user.
|
|
100
|
+
|
|
101
|
+
Args:
|
|
102
|
+
user_id: The user ID to use within the context
|
|
103
|
+
org_id: The organization ID for the user
|
|
104
|
+
org_role: The role of the user in the organization
|
|
105
|
+
|
|
106
|
+
Yields:
|
|
107
|
+
None
|
|
108
|
+
|
|
109
|
+
Example:
|
|
110
|
+
with mock_clerk.as_user("user_123", org_id="org_456"):
|
|
111
|
+
# Requests will be authenticated as user_123
|
|
112
|
+
pass
|
|
113
|
+
"""
|
|
114
|
+
|
|
115
|
+
previous = self._auth_state.snapshot()
|
|
116
|
+
self._auth_state.configure(user_id, org_id, org_role)
|
|
117
|
+
|
|
118
|
+
try:
|
|
119
|
+
yield
|
|
120
|
+
finally:
|
|
121
|
+
self._auth_state.restore(previous)
|
|
122
|
+
|
|
123
|
+
@contextmanager
|
|
124
|
+
def as_clerk_user(
|
|
125
|
+
self,
|
|
126
|
+
user: MockClerkUser,
|
|
127
|
+
org_id: str | None = None,
|
|
128
|
+
org_role: str = "org:admin",
|
|
129
|
+
) -> Generator[None, None, None]:
|
|
130
|
+
"""Context manager using predefined MockClerkUser.
|
|
131
|
+
|
|
132
|
+
Args:
|
|
133
|
+
user: The MockClerkUser enum value
|
|
134
|
+
org_id: The organization ID for the user
|
|
135
|
+
org_role: The role of the user in the organization
|
|
136
|
+
|
|
137
|
+
Example:
|
|
138
|
+
with mock_clerk.as_clerk_user(MockClerkUser.TEAM_OWNER, org_id="org_123"):
|
|
139
|
+
# Requests will be authenticated as team owner
|
|
140
|
+
pass
|
|
141
|
+
"""
|
|
142
|
+
|
|
143
|
+
with self.as_user(user.value, org_id, org_role):
|
|
144
|
+
yield
|
|
145
|
+
|
|
146
|
+
def add_organization_membership(
|
|
147
|
+
self,
|
|
148
|
+
user_id: str,
|
|
149
|
+
org_id: str,
|
|
150
|
+
role: str = "org:member",
|
|
151
|
+
org_name: str = "",
|
|
152
|
+
) -> MockOrganizationMembership:
|
|
153
|
+
"""Add an organization membership for a user.
|
|
154
|
+
|
|
155
|
+
Args:
|
|
156
|
+
user_id: The user ID to add membership for
|
|
157
|
+
org_id: The organization ID
|
|
158
|
+
role: The role in the organization
|
|
159
|
+
org_name: Optional organization name
|
|
160
|
+
|
|
161
|
+
Returns:
|
|
162
|
+
The created membership
|
|
163
|
+
"""
|
|
164
|
+
|
|
165
|
+
membership = MockOrganizationMembership(
|
|
166
|
+
id=f"orgmem_{user_id}_{org_id}",
|
|
167
|
+
role=role,
|
|
168
|
+
organization=MockOrganization(id=org_id, name=org_name),
|
|
169
|
+
)
|
|
170
|
+
|
|
171
|
+
if user_id not in self._memberships:
|
|
172
|
+
self._memberships[user_id] = []
|
|
173
|
+
|
|
174
|
+
self._memberships[user_id].append(membership)
|
|
175
|
+
|
|
176
|
+
return membership
|
|
177
|
+
|
|
178
|
+
async def _get_organization_memberships_async(
|
|
179
|
+
self,
|
|
180
|
+
user_id: str,
|
|
181
|
+
) -> MockOrganizationMembershipsResponse:
|
|
182
|
+
"""Get organization memberships for a user (internal async method)."""
|
|
183
|
+
|
|
184
|
+
memberships = self._memberships.get(user_id, [])
|
|
185
|
+
|
|
186
|
+
return MockOrganizationMembershipsResponse(
|
|
187
|
+
data=memberships,
|
|
188
|
+
total_count=len(memberships),
|
|
189
|
+
)
|
|
@@ -0,0 +1,148 @@
|
|
|
1
|
+
from contextlib import contextmanager
|
|
2
|
+
from typing import Generator
|
|
3
|
+
from unittest.mock import MagicMock, patch
|
|
4
|
+
|
|
5
|
+
import httpx
|
|
6
|
+
from clerk_backend_api import SDKError
|
|
7
|
+
from clerk_backend_api.models import ClerkErrors
|
|
8
|
+
from clerk_backend_api.models.clerkerror import ClerkError
|
|
9
|
+
from clerk_backend_api.models.clerkerrors import ClerkErrorsData
|
|
10
|
+
from pydantic import BaseModel
|
|
11
|
+
|
|
12
|
+
EMAIL_EXISTS_ERROR_CODE = "form_identifier_exists"
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
class MockClerkUserResponse(BaseModel):
|
|
16
|
+
"""Simple mock Clerk user returned from create_async."""
|
|
17
|
+
|
|
18
|
+
id: str
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
class MockClerkUserListResponse:
|
|
22
|
+
"""Mock response from Clerk users.list_async."""
|
|
23
|
+
|
|
24
|
+
def __init__(self, data: list[MockClerkUserResponse]):
|
|
25
|
+
self.data = data
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
@contextmanager
|
|
29
|
+
def mock_clerk_user_creation(
|
|
30
|
+
patch_target: str,
|
|
31
|
+
clerk_user_id: str = "user_clerk_mock_123",
|
|
32
|
+
) -> Generator[MagicMock, None, None]:
|
|
33
|
+
"""Mock Clerk user creation API.
|
|
34
|
+
|
|
35
|
+
Args:
|
|
36
|
+
patch_target: The module path to patch.
|
|
37
|
+
clerk_user_id: The ID to return for the created user.
|
|
38
|
+
|
|
39
|
+
Yields:
|
|
40
|
+
The mock object for assertions.
|
|
41
|
+
|
|
42
|
+
Example:
|
|
43
|
+
with mock_clerk_user_creation("api.core.clerk.clerk.users.create_async", "user_123") as mock:
|
|
44
|
+
# Your test code here
|
|
45
|
+
mock.assert_called_once()
|
|
46
|
+
"""
|
|
47
|
+
|
|
48
|
+
with patch(patch_target) as mock_create:
|
|
49
|
+
mock_create.return_value = MockClerkUserResponse(id=clerk_user_id)
|
|
50
|
+
yield mock_create
|
|
51
|
+
|
|
52
|
+
|
|
53
|
+
@contextmanager
|
|
54
|
+
def mock_clerk_user_creation_failure(
|
|
55
|
+
patch_target: str,
|
|
56
|
+
error_message: str = "Clerk API error",
|
|
57
|
+
) -> Generator[MagicMock, None, None]:
|
|
58
|
+
"""Mock Clerk user creation API to simulate a failure.
|
|
59
|
+
|
|
60
|
+
Args:
|
|
61
|
+
patch_target: The module path to patch.
|
|
62
|
+
error_message: The error message to include.
|
|
63
|
+
|
|
64
|
+
Yields:
|
|
65
|
+
The mock object for assertions.
|
|
66
|
+
|
|
67
|
+
Example:
|
|
68
|
+
with mock_clerk_user_creation_failure("api.core.clerk.clerk.users.create_async") as mock:
|
|
69
|
+
with pytest.raises(ErrorResponse):
|
|
70
|
+
# Your test code here
|
|
71
|
+
"""
|
|
72
|
+
|
|
73
|
+
mock_response = MagicMock()
|
|
74
|
+
mock_response.status_code = 500
|
|
75
|
+
mock_response.text = error_message
|
|
76
|
+
mock_response.headers = {}
|
|
77
|
+
|
|
78
|
+
with patch(patch_target) as mock_create:
|
|
79
|
+
mock_create.side_effect = SDKError(error_message, mock_response)
|
|
80
|
+
yield mock_create
|
|
81
|
+
|
|
82
|
+
|
|
83
|
+
@contextmanager
|
|
84
|
+
def mock_clerk_user_exists(
|
|
85
|
+
create_patch_target: str,
|
|
86
|
+
list_patch_target: str,
|
|
87
|
+
existing_clerk_user_id: str = "user_clerk_existing_123",
|
|
88
|
+
) -> Generator[tuple[MagicMock, MagicMock], None, None]:
|
|
89
|
+
"""Mock Clerk user creation to simulate an email already exists scenario.
|
|
90
|
+
|
|
91
|
+
This simulates the case where create_async fails because the email is taken,
|
|
92
|
+
but list_async returns the existing user so we can link it.
|
|
93
|
+
|
|
94
|
+
Args:
|
|
95
|
+
create_patch_target: The module path to patch for create_async.
|
|
96
|
+
list_patch_target: The module path to patch for list_async.
|
|
97
|
+
existing_clerk_user_id: The ID of the existing Clerk user to return.
|
|
98
|
+
|
|
99
|
+
Yields:
|
|
100
|
+
A tuple of (mock_create, mock_list) for assertions.
|
|
101
|
+
|
|
102
|
+
Example:
|
|
103
|
+
with mock_clerk_user_exists(
|
|
104
|
+
"api.core.clerk.clerk.users.create_async",
|
|
105
|
+
"api.core.clerk.clerk.users.list_async",
|
|
106
|
+
"user_existing_123"
|
|
107
|
+
) as (mock_create, mock_list):
|
|
108
|
+
# Your test code here
|
|
109
|
+
mock_create.assert_called_once()
|
|
110
|
+
mock_list.assert_called_once()
|
|
111
|
+
"""
|
|
112
|
+
|
|
113
|
+
mock_response = httpx.Response(
|
|
114
|
+
status_code=422,
|
|
115
|
+
json={
|
|
116
|
+
"errors": [
|
|
117
|
+
{
|
|
118
|
+
"message": "That email address is taken. Please try another.",
|
|
119
|
+
"long_message": "That email address is taken. Please try another.",
|
|
120
|
+
"code": EMAIL_EXISTS_ERROR_CODE,
|
|
121
|
+
}
|
|
122
|
+
]
|
|
123
|
+
},
|
|
124
|
+
)
|
|
125
|
+
|
|
126
|
+
email_exists_error = ClerkErrors(
|
|
127
|
+
data=ClerkErrorsData(
|
|
128
|
+
errors=[
|
|
129
|
+
ClerkError(
|
|
130
|
+
message="That email address is taken. Please try another.",
|
|
131
|
+
long_message="That email address is taken. Please try another.",
|
|
132
|
+
code=EMAIL_EXISTS_ERROR_CODE,
|
|
133
|
+
)
|
|
134
|
+
]
|
|
135
|
+
),
|
|
136
|
+
raw_response=mock_response,
|
|
137
|
+
)
|
|
138
|
+
|
|
139
|
+
with (
|
|
140
|
+
patch(create_patch_target) as mock_create,
|
|
141
|
+
patch(list_patch_target) as mock_list,
|
|
142
|
+
):
|
|
143
|
+
mock_create.side_effect = email_exists_error
|
|
144
|
+
mock_list.return_value = MockClerkUserListResponse(
|
|
145
|
+
data=[MockClerkUserResponse(id=existing_clerk_user_id)]
|
|
146
|
+
)
|
|
147
|
+
yield mock_create, mock_list
|
|
148
|
+
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
from pytest_clerk_mock.models.auth import MockAuthResult, MockClerkUser
|
|
2
|
+
from pytest_clerk_mock.models.organization import (
|
|
3
|
+
MockOrganization,
|
|
4
|
+
MockOrganizationMembership,
|
|
5
|
+
MockOrganizationMembershipsResponse,
|
|
6
|
+
)
|
|
7
|
+
from pytest_clerk_mock.models.user import MockEmailAddress, MockPhoneNumber, MockUser
|
|
8
|
+
|
|
9
|
+
__all__ = [
|
|
10
|
+
"MockAuthResult",
|
|
11
|
+
"MockClerkUser",
|
|
12
|
+
"MockEmailAddress",
|
|
13
|
+
"MockOrganization",
|
|
14
|
+
"MockOrganizationMembership",
|
|
15
|
+
"MockOrganizationMembershipsResponse",
|
|
16
|
+
"MockPhoneNumber",
|
|
17
|
+
"MockUser",
|
|
18
|
+
]
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
from enum import Enum
|
|
2
|
+
from typing import Any
|
|
3
|
+
|
|
4
|
+
from pydantic import BaseModel, Field
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
class MockClerkUser(Enum):
|
|
8
|
+
"""Predefined user types for common test scenarios."""
|
|
9
|
+
|
|
10
|
+
TEAM_OWNER = "user_test_owner"
|
|
11
|
+
TEAM_MEMBER = "user_test_member"
|
|
12
|
+
GUEST = "user_test_guest"
|
|
13
|
+
UNAUTHENTICATED = None
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
class MockAuthResult(BaseModel):
|
|
17
|
+
"""Mock result from Clerk authenticate_request."""
|
|
18
|
+
|
|
19
|
+
is_signed_in: bool = False
|
|
20
|
+
payload: dict[str, Any] = Field(default_factory=dict)
|
|
21
|
+
|
|
22
|
+
@property
|
|
23
|
+
def is_authenticated(self) -> bool:
|
|
24
|
+
"""Alias for is_signed_in."""
|
|
25
|
+
|
|
26
|
+
return self.is_signed_in
|
|
27
|
+
|
|
28
|
+
@classmethod
|
|
29
|
+
def signed_in(
|
|
30
|
+
cls,
|
|
31
|
+
user_id: str,
|
|
32
|
+
org_id: str | None = None,
|
|
33
|
+
org_role: str = "org:admin",
|
|
34
|
+
) -> "MockAuthResult":
|
|
35
|
+
"""Create a signed-in auth result."""
|
|
36
|
+
|
|
37
|
+
return cls(
|
|
38
|
+
is_signed_in=True,
|
|
39
|
+
payload={
|
|
40
|
+
"sub": user_id,
|
|
41
|
+
"org_id": org_id,
|
|
42
|
+
"org_role": org_role,
|
|
43
|
+
},
|
|
44
|
+
)
|
|
45
|
+
|
|
46
|
+
@classmethod
|
|
47
|
+
def signed_out(cls) -> "MockAuthResult":
|
|
48
|
+
"""Create a signed-out auth result."""
|
|
49
|
+
|
|
50
|
+
return cls(is_signed_in=False, payload={})
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
from pydantic import BaseModel, Field
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
class MockOrganization(BaseModel):
|
|
5
|
+
"""Represents a Clerk Organization."""
|
|
6
|
+
|
|
7
|
+
id: str
|
|
8
|
+
name: str = ""
|
|
9
|
+
slug: str = ""
|
|
10
|
+
created_at: int = 0
|
|
11
|
+
updated_at: int = 0
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
class MockOrganizationMembership(BaseModel):
|
|
15
|
+
"""Represents a user's membership in an organization."""
|
|
16
|
+
|
|
17
|
+
id: str
|
|
18
|
+
role: str = "org:member"
|
|
19
|
+
organization: MockOrganization | None = None
|
|
20
|
+
public_user_data: dict | None = None
|
|
21
|
+
created_at: int = 0
|
|
22
|
+
updated_at: int = 0
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
class MockOrganizationMembershipsResponse(BaseModel):
|
|
26
|
+
"""Response from get_organization_memberships_async."""
|
|
27
|
+
|
|
28
|
+
data: list[MockOrganizationMembership] = Field(default_factory=list)
|
|
29
|
+
total_count: int = 0
|
|
30
|
+
|
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
from datetime import datetime
|
|
2
|
+
from typing import Self
|
|
3
|
+
|
|
4
|
+
from pydantic import BaseModel, ConfigDict, Field
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
class MockEmailAddress(BaseModel):
|
|
8
|
+
"""Represents a Clerk email address object."""
|
|
9
|
+
|
|
10
|
+
model_config = ConfigDict(populate_by_name=True)
|
|
11
|
+
|
|
12
|
+
id: str
|
|
13
|
+
email_address: str
|
|
14
|
+
verification: dict | None = None
|
|
15
|
+
linked_to: list[dict] = Field(default_factory=list)
|
|
16
|
+
|
|
17
|
+
@classmethod
|
|
18
|
+
def create(cls, email: str, email_id: str) -> Self:
|
|
19
|
+
"""Create a verified email address."""
|
|
20
|
+
|
|
21
|
+
return cls(
|
|
22
|
+
id=email_id,
|
|
23
|
+
email_address=email,
|
|
24
|
+
verification={"status": "verified", "strategy": "email_code"},
|
|
25
|
+
linked_to=[],
|
|
26
|
+
)
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
class MockPhoneNumber(BaseModel):
|
|
30
|
+
"""Represents a Clerk phone number object."""
|
|
31
|
+
|
|
32
|
+
model_config = ConfigDict(populate_by_name=True)
|
|
33
|
+
|
|
34
|
+
id: str
|
|
35
|
+
phone_number: str
|
|
36
|
+
verification: dict | None = None
|
|
37
|
+
linked_to: list[dict] = Field(default_factory=list)
|
|
38
|
+
|
|
39
|
+
@classmethod
|
|
40
|
+
def create(cls, phone: str, phone_id: str) -> Self:
|
|
41
|
+
"""Create a verified phone number."""
|
|
42
|
+
|
|
43
|
+
return cls(
|
|
44
|
+
id=phone_id,
|
|
45
|
+
phone_number=phone,
|
|
46
|
+
verification={"status": "verified", "strategy": "phone_code"},
|
|
47
|
+
linked_to=[],
|
|
48
|
+
)
|
|
49
|
+
|
|
50
|
+
|
|
51
|
+
class MockUser(BaseModel):
|
|
52
|
+
"""Represents a Clerk User object."""
|
|
53
|
+
|
|
54
|
+
model_config = ConfigDict(populate_by_name=True)
|
|
55
|
+
|
|
56
|
+
id: str
|
|
57
|
+
external_id: str | None = None
|
|
58
|
+
primary_email_address_id: str | None = None
|
|
59
|
+
primary_phone_number_id: str | None = None
|
|
60
|
+
primary_web3_wallet_id: str | None = None
|
|
61
|
+
username: str | None = None
|
|
62
|
+
first_name: str | None = None
|
|
63
|
+
last_name: str | None = None
|
|
64
|
+
profile_image_url: str = ""
|
|
65
|
+
image_url: str = ""
|
|
66
|
+
has_image: bool = False
|
|
67
|
+
public_metadata: dict = Field(default_factory=dict)
|
|
68
|
+
private_metadata: dict = Field(default_factory=dict)
|
|
69
|
+
unsafe_metadata: dict = Field(default_factory=dict)
|
|
70
|
+
email_addresses: list[MockEmailAddress] = Field(default_factory=list)
|
|
71
|
+
phone_numbers: list[MockPhoneNumber] = Field(default_factory=list)
|
|
72
|
+
web3_wallets: list[dict] = Field(default_factory=list)
|
|
73
|
+
passkeys: list[dict] = Field(default_factory=list)
|
|
74
|
+
password_enabled: bool = False
|
|
75
|
+
two_factor_enabled: bool = False
|
|
76
|
+
totp_enabled: bool = False
|
|
77
|
+
backup_code_enabled: bool = False
|
|
78
|
+
external_accounts: list[dict] = Field(default_factory=list)
|
|
79
|
+
saml_accounts: list[dict] = Field(default_factory=list)
|
|
80
|
+
last_sign_in_at: int | None = None
|
|
81
|
+
banned: bool = False
|
|
82
|
+
locked: bool = False
|
|
83
|
+
lockout_expires_in_seconds: int | None = None
|
|
84
|
+
verification_attempts_remaining: int | None = None
|
|
85
|
+
created_at: int = Field(default_factory=lambda: int(datetime.now().timestamp() * 1000))
|
|
86
|
+
updated_at: int = Field(default_factory=lambda: int(datetime.now().timestamp() * 1000))
|
|
87
|
+
delete_self_enabled: bool = True
|
|
88
|
+
create_organization_enabled: bool = True
|
|
89
|
+
last_active_at: int | None = None
|
|
90
|
+
create_organizations_limit: int | None = None
|
|
91
|
+
legal_accepted_at: int | None = None
|
|
92
|
+
|