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.
Files changed (131) hide show
  1. {core_framework-0.12.6 → core_framework-0.12.7}/PKG-INFO +1 -1
  2. {core_framework-0.12.6 → core_framework-0.12.7}/core/__init__.py +1 -1
  3. {core_framework-0.12.6 → core_framework-0.12.7}/core/auth/decorators.py +28 -7
  4. {core_framework-0.12.6 → core_framework-0.12.7}/core/permissions.py +29 -8
  5. {core_framework-0.12.6 → core_framework-0.12.7}/pyproject.toml +1 -1
  6. core_framework-0.12.7/tests/test_auth_helpers.py +206 -0
  7. core_framework-0.12.7/tests/test_imports.py +263 -0
  8. core_framework-0.12.7/tests/test_permissions.py +373 -0
  9. {core_framework-0.12.6 → core_framework-0.12.7}/.gitignore +0 -0
  10. {core_framework-0.12.6 → core_framework-0.12.7}/README.md +0 -0
  11. {core_framework-0.12.6 → core_framework-0.12.7}/core/app.py +0 -0
  12. {core_framework-0.12.6 → core_framework-0.12.7}/core/auth/__init__.py +0 -0
  13. {core_framework-0.12.6 → core_framework-0.12.7}/core/auth/backends.py +0 -0
  14. {core_framework-0.12.6 → core_framework-0.12.7}/core/auth/base.py +0 -0
  15. {core_framework-0.12.6 → core_framework-0.12.7}/core/auth/hashers.py +0 -0
  16. {core_framework-0.12.6 → core_framework-0.12.7}/core/auth/helpers.py +0 -0
  17. {core_framework-0.12.6 → core_framework-0.12.7}/core/auth/middleware.py +0 -0
  18. {core_framework-0.12.6 → core_framework-0.12.7}/core/auth/models.py +0 -0
  19. {core_framework-0.12.6 → core_framework-0.12.7}/core/auth/permissions.py +0 -0
  20. {core_framework-0.12.6 → core_framework-0.12.7}/core/auth/schemas.py +0 -0
  21. {core_framework-0.12.6 → core_framework-0.12.7}/core/auth/tokens.py +0 -0
  22. {core_framework-0.12.6 → core_framework-0.12.7}/core/auth/views.py +0 -0
  23. {core_framework-0.12.6 → core_framework-0.12.7}/core/choices.py +0 -0
  24. {core_framework-0.12.6 → core_framework-0.12.7}/core/cli/__init__.py +0 -0
  25. {core_framework-0.12.6 → core_framework-0.12.7}/core/cli/main.py +0 -0
  26. {core_framework-0.12.6 → core_framework-0.12.7}/core/config.py +0 -0
  27. {core_framework-0.12.6 → core_framework-0.12.7}/core/database.py +0 -0
  28. {core_framework-0.12.6 → core_framework-0.12.7}/core/datetime.py +0 -0
  29. {core_framework-0.12.6 → core_framework-0.12.7}/core/dependencies.py +0 -0
  30. {core_framework-0.12.6 → core_framework-0.12.7}/core/deployment/__init__.py +0 -0
  31. {core_framework-0.12.6 → core_framework-0.12.7}/core/deployment/docker.py +0 -0
  32. {core_framework-0.12.6 → core_framework-0.12.7}/core/deployment/kubernetes.py +0 -0
  33. {core_framework-0.12.6 → core_framework-0.12.7}/core/deployment/pm2.py +0 -0
  34. {core_framework-0.12.6 → core_framework-0.12.7}/core/exceptions.py +0 -0
  35. {core_framework-0.12.6 → core_framework-0.12.7}/core/fields.py +0 -0
  36. {core_framework-0.12.6 → core_framework-0.12.7}/core/messaging/__init__.py +0 -0
  37. {core_framework-0.12.6 → core_framework-0.12.7}/core/messaging/avro.py +0 -0
  38. {core_framework-0.12.6 → core_framework-0.12.7}/core/messaging/base.py +0 -0
  39. {core_framework-0.12.6 → core_framework-0.12.7}/core/messaging/config.py +0 -0
  40. {core_framework-0.12.6 → core_framework-0.12.7}/core/messaging/confluent/__init__.py +0 -0
  41. {core_framework-0.12.6 → core_framework-0.12.7}/core/messaging/confluent/consumer.py +0 -0
  42. {core_framework-0.12.6 → core_framework-0.12.7}/core/messaging/confluent/producer.py +0 -0
  43. {core_framework-0.12.6 → core_framework-0.12.7}/core/messaging/decorators.py +0 -0
  44. {core_framework-0.12.6 → core_framework-0.12.7}/core/messaging/kafka/__init__.py +0 -0
  45. {core_framework-0.12.6 → core_framework-0.12.7}/core/messaging/kafka/admin.py +0 -0
  46. {core_framework-0.12.6 → core_framework-0.12.7}/core/messaging/kafka/broker.py +0 -0
  47. {core_framework-0.12.6 → core_framework-0.12.7}/core/messaging/kafka/consumer.py +0 -0
  48. {core_framework-0.12.6 → core_framework-0.12.7}/core/messaging/kafka/producer.py +0 -0
  49. {core_framework-0.12.6 → core_framework-0.12.7}/core/messaging/rabbitmq/__init__.py +0 -0
  50. {core_framework-0.12.6 → core_framework-0.12.7}/core/messaging/rabbitmq/broker.py +0 -0
  51. {core_framework-0.12.6 → core_framework-0.12.7}/core/messaging/rabbitmq/consumer.py +0 -0
  52. {core_framework-0.12.6 → core_framework-0.12.7}/core/messaging/rabbitmq/producer.py +0 -0
  53. {core_framework-0.12.6 → core_framework-0.12.7}/core/messaging/redis/__init__.py +0 -0
  54. {core_framework-0.12.6 → core_framework-0.12.7}/core/messaging/redis/broker.py +0 -0
  55. {core_framework-0.12.6 → core_framework-0.12.7}/core/messaging/redis/consumer.py +0 -0
  56. {core_framework-0.12.6 → core_framework-0.12.7}/core/messaging/redis/producer.py +0 -0
  57. {core_framework-0.12.6 → core_framework-0.12.7}/core/messaging/registry.py +0 -0
  58. {core_framework-0.12.6 → core_framework-0.12.7}/core/messaging/topics.py +0 -0
  59. {core_framework-0.12.6 → core_framework-0.12.7}/core/messaging/workers.py +0 -0
  60. {core_framework-0.12.6 → core_framework-0.12.7}/core/middleware.py +0 -0
  61. {core_framework-0.12.6 → core_framework-0.12.7}/core/migrations/__init__.py +0 -0
  62. {core_framework-0.12.6 → core_framework-0.12.7}/core/migrations/analyzer.py +0 -0
  63. {core_framework-0.12.6 → core_framework-0.12.7}/core/migrations/cli.py +0 -0
  64. {core_framework-0.12.6 → core_framework-0.12.7}/core/migrations/engine.py +0 -0
  65. {core_framework-0.12.6 → core_framework-0.12.7}/core/migrations/migration.py +0 -0
  66. {core_framework-0.12.6 → core_framework-0.12.7}/core/migrations/operations.py +0 -0
  67. {core_framework-0.12.6 → core_framework-0.12.7}/core/migrations/state.py +0 -0
  68. {core_framework-0.12.6 → core_framework-0.12.7}/core/models.py +0 -0
  69. {core_framework-0.12.6 → core_framework-0.12.7}/core/querysets.py +0 -0
  70. {core_framework-0.12.6 → core_framework-0.12.7}/core/relations.py +0 -0
  71. {core_framework-0.12.6 → core_framework-0.12.7}/core/routing.py +0 -0
  72. {core_framework-0.12.6 → core_framework-0.12.7}/core/serializers.py +0 -0
  73. {core_framework-0.12.6 → core_framework-0.12.7}/core/tasks/__init__.py +0 -0
  74. {core_framework-0.12.6 → core_framework-0.12.7}/core/tasks/base.py +0 -0
  75. {core_framework-0.12.6 → core_framework-0.12.7}/core/tasks/config.py +0 -0
  76. {core_framework-0.12.6 → core_framework-0.12.7}/core/tasks/decorators.py +0 -0
  77. {core_framework-0.12.6 → core_framework-0.12.7}/core/tasks/registry.py +0 -0
  78. {core_framework-0.12.6 → core_framework-0.12.7}/core/tasks/scheduler.py +0 -0
  79. {core_framework-0.12.6 → core_framework-0.12.7}/core/tasks/worker.py +0 -0
  80. {core_framework-0.12.6 → core_framework-0.12.7}/core/tenancy.py +0 -0
  81. {core_framework-0.12.6 → core_framework-0.12.7}/core/validators.py +0 -0
  82. {core_framework-0.12.6 → core_framework-0.12.7}/core/views.py +0 -0
  83. {core_framework-0.12.6 → core_framework-0.12.7}/docs/01-quickstart.md +0 -0
  84. {core_framework-0.12.6 → core_framework-0.12.7}/docs/02-viewsets.md +0 -0
  85. {core_framework-0.12.6 → core_framework-0.12.7}/docs/03-authentication.md +0 -0
  86. {core_framework-0.12.6 → core_framework-0.12.7}/docs/04-messaging.md +0 -0
  87. {core_framework-0.12.6 → core_framework-0.12.7}/docs/05-multi-service.md +0 -0
  88. {core_framework-0.12.6 → core_framework-0.12.7}/docs/06-tasks.md +0 -0
  89. {core_framework-0.12.6 → core_framework-0.12.7}/docs/07-deployment.md +0 -0
  90. {core_framework-0.12.6 → core_framework-0.12.7}/docs/08-complete-example.md +0 -0
  91. {core_framework-0.12.6 → core_framework-0.12.7}/docs/09-settings.md +0 -0
  92. {core_framework-0.12.6 → core_framework-0.12.7}/docs/10-migrations.md +0 -0
  93. {core_framework-0.12.6 → core_framework-0.12.7}/docs/11-permissions.md +0 -0
  94. {core_framework-0.12.6 → core_framework-0.12.7}/docs/12-auth-backends.md +0 -0
  95. {core_framework-0.12.6 → core_framework-0.12.7}/docs/13-validators.md +0 -0
  96. {core_framework-0.12.6 → core_framework-0.12.7}/docs/14-querysets.md +0 -0
  97. {core_framework-0.12.6 → core_framework-0.12.7}/docs/15-routing.md +0 -0
  98. {core_framework-0.12.6 → core_framework-0.12.7}/docs/16-serializers.md +0 -0
  99. {core_framework-0.12.6 → core_framework-0.12.7}/docs/17-datetime.md +0 -0
  100. {core_framework-0.12.6 → core_framework-0.12.7}/docs/18-dependencies.md +0 -0
  101. {core_framework-0.12.6 → core_framework-0.12.7}/docs/19-views.md +0 -0
  102. {core_framework-0.12.6 → core_framework-0.12.7}/docs/20-fields.md +0 -0
  103. {core_framework-0.12.6 → core_framework-0.12.7}/docs/21-tenancy.md +0 -0
  104. {core_framework-0.12.6 → core_framework-0.12.7}/docs/22-replicas.md +0 -0
  105. {core_framework-0.12.6 → core_framework-0.12.7}/docs/23-soft-delete.md +0 -0
  106. {core_framework-0.12.6 → core_framework-0.12.7}/docs/24-relations.md +0 -0
  107. {core_framework-0.12.6 → core_framework-0.12.7}/docs/25-exceptions.md +0 -0
  108. {core_framework-0.12.6 → core_framework-0.12.7}/docs/26-choices.md +0 -0
  109. {core_framework-0.12.6 → core_framework-0.12.7}/docs/27-workers.md +0 -0
  110. {core_framework-0.12.6 → core_framework-0.12.7}/docs/28-avro.md +0 -0
  111. {core_framework-0.12.6 → core_framework-0.12.7}/docs/29-topics.md +0 -0
  112. {core_framework-0.12.6 → core_framework-0.12.7}/docs/30-changelog-0.12.2.md +0 -0
  113. {core_framework-0.12.6 → core_framework-0.12.7}/docs/31-middleware.md +0 -0
  114. {core_framework-0.12.6 → core_framework-0.12.7}/docs/32-migration-guide-0.12.2.md +0 -0
  115. {core_framework-0.12.6 → core_framework-0.12.7}/docs/33-changelog-0.12.3.md +0 -0
  116. {core_framework-0.12.6 → core_framework-0.12.7}/docs/99-faq-troubleshooting.md +0 -0
  117. {core_framework-0.12.6 → core_framework-0.12.7}/docs/GUIDE.md +0 -0
  118. {core_framework-0.12.6 → core_framework-0.12.7}/docs/README.md +0 -0
  119. {core_framework-0.12.6 → core_framework-0.12.7}/example/__init__.py +0 -0
  120. {core_framework-0.12.6 → core_framework-0.12.7}/example/app.py +0 -0
  121. {core_framework-0.12.6 → core_framework-0.12.7}/example/auth.py +0 -0
  122. {core_framework-0.12.6 → core_framework-0.12.7}/example/models.py +0 -0
  123. {core_framework-0.12.6 → core_framework-0.12.7}/example/schemas.py +0 -0
  124. {core_framework-0.12.6 → core_framework-0.12.7}/example/views.py +0 -0
  125. {core_framework-0.12.6 → core_framework-0.12.7}/libs/__init__.py +0 -0
  126. {core_framework-0.12.6 → core_framework-0.12.7}/main.py +0 -0
  127. {core_framework-0.12.6 → core_framework-0.12.7}/tests/__init__.py +0 -0
  128. {core_framework-0.12.6 → core_framework-0.12.7}/tests/conftest.py +0 -0
  129. {core_framework-0.12.6 → core_framework-0.12.7}/tests/test_models.py +0 -0
  130. {core_framework-0.12.6 → core_framework-0.12.7}/tests/test_querysets.py +0 -0
  131. {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.6
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
@@ -278,7 +278,7 @@ from core.exceptions import (
278
278
  MissingDependency,
279
279
  )
280
280
 
281
- __version__ = "0.12.6"
281
+ __version__ = "0.12.7"
282
282
  __all__ = [
283
283
  # Models
284
284
  "Model",
@@ -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 = get_request_user(request)
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 = get_request_user(request)
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 = get_request_user(request)
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 = get_request_user(request)
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 = get_request_user(request)
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 = get_request_user(request)
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 = get_request_user(request)
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 = get_request_user(request)
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 = get_request_user(request)
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 = get_request_user(request)
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 = get_request_user(request)
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 = get_request_user(request)
344
+ user = _get_user(request)
324
345
  if user is None:
325
346
  return False
326
347
 
@@ -1,6 +1,6 @@
1
1
  [project]
2
2
  name = "core-framework"
3
- version = "0.12.6"
3
+ version = "0.12.7"
4
4
  description = "Core Framework - Django-inspired, FastAPI-powered. Alta performance, baixo acoplamento, produtividade extrema."
5
5
  requires-python = ">=3.10,<4.0"
6
6
  readme = "README.md"
@@ -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