core-framework 0.12.6__tar.gz → 0.12.7__tar.gz
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.
- {core_framework-0.12.6 → core_framework-0.12.7}/PKG-INFO +1 -1
- {core_framework-0.12.6 → core_framework-0.12.7}/core/__init__.py +1 -1
- {core_framework-0.12.6 → core_framework-0.12.7}/core/auth/decorators.py +28 -7
- {core_framework-0.12.6 → core_framework-0.12.7}/core/permissions.py +29 -8
- {core_framework-0.12.6 → core_framework-0.12.7}/pyproject.toml +1 -1
- core_framework-0.12.7/tests/test_auth_helpers.py +206 -0
- core_framework-0.12.7/tests/test_imports.py +263 -0
- core_framework-0.12.7/tests/test_permissions.py +373 -0
- {core_framework-0.12.6 → core_framework-0.12.7}/.gitignore +0 -0
- {core_framework-0.12.6 → core_framework-0.12.7}/README.md +0 -0
- {core_framework-0.12.6 → core_framework-0.12.7}/core/app.py +0 -0
- {core_framework-0.12.6 → core_framework-0.12.7}/core/auth/__init__.py +0 -0
- {core_framework-0.12.6 → core_framework-0.12.7}/core/auth/backends.py +0 -0
- {core_framework-0.12.6 → core_framework-0.12.7}/core/auth/base.py +0 -0
- {core_framework-0.12.6 → core_framework-0.12.7}/core/auth/hashers.py +0 -0
- {core_framework-0.12.6 → core_framework-0.12.7}/core/auth/helpers.py +0 -0
- {core_framework-0.12.6 → core_framework-0.12.7}/core/auth/middleware.py +0 -0
- {core_framework-0.12.6 → core_framework-0.12.7}/core/auth/models.py +0 -0
- {core_framework-0.12.6 → core_framework-0.12.7}/core/auth/permissions.py +0 -0
- {core_framework-0.12.6 → core_framework-0.12.7}/core/auth/schemas.py +0 -0
- {core_framework-0.12.6 → core_framework-0.12.7}/core/auth/tokens.py +0 -0
- {core_framework-0.12.6 → core_framework-0.12.7}/core/auth/views.py +0 -0
- {core_framework-0.12.6 → core_framework-0.12.7}/core/choices.py +0 -0
- {core_framework-0.12.6 → core_framework-0.12.7}/core/cli/__init__.py +0 -0
- {core_framework-0.12.6 → core_framework-0.12.7}/core/cli/main.py +0 -0
- {core_framework-0.12.6 → core_framework-0.12.7}/core/config.py +0 -0
- {core_framework-0.12.6 → core_framework-0.12.7}/core/database.py +0 -0
- {core_framework-0.12.6 → core_framework-0.12.7}/core/datetime.py +0 -0
- {core_framework-0.12.6 → core_framework-0.12.7}/core/dependencies.py +0 -0
- {core_framework-0.12.6 → core_framework-0.12.7}/core/deployment/__init__.py +0 -0
- {core_framework-0.12.6 → core_framework-0.12.7}/core/deployment/docker.py +0 -0
- {core_framework-0.12.6 → core_framework-0.12.7}/core/deployment/kubernetes.py +0 -0
- {core_framework-0.12.6 → core_framework-0.12.7}/core/deployment/pm2.py +0 -0
- {core_framework-0.12.6 → core_framework-0.12.7}/core/exceptions.py +0 -0
- {core_framework-0.12.6 → core_framework-0.12.7}/core/fields.py +0 -0
- {core_framework-0.12.6 → core_framework-0.12.7}/core/messaging/__init__.py +0 -0
- {core_framework-0.12.6 → core_framework-0.12.7}/core/messaging/avro.py +0 -0
- {core_framework-0.12.6 → core_framework-0.12.7}/core/messaging/base.py +0 -0
- {core_framework-0.12.6 → core_framework-0.12.7}/core/messaging/config.py +0 -0
- {core_framework-0.12.6 → core_framework-0.12.7}/core/messaging/confluent/__init__.py +0 -0
- {core_framework-0.12.6 → core_framework-0.12.7}/core/messaging/confluent/consumer.py +0 -0
- {core_framework-0.12.6 → core_framework-0.12.7}/core/messaging/confluent/producer.py +0 -0
- {core_framework-0.12.6 → core_framework-0.12.7}/core/messaging/decorators.py +0 -0
- {core_framework-0.12.6 → core_framework-0.12.7}/core/messaging/kafka/__init__.py +0 -0
- {core_framework-0.12.6 → core_framework-0.12.7}/core/messaging/kafka/admin.py +0 -0
- {core_framework-0.12.6 → core_framework-0.12.7}/core/messaging/kafka/broker.py +0 -0
- {core_framework-0.12.6 → core_framework-0.12.7}/core/messaging/kafka/consumer.py +0 -0
- {core_framework-0.12.6 → core_framework-0.12.7}/core/messaging/kafka/producer.py +0 -0
- {core_framework-0.12.6 → core_framework-0.12.7}/core/messaging/rabbitmq/__init__.py +0 -0
- {core_framework-0.12.6 → core_framework-0.12.7}/core/messaging/rabbitmq/broker.py +0 -0
- {core_framework-0.12.6 → core_framework-0.12.7}/core/messaging/rabbitmq/consumer.py +0 -0
- {core_framework-0.12.6 → core_framework-0.12.7}/core/messaging/rabbitmq/producer.py +0 -0
- {core_framework-0.12.6 → core_framework-0.12.7}/core/messaging/redis/__init__.py +0 -0
- {core_framework-0.12.6 → core_framework-0.12.7}/core/messaging/redis/broker.py +0 -0
- {core_framework-0.12.6 → core_framework-0.12.7}/core/messaging/redis/consumer.py +0 -0
- {core_framework-0.12.6 → core_framework-0.12.7}/core/messaging/redis/producer.py +0 -0
- {core_framework-0.12.6 → core_framework-0.12.7}/core/messaging/registry.py +0 -0
- {core_framework-0.12.6 → core_framework-0.12.7}/core/messaging/topics.py +0 -0
- {core_framework-0.12.6 → core_framework-0.12.7}/core/messaging/workers.py +0 -0
- {core_framework-0.12.6 → core_framework-0.12.7}/core/middleware.py +0 -0
- {core_framework-0.12.6 → core_framework-0.12.7}/core/migrations/__init__.py +0 -0
- {core_framework-0.12.6 → core_framework-0.12.7}/core/migrations/analyzer.py +0 -0
- {core_framework-0.12.6 → core_framework-0.12.7}/core/migrations/cli.py +0 -0
- {core_framework-0.12.6 → core_framework-0.12.7}/core/migrations/engine.py +0 -0
- {core_framework-0.12.6 → core_framework-0.12.7}/core/migrations/migration.py +0 -0
- {core_framework-0.12.6 → core_framework-0.12.7}/core/migrations/operations.py +0 -0
- {core_framework-0.12.6 → core_framework-0.12.7}/core/migrations/state.py +0 -0
- {core_framework-0.12.6 → core_framework-0.12.7}/core/models.py +0 -0
- {core_framework-0.12.6 → core_framework-0.12.7}/core/querysets.py +0 -0
- {core_framework-0.12.6 → core_framework-0.12.7}/core/relations.py +0 -0
- {core_framework-0.12.6 → core_framework-0.12.7}/core/routing.py +0 -0
- {core_framework-0.12.6 → core_framework-0.12.7}/core/serializers.py +0 -0
- {core_framework-0.12.6 → core_framework-0.12.7}/core/tasks/__init__.py +0 -0
- {core_framework-0.12.6 → core_framework-0.12.7}/core/tasks/base.py +0 -0
- {core_framework-0.12.6 → core_framework-0.12.7}/core/tasks/config.py +0 -0
- {core_framework-0.12.6 → core_framework-0.12.7}/core/tasks/decorators.py +0 -0
- {core_framework-0.12.6 → core_framework-0.12.7}/core/tasks/registry.py +0 -0
- {core_framework-0.12.6 → core_framework-0.12.7}/core/tasks/scheduler.py +0 -0
- {core_framework-0.12.6 → core_framework-0.12.7}/core/tasks/worker.py +0 -0
- {core_framework-0.12.6 → core_framework-0.12.7}/core/tenancy.py +0 -0
- {core_framework-0.12.6 → core_framework-0.12.7}/core/validators.py +0 -0
- {core_framework-0.12.6 → core_framework-0.12.7}/core/views.py +0 -0
- {core_framework-0.12.6 → core_framework-0.12.7}/docs/01-quickstart.md +0 -0
- {core_framework-0.12.6 → core_framework-0.12.7}/docs/02-viewsets.md +0 -0
- {core_framework-0.12.6 → core_framework-0.12.7}/docs/03-authentication.md +0 -0
- {core_framework-0.12.6 → core_framework-0.12.7}/docs/04-messaging.md +0 -0
- {core_framework-0.12.6 → core_framework-0.12.7}/docs/05-multi-service.md +0 -0
- {core_framework-0.12.6 → core_framework-0.12.7}/docs/06-tasks.md +0 -0
- {core_framework-0.12.6 → core_framework-0.12.7}/docs/07-deployment.md +0 -0
- {core_framework-0.12.6 → core_framework-0.12.7}/docs/08-complete-example.md +0 -0
- {core_framework-0.12.6 → core_framework-0.12.7}/docs/09-settings.md +0 -0
- {core_framework-0.12.6 → core_framework-0.12.7}/docs/10-migrations.md +0 -0
- {core_framework-0.12.6 → core_framework-0.12.7}/docs/11-permissions.md +0 -0
- {core_framework-0.12.6 → core_framework-0.12.7}/docs/12-auth-backends.md +0 -0
- {core_framework-0.12.6 → core_framework-0.12.7}/docs/13-validators.md +0 -0
- {core_framework-0.12.6 → core_framework-0.12.7}/docs/14-querysets.md +0 -0
- {core_framework-0.12.6 → core_framework-0.12.7}/docs/15-routing.md +0 -0
- {core_framework-0.12.6 → core_framework-0.12.7}/docs/16-serializers.md +0 -0
- {core_framework-0.12.6 → core_framework-0.12.7}/docs/17-datetime.md +0 -0
- {core_framework-0.12.6 → core_framework-0.12.7}/docs/18-dependencies.md +0 -0
- {core_framework-0.12.6 → core_framework-0.12.7}/docs/19-views.md +0 -0
- {core_framework-0.12.6 → core_framework-0.12.7}/docs/20-fields.md +0 -0
- {core_framework-0.12.6 → core_framework-0.12.7}/docs/21-tenancy.md +0 -0
- {core_framework-0.12.6 → core_framework-0.12.7}/docs/22-replicas.md +0 -0
- {core_framework-0.12.6 → core_framework-0.12.7}/docs/23-soft-delete.md +0 -0
- {core_framework-0.12.6 → core_framework-0.12.7}/docs/24-relations.md +0 -0
- {core_framework-0.12.6 → core_framework-0.12.7}/docs/25-exceptions.md +0 -0
- {core_framework-0.12.6 → core_framework-0.12.7}/docs/26-choices.md +0 -0
- {core_framework-0.12.6 → core_framework-0.12.7}/docs/27-workers.md +0 -0
- {core_framework-0.12.6 → core_framework-0.12.7}/docs/28-avro.md +0 -0
- {core_framework-0.12.6 → core_framework-0.12.7}/docs/29-topics.md +0 -0
- {core_framework-0.12.6 → core_framework-0.12.7}/docs/30-changelog-0.12.2.md +0 -0
- {core_framework-0.12.6 → core_framework-0.12.7}/docs/31-middleware.md +0 -0
- {core_framework-0.12.6 → core_framework-0.12.7}/docs/32-migration-guide-0.12.2.md +0 -0
- {core_framework-0.12.6 → core_framework-0.12.7}/docs/33-changelog-0.12.3.md +0 -0
- {core_framework-0.12.6 → core_framework-0.12.7}/docs/99-faq-troubleshooting.md +0 -0
- {core_framework-0.12.6 → core_framework-0.12.7}/docs/GUIDE.md +0 -0
- {core_framework-0.12.6 → core_framework-0.12.7}/docs/README.md +0 -0
- {core_framework-0.12.6 → core_framework-0.12.7}/example/__init__.py +0 -0
- {core_framework-0.12.6 → core_framework-0.12.7}/example/app.py +0 -0
- {core_framework-0.12.6 → core_framework-0.12.7}/example/auth.py +0 -0
- {core_framework-0.12.6 → core_framework-0.12.7}/example/models.py +0 -0
- {core_framework-0.12.6 → core_framework-0.12.7}/example/schemas.py +0 -0
- {core_framework-0.12.6 → core_framework-0.12.7}/example/views.py +0 -0
- {core_framework-0.12.6 → core_framework-0.12.7}/libs/__init__.py +0 -0
- {core_framework-0.12.6 → core_framework-0.12.7}/main.py +0 -0
- {core_framework-0.12.6 → core_framework-0.12.7}/tests/__init__.py +0 -0
- {core_framework-0.12.6 → core_framework-0.12.7}/tests/conftest.py +0 -0
- {core_framework-0.12.6 → core_framework-0.12.7}/tests/test_models.py +0 -0
- {core_framework-0.12.6 → core_framework-0.12.7}/tests/test_querysets.py +0 -0
- {core_framework-0.12.6 → core_framework-0.12.7}/tests/test_serializers.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: core-framework
|
|
3
|
-
Version: 0.12.
|
|
3
|
+
Version: 0.12.7
|
|
4
4
|
Summary: Core Framework - Django-inspired, FastAPI-powered. Alta performance, baixo acoplamento, produtividade extrema.
|
|
5
5
|
Project-URL: Homepage, https://github.com/SorPuti/core-framework
|
|
6
6
|
Project-URL: Documentation, https://github.com/SorPuti/core-framework#readme
|
|
@@ -24,12 +24,33 @@ from typing import Any, TYPE_CHECKING
|
|
|
24
24
|
from fastapi import Depends, HTTPException, Request, status
|
|
25
25
|
|
|
26
26
|
from core.permissions import Permission as PermissionBase
|
|
27
|
-
from core.auth.helpers import get_request_user
|
|
28
27
|
|
|
29
28
|
if TYPE_CHECKING:
|
|
30
29
|
pass
|
|
31
30
|
|
|
32
31
|
|
|
32
|
+
def _get_user(request: Request) -> Any | None:
|
|
33
|
+
"""
|
|
34
|
+
Get authenticated user from request.
|
|
35
|
+
|
|
36
|
+
Internal helper to avoid circular imports.
|
|
37
|
+
Checks both Starlette and legacy patterns.
|
|
38
|
+
"""
|
|
39
|
+
# Pattern 1: request.user (Starlette AuthenticationMiddleware)
|
|
40
|
+
user = getattr(request, "user", None)
|
|
41
|
+
if user is not None:
|
|
42
|
+
if getattr(user, "is_authenticated", False):
|
|
43
|
+
if hasattr(user, "_user"):
|
|
44
|
+
return user._user
|
|
45
|
+
return user
|
|
46
|
+
|
|
47
|
+
# Pattern 2: request.state.user (legacy)
|
|
48
|
+
if hasattr(request, "state"):
|
|
49
|
+
return getattr(request.state, "user", None)
|
|
50
|
+
|
|
51
|
+
return None
|
|
52
|
+
|
|
53
|
+
|
|
33
54
|
# =============================================================================
|
|
34
55
|
# Classes de Permissão (para uso em ViewSets)
|
|
35
56
|
# =============================================================================
|
|
@@ -71,7 +92,7 @@ class HasPermission(PermissionBase):
|
|
|
71
92
|
request: Request,
|
|
72
93
|
view: Any = None,
|
|
73
94
|
) -> bool:
|
|
74
|
-
user =
|
|
95
|
+
user = _get_user(request)
|
|
75
96
|
|
|
76
97
|
if user is None:
|
|
77
98
|
return False
|
|
@@ -136,7 +157,7 @@ class IsInGroup(PermissionBase):
|
|
|
136
157
|
request: Request,
|
|
137
158
|
view: Any = None,
|
|
138
159
|
) -> bool:
|
|
139
|
-
user =
|
|
160
|
+
user = _get_user(request)
|
|
140
161
|
|
|
141
162
|
if user is None:
|
|
142
163
|
return False
|
|
@@ -180,7 +201,7 @@ class IsSuperuser(PermissionBase):
|
|
|
180
201
|
request: Request,
|
|
181
202
|
view: Any = None,
|
|
182
203
|
) -> bool:
|
|
183
|
-
user =
|
|
204
|
+
user = _get_user(request)
|
|
184
205
|
|
|
185
206
|
if user is None:
|
|
186
207
|
return False
|
|
@@ -208,7 +229,7 @@ class IsStaff(PermissionBase):
|
|
|
208
229
|
request: Request,
|
|
209
230
|
view: Any = None,
|
|
210
231
|
) -> bool:
|
|
211
|
-
user =
|
|
232
|
+
user = _get_user(request)
|
|
212
233
|
|
|
213
234
|
if user is None:
|
|
214
235
|
return False
|
|
@@ -236,7 +257,7 @@ class IsActive(PermissionBase):
|
|
|
236
257
|
request: Request,
|
|
237
258
|
view: Any = None,
|
|
238
259
|
) -> bool:
|
|
239
|
-
user =
|
|
260
|
+
user = _get_user(request)
|
|
240
261
|
|
|
241
262
|
if user is None:
|
|
242
263
|
return False
|
|
@@ -372,7 +393,7 @@ def login_required():
|
|
|
372
393
|
...
|
|
373
394
|
"""
|
|
374
395
|
async def check(request: Request):
|
|
375
|
-
user =
|
|
396
|
+
user = _get_user(request)
|
|
376
397
|
|
|
377
398
|
if user is None:
|
|
378
399
|
raise HTTPException(
|
|
@@ -15,12 +15,33 @@ from typing import Any, TYPE_CHECKING
|
|
|
15
15
|
|
|
16
16
|
from fastapi import HTTPException, Request, status
|
|
17
17
|
|
|
18
|
-
from core.auth.helpers import get_request_user
|
|
19
|
-
|
|
20
18
|
if TYPE_CHECKING:
|
|
21
19
|
from core.views import APIView
|
|
22
20
|
|
|
23
21
|
|
|
22
|
+
def _get_user(request: Request) -> Any | None:
|
|
23
|
+
"""
|
|
24
|
+
Get authenticated user from request.
|
|
25
|
+
|
|
26
|
+
Internal helper to avoid circular imports.
|
|
27
|
+
Checks both Starlette and legacy patterns.
|
|
28
|
+
"""
|
|
29
|
+
# Pattern 1: request.user (Starlette AuthenticationMiddleware)
|
|
30
|
+
user = getattr(request, "user", None)
|
|
31
|
+
if user is not None:
|
|
32
|
+
if getattr(user, "is_authenticated", False):
|
|
33
|
+
# If it's our AuthenticatedUser wrapper, return the underlying model
|
|
34
|
+
if hasattr(user, "_user"):
|
|
35
|
+
return user._user
|
|
36
|
+
return user
|
|
37
|
+
|
|
38
|
+
# Pattern 2: request.state.user (legacy)
|
|
39
|
+
if hasattr(request, "state"):
|
|
40
|
+
return getattr(request.state, "user", None)
|
|
41
|
+
|
|
42
|
+
return None
|
|
43
|
+
|
|
44
|
+
|
|
24
45
|
class Permission(ABC):
|
|
25
46
|
"""
|
|
26
47
|
Classe base para permissões.
|
|
@@ -34,7 +55,7 @@ class Permission(ABC):
|
|
|
34
55
|
request: Request,
|
|
35
56
|
view: APIView | None = None,
|
|
36
57
|
) -> bool:
|
|
37
|
-
user =
|
|
58
|
+
user = _get_user(request)
|
|
38
59
|
return user is not None and user.is_admin
|
|
39
60
|
"""
|
|
40
61
|
|
|
@@ -216,7 +237,7 @@ class IsAuthenticated(Permission):
|
|
|
216
237
|
request: Request,
|
|
217
238
|
view: "APIView | None" = None,
|
|
218
239
|
) -> bool:
|
|
219
|
-
user =
|
|
240
|
+
user = _get_user(request)
|
|
220
241
|
return user is not None
|
|
221
242
|
|
|
222
243
|
|
|
@@ -238,7 +259,7 @@ class IsAuthenticatedOrReadOnly(Permission):
|
|
|
238
259
|
if request.method in self.SAFE_METHODS:
|
|
239
260
|
return True
|
|
240
261
|
|
|
241
|
-
user =
|
|
262
|
+
user = _get_user(request)
|
|
242
263
|
return user is not None
|
|
243
264
|
|
|
244
265
|
|
|
@@ -252,7 +273,7 @@ class IsAdmin(Permission):
|
|
|
252
273
|
request: Request,
|
|
253
274
|
view: "APIView | None" = None,
|
|
254
275
|
) -> bool:
|
|
255
|
-
user =
|
|
276
|
+
user = _get_user(request)
|
|
256
277
|
if user is None:
|
|
257
278
|
return False
|
|
258
279
|
|
|
@@ -287,7 +308,7 @@ class IsOwner(Permission):
|
|
|
287
308
|
if obj is None:
|
|
288
309
|
return True
|
|
289
310
|
|
|
290
|
-
user =
|
|
311
|
+
user = _get_user(request)
|
|
291
312
|
if user is None:
|
|
292
313
|
return False
|
|
293
314
|
|
|
@@ -320,7 +341,7 @@ class HasRole(Permission):
|
|
|
320
341
|
request: Request,
|
|
321
342
|
view: "APIView | None" = None,
|
|
322
343
|
) -> bool:
|
|
323
|
-
user =
|
|
344
|
+
user = _get_user(request)
|
|
324
345
|
if user is None:
|
|
325
346
|
return False
|
|
326
347
|
|
|
@@ -0,0 +1,206 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Tests for auth helpers.
|
|
3
|
+
|
|
4
|
+
These tests validate the get_request_user helper works correctly
|
|
5
|
+
with both Starlette and legacy patterns.
|
|
6
|
+
"""
|
|
7
|
+
|
|
8
|
+
import pytest
|
|
9
|
+
from unittest.mock import MagicMock, PropertyMock
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
class TestGetRequestUser:
|
|
13
|
+
"""Test get_request_user helper function."""
|
|
14
|
+
|
|
15
|
+
def test_returns_none_when_no_user(self):
|
|
16
|
+
"""Test returns None when no user is set."""
|
|
17
|
+
from core.auth.helpers import get_request_user
|
|
18
|
+
|
|
19
|
+
request = MagicMock()
|
|
20
|
+
request.user = None
|
|
21
|
+
request.state = MagicMock()
|
|
22
|
+
request.state.user = None
|
|
23
|
+
|
|
24
|
+
result = get_request_user(request)
|
|
25
|
+
assert result is None
|
|
26
|
+
|
|
27
|
+
def test_returns_user_from_starlette_pattern(self):
|
|
28
|
+
"""Test returns user from request.user (Starlette pattern)."""
|
|
29
|
+
from core.auth.helpers import get_request_user
|
|
30
|
+
|
|
31
|
+
# Create a mock user that looks like AuthenticatedUser
|
|
32
|
+
mock_user = MagicMock()
|
|
33
|
+
mock_user.is_authenticated = True
|
|
34
|
+
mock_user._user = MagicMock() # The underlying model
|
|
35
|
+
mock_user._user.email = "test@example.com"
|
|
36
|
+
|
|
37
|
+
request = MagicMock()
|
|
38
|
+
request.user = mock_user
|
|
39
|
+
|
|
40
|
+
result = get_request_user(request)
|
|
41
|
+
assert result == mock_user._user
|
|
42
|
+
|
|
43
|
+
def test_returns_user_from_legacy_pattern(self):
|
|
44
|
+
"""Test returns user from request.state.user (legacy pattern)."""
|
|
45
|
+
from core.auth.helpers import get_request_user
|
|
46
|
+
|
|
47
|
+
mock_user = MagicMock()
|
|
48
|
+
mock_user.email = "test@example.com"
|
|
49
|
+
|
|
50
|
+
request = MagicMock()
|
|
51
|
+
request.user = MagicMock()
|
|
52
|
+
request.user.is_authenticated = False # Not authenticated via Starlette
|
|
53
|
+
request.state = MagicMock()
|
|
54
|
+
request.state.user = mock_user
|
|
55
|
+
|
|
56
|
+
result = get_request_user(request)
|
|
57
|
+
assert result == mock_user
|
|
58
|
+
|
|
59
|
+
def test_prefers_starlette_over_legacy(self):
|
|
60
|
+
"""Test prefers request.user over request.state.user."""
|
|
61
|
+
from core.auth.helpers import get_request_user
|
|
62
|
+
|
|
63
|
+
starlette_user = MagicMock()
|
|
64
|
+
starlette_user.is_authenticated = True
|
|
65
|
+
starlette_user._user = MagicMock()
|
|
66
|
+
starlette_user._user.email = "starlette@example.com"
|
|
67
|
+
|
|
68
|
+
legacy_user = MagicMock()
|
|
69
|
+
legacy_user.email = "legacy@example.com"
|
|
70
|
+
|
|
71
|
+
request = MagicMock()
|
|
72
|
+
request.user = starlette_user
|
|
73
|
+
request.state = MagicMock()
|
|
74
|
+
request.state.user = legacy_user
|
|
75
|
+
|
|
76
|
+
result = get_request_user(request)
|
|
77
|
+
# Should return the Starlette user
|
|
78
|
+
assert result.email == "starlette@example.com"
|
|
79
|
+
|
|
80
|
+
def test_handles_missing_state(self):
|
|
81
|
+
"""Test handles request without state attribute."""
|
|
82
|
+
from core.auth.helpers import get_request_user
|
|
83
|
+
|
|
84
|
+
request = MagicMock(spec=[]) # No attributes by default
|
|
85
|
+
request.user = None
|
|
86
|
+
|
|
87
|
+
# This should not raise an error
|
|
88
|
+
result = get_request_user(request)
|
|
89
|
+
assert result is None
|
|
90
|
+
|
|
91
|
+
|
|
92
|
+
class TestIsAuthenticated:
|
|
93
|
+
"""Test is_authenticated helper function."""
|
|
94
|
+
|
|
95
|
+
def test_returns_false_when_no_user(self):
|
|
96
|
+
"""Test returns False when no user."""
|
|
97
|
+
from core.auth.helpers import is_authenticated
|
|
98
|
+
|
|
99
|
+
request = MagicMock()
|
|
100
|
+
request.user = None
|
|
101
|
+
request.state = MagicMock()
|
|
102
|
+
request.state.user = None
|
|
103
|
+
|
|
104
|
+
assert is_authenticated(request) is False
|
|
105
|
+
|
|
106
|
+
def test_returns_true_for_starlette_user(self):
|
|
107
|
+
"""Test returns True for Starlette authenticated user."""
|
|
108
|
+
from core.auth.helpers import is_authenticated
|
|
109
|
+
|
|
110
|
+
mock_user = MagicMock()
|
|
111
|
+
mock_user.is_authenticated = True
|
|
112
|
+
|
|
113
|
+
request = MagicMock()
|
|
114
|
+
request.user = mock_user
|
|
115
|
+
|
|
116
|
+
assert is_authenticated(request) is True
|
|
117
|
+
|
|
118
|
+
def test_returns_true_for_legacy_user(self):
|
|
119
|
+
"""Test returns True for legacy user."""
|
|
120
|
+
from core.auth.helpers import is_authenticated
|
|
121
|
+
|
|
122
|
+
mock_user = MagicMock()
|
|
123
|
+
|
|
124
|
+
request = MagicMock()
|
|
125
|
+
request.user = MagicMock()
|
|
126
|
+
request.user.is_authenticated = False
|
|
127
|
+
request.state = MagicMock()
|
|
128
|
+
request.state.user = mock_user
|
|
129
|
+
|
|
130
|
+
assert is_authenticated(request) is True
|
|
131
|
+
|
|
132
|
+
|
|
133
|
+
class TestSetRequestUser:
|
|
134
|
+
"""Test set_request_user helper function."""
|
|
135
|
+
|
|
136
|
+
def test_sets_user_on_state(self):
|
|
137
|
+
"""Test sets user on request.state."""
|
|
138
|
+
from core.auth.helpers import set_request_user
|
|
139
|
+
|
|
140
|
+
mock_user = MagicMock()
|
|
141
|
+
request = MagicMock()
|
|
142
|
+
request.state = MagicMock()
|
|
143
|
+
|
|
144
|
+
set_request_user(request, mock_user)
|
|
145
|
+
|
|
146
|
+
assert request.state.user == mock_user
|
|
147
|
+
|
|
148
|
+
def test_clears_user_when_none(self):
|
|
149
|
+
"""Test clears user when None is passed."""
|
|
150
|
+
from core.auth.helpers import set_request_user
|
|
151
|
+
|
|
152
|
+
request = MagicMock()
|
|
153
|
+
request.state = MagicMock()
|
|
154
|
+
request.state.user = MagicMock() # Previous user
|
|
155
|
+
|
|
156
|
+
set_request_user(request, None)
|
|
157
|
+
|
|
158
|
+
assert request.state.user is None
|
|
159
|
+
|
|
160
|
+
|
|
161
|
+
class TestAuthenticatedUserWrapper:
|
|
162
|
+
"""Test AuthenticatedUser wrapper class."""
|
|
163
|
+
|
|
164
|
+
def test_is_authenticated_property(self):
|
|
165
|
+
"""Test is_authenticated returns True."""
|
|
166
|
+
from core.auth.middleware import AuthenticatedUser
|
|
167
|
+
|
|
168
|
+
mock_model = MagicMock()
|
|
169
|
+
wrapper = AuthenticatedUser(mock_model)
|
|
170
|
+
|
|
171
|
+
assert wrapper.is_authenticated is True
|
|
172
|
+
|
|
173
|
+
def test_proxies_attributes(self):
|
|
174
|
+
"""Test proxies attribute access to underlying model."""
|
|
175
|
+
from core.auth.middleware import AuthenticatedUser
|
|
176
|
+
|
|
177
|
+
mock_model = MagicMock()
|
|
178
|
+
mock_model.email = "test@example.com"
|
|
179
|
+
mock_model.id = 123
|
|
180
|
+
|
|
181
|
+
wrapper = AuthenticatedUser(mock_model)
|
|
182
|
+
|
|
183
|
+
assert wrapper.email == "test@example.com"
|
|
184
|
+
assert wrapper.id == 123
|
|
185
|
+
|
|
186
|
+
def test_display_name(self):
|
|
187
|
+
"""Test display_name returns email."""
|
|
188
|
+
from core.auth.middleware import AuthenticatedUser
|
|
189
|
+
|
|
190
|
+
mock_model = MagicMock()
|
|
191
|
+
mock_model.email = "test@example.com"
|
|
192
|
+
|
|
193
|
+
wrapper = AuthenticatedUser(mock_model)
|
|
194
|
+
|
|
195
|
+
assert wrapper.display_name == "test@example.com"
|
|
196
|
+
|
|
197
|
+
def test_identity(self):
|
|
198
|
+
"""Test identity returns string id."""
|
|
199
|
+
from core.auth.middleware import AuthenticatedUser
|
|
200
|
+
|
|
201
|
+
mock_model = MagicMock()
|
|
202
|
+
mock_model.id = 123
|
|
203
|
+
|
|
204
|
+
wrapper = AuthenticatedUser(mock_model)
|
|
205
|
+
|
|
206
|
+
assert wrapper.identity == "123"
|
|
@@ -0,0 +1,263 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Tests for module imports.
|
|
3
|
+
|
|
4
|
+
These tests ensure there are no circular imports or missing dependencies.
|
|
5
|
+
Run these BEFORE committing any changes!
|
|
6
|
+
"""
|
|
7
|
+
|
|
8
|
+
import pytest
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
class TestCoreImports:
|
|
12
|
+
"""Test that core modules can be imported without errors."""
|
|
13
|
+
|
|
14
|
+
def test_import_core(self):
|
|
15
|
+
"""Test importing the main core module."""
|
|
16
|
+
import core
|
|
17
|
+
assert hasattr(core, "__version__")
|
|
18
|
+
|
|
19
|
+
def test_import_core_models(self):
|
|
20
|
+
"""Test importing core.models."""
|
|
21
|
+
from core.models import Model, Field
|
|
22
|
+
assert Model is not None
|
|
23
|
+
assert Field is not None
|
|
24
|
+
|
|
25
|
+
def test_import_core_views(self):
|
|
26
|
+
"""Test importing core.views."""
|
|
27
|
+
from core.views import ViewSet, ModelViewSet, action
|
|
28
|
+
assert ViewSet is not None
|
|
29
|
+
assert ModelViewSet is not None
|
|
30
|
+
assert action is not None
|
|
31
|
+
|
|
32
|
+
def test_import_core_permissions(self):
|
|
33
|
+
"""Test importing core.permissions."""
|
|
34
|
+
from core.permissions import (
|
|
35
|
+
Permission,
|
|
36
|
+
AllowAny,
|
|
37
|
+
DenyAll,
|
|
38
|
+
IsAuthenticated,
|
|
39
|
+
IsAuthenticatedOrReadOnly,
|
|
40
|
+
IsAdmin,
|
|
41
|
+
IsOwner,
|
|
42
|
+
HasRole,
|
|
43
|
+
check_permissions,
|
|
44
|
+
)
|
|
45
|
+
assert Permission is not None
|
|
46
|
+
assert IsAuthenticated is not None
|
|
47
|
+
|
|
48
|
+
def test_import_core_exceptions(self):
|
|
49
|
+
"""Test importing core.exceptions."""
|
|
50
|
+
from core.exceptions import (
|
|
51
|
+
CoreException,
|
|
52
|
+
ValidationException,
|
|
53
|
+
DatabaseException,
|
|
54
|
+
AuthException,
|
|
55
|
+
InvalidToken,
|
|
56
|
+
TokenExpired,
|
|
57
|
+
PermissionDenied,
|
|
58
|
+
NotFound,
|
|
59
|
+
Unauthorized,
|
|
60
|
+
)
|
|
61
|
+
assert CoreException is not None
|
|
62
|
+
assert InvalidToken is not None
|
|
63
|
+
|
|
64
|
+
|
|
65
|
+
class TestAuthImports:
|
|
66
|
+
"""Test that auth modules can be imported without circular import errors."""
|
|
67
|
+
|
|
68
|
+
def test_import_auth_module(self):
|
|
69
|
+
"""Test importing the main auth module."""
|
|
70
|
+
from core.auth import (
|
|
71
|
+
configure_auth,
|
|
72
|
+
get_auth_config,
|
|
73
|
+
AuthBackend,
|
|
74
|
+
TokenBackend,
|
|
75
|
+
)
|
|
76
|
+
assert configure_auth is not None
|
|
77
|
+
|
|
78
|
+
def test_import_auth_models(self):
|
|
79
|
+
"""Test importing auth models."""
|
|
80
|
+
from core.auth.models import (
|
|
81
|
+
AbstractUser,
|
|
82
|
+
AbstractUUIDUser,
|
|
83
|
+
PermissionsMixin,
|
|
84
|
+
Group,
|
|
85
|
+
Permission,
|
|
86
|
+
)
|
|
87
|
+
assert AbstractUser is not None
|
|
88
|
+
assert AbstractUUIDUser is not None
|
|
89
|
+
|
|
90
|
+
def test_import_auth_tokens(self):
|
|
91
|
+
"""Test importing auth tokens."""
|
|
92
|
+
from core.auth.tokens import (
|
|
93
|
+
create_access_token,
|
|
94
|
+
create_refresh_token,
|
|
95
|
+
decode_token,
|
|
96
|
+
verify_token,
|
|
97
|
+
JWTBackend,
|
|
98
|
+
)
|
|
99
|
+
assert create_access_token is not None
|
|
100
|
+
assert JWTBackend is not None
|
|
101
|
+
|
|
102
|
+
def test_import_auth_middleware(self):
|
|
103
|
+
"""Test importing auth middleware."""
|
|
104
|
+
from core.auth.middleware import (
|
|
105
|
+
AuthenticationMiddleware,
|
|
106
|
+
OptionalAuthenticationMiddleware,
|
|
107
|
+
JWTAuthBackend,
|
|
108
|
+
AuthenticatedUser,
|
|
109
|
+
)
|
|
110
|
+
assert AuthenticationMiddleware is not None
|
|
111
|
+
assert JWTAuthBackend is not None
|
|
112
|
+
|
|
113
|
+
def test_import_auth_decorators(self):
|
|
114
|
+
"""Test importing auth decorators."""
|
|
115
|
+
from core.auth.decorators import (
|
|
116
|
+
HasPermission,
|
|
117
|
+
IsInGroup,
|
|
118
|
+
IsSuperuser,
|
|
119
|
+
IsStaff,
|
|
120
|
+
IsActive,
|
|
121
|
+
require_permission,
|
|
122
|
+
require_group,
|
|
123
|
+
login_required,
|
|
124
|
+
)
|
|
125
|
+
assert HasPermission is not None
|
|
126
|
+
assert login_required is not None
|
|
127
|
+
|
|
128
|
+
def test_import_auth_helpers(self):
|
|
129
|
+
"""Test importing auth helpers."""
|
|
130
|
+
from core.auth.helpers import (
|
|
131
|
+
get_request_user,
|
|
132
|
+
is_authenticated,
|
|
133
|
+
set_request_user,
|
|
134
|
+
)
|
|
135
|
+
assert get_request_user is not None
|
|
136
|
+
assert is_authenticated is not None
|
|
137
|
+
|
|
138
|
+
def test_import_auth_schemas(self):
|
|
139
|
+
"""Test importing auth schemas."""
|
|
140
|
+
from core.auth.schemas import (
|
|
141
|
+
BaseRegisterInput,
|
|
142
|
+
BaseLoginInput,
|
|
143
|
+
TokenResponse,
|
|
144
|
+
BaseUserOutput,
|
|
145
|
+
)
|
|
146
|
+
assert BaseRegisterInput is not None
|
|
147
|
+
assert TokenResponse is not None
|
|
148
|
+
|
|
149
|
+
def test_import_auth_views(self):
|
|
150
|
+
"""Test importing auth views."""
|
|
151
|
+
from core.auth.views import AuthViewSet
|
|
152
|
+
assert AuthViewSet is not None
|
|
153
|
+
|
|
154
|
+
|
|
155
|
+
class TestMiddlewareImports:
|
|
156
|
+
"""Test that middleware modules can be imported."""
|
|
157
|
+
|
|
158
|
+
def test_import_middleware(self):
|
|
159
|
+
"""Test importing core.middleware."""
|
|
160
|
+
from core.middleware import (
|
|
161
|
+
BaseMiddleware,
|
|
162
|
+
configure_middleware,
|
|
163
|
+
apply_middlewares,
|
|
164
|
+
TimingMiddleware,
|
|
165
|
+
RequestIDMiddleware,
|
|
166
|
+
LoggingMiddleware,
|
|
167
|
+
)
|
|
168
|
+
assert BaseMiddleware is not None
|
|
169
|
+
assert configure_middleware is not None
|
|
170
|
+
|
|
171
|
+
|
|
172
|
+
class TestDependenciesImports:
|
|
173
|
+
"""Test that dependencies can be imported."""
|
|
174
|
+
|
|
175
|
+
def test_import_dependencies(self):
|
|
176
|
+
"""Test importing core.dependencies."""
|
|
177
|
+
from core.dependencies import (
|
|
178
|
+
get_db,
|
|
179
|
+
get_current_user,
|
|
180
|
+
get_optional_user,
|
|
181
|
+
DatabaseSession,
|
|
182
|
+
CurrentUser,
|
|
183
|
+
)
|
|
184
|
+
assert get_db is not None
|
|
185
|
+
assert get_current_user is not None
|
|
186
|
+
|
|
187
|
+
|
|
188
|
+
class TestTenancyImports:
|
|
189
|
+
"""Test that tenancy modules can be imported."""
|
|
190
|
+
|
|
191
|
+
def test_import_tenancy(self):
|
|
192
|
+
"""Test importing core.tenancy."""
|
|
193
|
+
from core.tenancy import (
|
|
194
|
+
TenantMixin,
|
|
195
|
+
set_tenant,
|
|
196
|
+
get_tenant,
|
|
197
|
+
clear_tenant,
|
|
198
|
+
)
|
|
199
|
+
assert TenantMixin is not None
|
|
200
|
+
assert set_tenant is not None
|
|
201
|
+
|
|
202
|
+
|
|
203
|
+
class TestMigrationsImports:
|
|
204
|
+
"""Test that migrations modules can be imported."""
|
|
205
|
+
|
|
206
|
+
def test_import_migrations(self):
|
|
207
|
+
"""Test importing core.migrations."""
|
|
208
|
+
from core.migrations import (
|
|
209
|
+
MigrationEngine,
|
|
210
|
+
Operation,
|
|
211
|
+
CreateTable,
|
|
212
|
+
DropTable,
|
|
213
|
+
)
|
|
214
|
+
assert MigrationEngine is not None
|
|
215
|
+
assert CreateTable is not None
|
|
216
|
+
|
|
217
|
+
|
|
218
|
+
class TestImportOrder:
|
|
219
|
+
"""Test that imports work in various orders (to catch hidden circular deps)."""
|
|
220
|
+
|
|
221
|
+
def test_permissions_before_auth(self):
|
|
222
|
+
"""Test importing permissions before auth."""
|
|
223
|
+
from core.permissions import IsAuthenticated
|
|
224
|
+
from core.auth import AuthViewSet
|
|
225
|
+
assert IsAuthenticated is not None
|
|
226
|
+
assert AuthViewSet is not None
|
|
227
|
+
|
|
228
|
+
def test_auth_before_permissions(self):
|
|
229
|
+
"""Test importing auth before permissions."""
|
|
230
|
+
from core.auth import AuthViewSet
|
|
231
|
+
from core.permissions import IsAuthenticated
|
|
232
|
+
assert AuthViewSet is not None
|
|
233
|
+
assert IsAuthenticated is not None
|
|
234
|
+
|
|
235
|
+
def test_views_before_auth(self):
|
|
236
|
+
"""Test importing views before auth."""
|
|
237
|
+
from core.views import ViewSet
|
|
238
|
+
from core.auth import AuthViewSet
|
|
239
|
+
assert ViewSet is not None
|
|
240
|
+
assert AuthViewSet is not None
|
|
241
|
+
|
|
242
|
+
def test_all_together(self):
|
|
243
|
+
"""Test importing everything at once."""
|
|
244
|
+
from core import (
|
|
245
|
+
Model,
|
|
246
|
+
ViewSet,
|
|
247
|
+
ModelViewSet,
|
|
248
|
+
)
|
|
249
|
+
from core.auth import (
|
|
250
|
+
AbstractUser,
|
|
251
|
+
AuthViewSet,
|
|
252
|
+
configure_auth,
|
|
253
|
+
)
|
|
254
|
+
from core.permissions import (
|
|
255
|
+
IsAuthenticated,
|
|
256
|
+
AllowAny,
|
|
257
|
+
)
|
|
258
|
+
from core.middleware import (
|
|
259
|
+
BaseMiddleware,
|
|
260
|
+
)
|
|
261
|
+
assert Model is not None
|
|
262
|
+
assert AuthViewSet is not None
|
|
263
|
+
assert IsAuthenticated is not None
|