core-framework 0.12.6__tar.gz → 0.12.8__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.8}/PKG-INFO +6 -1
- {core_framework-0.12.6 → core_framework-0.12.8}/core/__init__.py +1 -1
- {core_framework-0.12.6 → core_framework-0.12.8}/core/auth/decorators.py +28 -7
- {core_framework-0.12.6 → core_framework-0.12.8}/core/cli/__init__.py +2 -0
- {core_framework-0.12.6 → core_framework-0.12.8}/core/cli/main.py +163 -0
- {core_framework-0.12.6 → core_framework-0.12.8}/core/permissions.py +29 -8
- core_framework-0.12.8/core/testing/__init__.py +99 -0
- core_framework-0.12.8/core/testing/assertions.py +347 -0
- core_framework-0.12.8/core/testing/client.py +247 -0
- core_framework-0.12.8/core/testing/database.py +307 -0
- core_framework-0.12.8/core/testing/factories.py +393 -0
- core_framework-0.12.8/core/testing/mocks.py +658 -0
- core_framework-0.12.8/core/testing/plugin.py +635 -0
- {core_framework-0.12.6 → core_framework-0.12.8}/pyproject.toml +10 -1
- core_framework-0.12.8/tests/test_auth_helpers.py +206 -0
- core_framework-0.12.8/tests/test_imports.py +263 -0
- core_framework-0.12.8/tests/test_permissions.py +373 -0
- {core_framework-0.12.6 → core_framework-0.12.8}/.gitignore +0 -0
- {core_framework-0.12.6 → core_framework-0.12.8}/README.md +0 -0
- {core_framework-0.12.6 → core_framework-0.12.8}/core/app.py +0 -0
- {core_framework-0.12.6 → core_framework-0.12.8}/core/auth/__init__.py +0 -0
- {core_framework-0.12.6 → core_framework-0.12.8}/core/auth/backends.py +0 -0
- {core_framework-0.12.6 → core_framework-0.12.8}/core/auth/base.py +0 -0
- {core_framework-0.12.6 → core_framework-0.12.8}/core/auth/hashers.py +0 -0
- {core_framework-0.12.6 → core_framework-0.12.8}/core/auth/helpers.py +0 -0
- {core_framework-0.12.6 → core_framework-0.12.8}/core/auth/middleware.py +0 -0
- {core_framework-0.12.6 → core_framework-0.12.8}/core/auth/models.py +0 -0
- {core_framework-0.12.6 → core_framework-0.12.8}/core/auth/permissions.py +0 -0
- {core_framework-0.12.6 → core_framework-0.12.8}/core/auth/schemas.py +0 -0
- {core_framework-0.12.6 → core_framework-0.12.8}/core/auth/tokens.py +0 -0
- {core_framework-0.12.6 → core_framework-0.12.8}/core/auth/views.py +0 -0
- {core_framework-0.12.6 → core_framework-0.12.8}/core/choices.py +0 -0
- {core_framework-0.12.6 → core_framework-0.12.8}/core/config.py +0 -0
- {core_framework-0.12.6 → core_framework-0.12.8}/core/database.py +0 -0
- {core_framework-0.12.6 → core_framework-0.12.8}/core/datetime.py +0 -0
- {core_framework-0.12.6 → core_framework-0.12.8}/core/dependencies.py +0 -0
- {core_framework-0.12.6 → core_framework-0.12.8}/core/deployment/__init__.py +0 -0
- {core_framework-0.12.6 → core_framework-0.12.8}/core/deployment/docker.py +0 -0
- {core_framework-0.12.6 → core_framework-0.12.8}/core/deployment/kubernetes.py +0 -0
- {core_framework-0.12.6 → core_framework-0.12.8}/core/deployment/pm2.py +0 -0
- {core_framework-0.12.6 → core_framework-0.12.8}/core/exceptions.py +0 -0
- {core_framework-0.12.6 → core_framework-0.12.8}/core/fields.py +0 -0
- {core_framework-0.12.6 → core_framework-0.12.8}/core/messaging/__init__.py +0 -0
- {core_framework-0.12.6 → core_framework-0.12.8}/core/messaging/avro.py +0 -0
- {core_framework-0.12.6 → core_framework-0.12.8}/core/messaging/base.py +0 -0
- {core_framework-0.12.6 → core_framework-0.12.8}/core/messaging/config.py +0 -0
- {core_framework-0.12.6 → core_framework-0.12.8}/core/messaging/confluent/__init__.py +0 -0
- {core_framework-0.12.6 → core_framework-0.12.8}/core/messaging/confluent/consumer.py +0 -0
- {core_framework-0.12.6 → core_framework-0.12.8}/core/messaging/confluent/producer.py +0 -0
- {core_framework-0.12.6 → core_framework-0.12.8}/core/messaging/decorators.py +0 -0
- {core_framework-0.12.6 → core_framework-0.12.8}/core/messaging/kafka/__init__.py +0 -0
- {core_framework-0.12.6 → core_framework-0.12.8}/core/messaging/kafka/admin.py +0 -0
- {core_framework-0.12.6 → core_framework-0.12.8}/core/messaging/kafka/broker.py +0 -0
- {core_framework-0.12.6 → core_framework-0.12.8}/core/messaging/kafka/consumer.py +0 -0
- {core_framework-0.12.6 → core_framework-0.12.8}/core/messaging/kafka/producer.py +0 -0
- {core_framework-0.12.6 → core_framework-0.12.8}/core/messaging/rabbitmq/__init__.py +0 -0
- {core_framework-0.12.6 → core_framework-0.12.8}/core/messaging/rabbitmq/broker.py +0 -0
- {core_framework-0.12.6 → core_framework-0.12.8}/core/messaging/rabbitmq/consumer.py +0 -0
- {core_framework-0.12.6 → core_framework-0.12.8}/core/messaging/rabbitmq/producer.py +0 -0
- {core_framework-0.12.6 → core_framework-0.12.8}/core/messaging/redis/__init__.py +0 -0
- {core_framework-0.12.6 → core_framework-0.12.8}/core/messaging/redis/broker.py +0 -0
- {core_framework-0.12.6 → core_framework-0.12.8}/core/messaging/redis/consumer.py +0 -0
- {core_framework-0.12.6 → core_framework-0.12.8}/core/messaging/redis/producer.py +0 -0
- {core_framework-0.12.6 → core_framework-0.12.8}/core/messaging/registry.py +0 -0
- {core_framework-0.12.6 → core_framework-0.12.8}/core/messaging/topics.py +0 -0
- {core_framework-0.12.6 → core_framework-0.12.8}/core/messaging/workers.py +0 -0
- {core_framework-0.12.6 → core_framework-0.12.8}/core/middleware.py +0 -0
- {core_framework-0.12.6 → core_framework-0.12.8}/core/migrations/__init__.py +0 -0
- {core_framework-0.12.6 → core_framework-0.12.8}/core/migrations/analyzer.py +0 -0
- {core_framework-0.12.6 → core_framework-0.12.8}/core/migrations/cli.py +0 -0
- {core_framework-0.12.6 → core_framework-0.12.8}/core/migrations/engine.py +0 -0
- {core_framework-0.12.6 → core_framework-0.12.8}/core/migrations/migration.py +0 -0
- {core_framework-0.12.6 → core_framework-0.12.8}/core/migrations/operations.py +0 -0
- {core_framework-0.12.6 → core_framework-0.12.8}/core/migrations/state.py +0 -0
- {core_framework-0.12.6 → core_framework-0.12.8}/core/models.py +0 -0
- {core_framework-0.12.6 → core_framework-0.12.8}/core/querysets.py +0 -0
- {core_framework-0.12.6 → core_framework-0.12.8}/core/relations.py +0 -0
- {core_framework-0.12.6 → core_framework-0.12.8}/core/routing.py +0 -0
- {core_framework-0.12.6 → core_framework-0.12.8}/core/serializers.py +0 -0
- {core_framework-0.12.6 → core_framework-0.12.8}/core/tasks/__init__.py +0 -0
- {core_framework-0.12.6 → core_framework-0.12.8}/core/tasks/base.py +0 -0
- {core_framework-0.12.6 → core_framework-0.12.8}/core/tasks/config.py +0 -0
- {core_framework-0.12.6 → core_framework-0.12.8}/core/tasks/decorators.py +0 -0
- {core_framework-0.12.6 → core_framework-0.12.8}/core/tasks/registry.py +0 -0
- {core_framework-0.12.6 → core_framework-0.12.8}/core/tasks/scheduler.py +0 -0
- {core_framework-0.12.6 → core_framework-0.12.8}/core/tasks/worker.py +0 -0
- {core_framework-0.12.6 → core_framework-0.12.8}/core/tenancy.py +0 -0
- {core_framework-0.12.6 → core_framework-0.12.8}/core/validators.py +0 -0
- {core_framework-0.12.6 → core_framework-0.12.8}/core/views.py +0 -0
- {core_framework-0.12.6 → core_framework-0.12.8}/docs/01-quickstart.md +0 -0
- {core_framework-0.12.6 → core_framework-0.12.8}/docs/02-viewsets.md +0 -0
- {core_framework-0.12.6 → core_framework-0.12.8}/docs/03-authentication.md +0 -0
- {core_framework-0.12.6 → core_framework-0.12.8}/docs/04-messaging.md +0 -0
- {core_framework-0.12.6 → core_framework-0.12.8}/docs/05-multi-service.md +0 -0
- {core_framework-0.12.6 → core_framework-0.12.8}/docs/06-tasks.md +0 -0
- {core_framework-0.12.6 → core_framework-0.12.8}/docs/07-deployment.md +0 -0
- {core_framework-0.12.6 → core_framework-0.12.8}/docs/08-complete-example.md +0 -0
- {core_framework-0.12.6 → core_framework-0.12.8}/docs/09-settings.md +0 -0
- {core_framework-0.12.6 → core_framework-0.12.8}/docs/10-migrations.md +0 -0
- {core_framework-0.12.6 → core_framework-0.12.8}/docs/11-permissions.md +0 -0
- {core_framework-0.12.6 → core_framework-0.12.8}/docs/12-auth-backends.md +0 -0
- {core_framework-0.12.6 → core_framework-0.12.8}/docs/13-validators.md +0 -0
- {core_framework-0.12.6 → core_framework-0.12.8}/docs/14-querysets.md +0 -0
- {core_framework-0.12.6 → core_framework-0.12.8}/docs/15-routing.md +0 -0
- {core_framework-0.12.6 → core_framework-0.12.8}/docs/16-serializers.md +0 -0
- {core_framework-0.12.6 → core_framework-0.12.8}/docs/17-datetime.md +0 -0
- {core_framework-0.12.6 → core_framework-0.12.8}/docs/18-dependencies.md +0 -0
- {core_framework-0.12.6 → core_framework-0.12.8}/docs/19-views.md +0 -0
- {core_framework-0.12.6 → core_framework-0.12.8}/docs/20-fields.md +0 -0
- {core_framework-0.12.6 → core_framework-0.12.8}/docs/21-tenancy.md +0 -0
- {core_framework-0.12.6 → core_framework-0.12.8}/docs/22-replicas.md +0 -0
- {core_framework-0.12.6 → core_framework-0.12.8}/docs/23-soft-delete.md +0 -0
- {core_framework-0.12.6 → core_framework-0.12.8}/docs/24-relations.md +0 -0
- {core_framework-0.12.6 → core_framework-0.12.8}/docs/25-exceptions.md +0 -0
- {core_framework-0.12.6 → core_framework-0.12.8}/docs/26-choices.md +0 -0
- {core_framework-0.12.6 → core_framework-0.12.8}/docs/27-workers.md +0 -0
- {core_framework-0.12.6 → core_framework-0.12.8}/docs/28-avro.md +0 -0
- {core_framework-0.12.6 → core_framework-0.12.8}/docs/29-topics.md +0 -0
- {core_framework-0.12.6 → core_framework-0.12.8}/docs/30-changelog-0.12.2.md +0 -0
- {core_framework-0.12.6 → core_framework-0.12.8}/docs/31-middleware.md +0 -0
- {core_framework-0.12.6 → core_framework-0.12.8}/docs/32-migration-guide-0.12.2.md +0 -0
- {core_framework-0.12.6 → core_framework-0.12.8}/docs/33-changelog-0.12.3.md +0 -0
- {core_framework-0.12.6 → core_framework-0.12.8}/docs/99-faq-troubleshooting.md +0 -0
- {core_framework-0.12.6 → core_framework-0.12.8}/docs/GUIDE.md +0 -0
- {core_framework-0.12.6 → core_framework-0.12.8}/docs/README.md +0 -0
- {core_framework-0.12.6 → core_framework-0.12.8}/example/__init__.py +0 -0
- {core_framework-0.12.6 → core_framework-0.12.8}/example/app.py +0 -0
- {core_framework-0.12.6 → core_framework-0.12.8}/example/auth.py +0 -0
- {core_framework-0.12.6 → core_framework-0.12.8}/example/models.py +0 -0
- {core_framework-0.12.6 → core_framework-0.12.8}/example/schemas.py +0 -0
- {core_framework-0.12.6 → core_framework-0.12.8}/example/views.py +0 -0
- {core_framework-0.12.6 → core_framework-0.12.8}/libs/__init__.py +0 -0
- {core_framework-0.12.6 → core_framework-0.12.8}/main.py +0 -0
- {core_framework-0.12.6 → core_framework-0.12.8}/tests/__init__.py +0 -0
- {core_framework-0.12.6 → core_framework-0.12.8}/tests/conftest.py +0 -0
- {core_framework-0.12.6 → core_framework-0.12.8}/tests/test_models.py +0 -0
- {core_framework-0.12.6 → core_framework-0.12.8}/tests/test_querysets.py +0 -0
- {core_framework-0.12.6 → core_framework-0.12.8}/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.8
|
|
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
|
|
@@ -56,6 +56,11 @@ Provides-Extra: rabbitmq
|
|
|
56
56
|
Requires-Dist: aio-pika>=9.0.0; extra == 'rabbitmq'
|
|
57
57
|
Provides-Extra: redis
|
|
58
58
|
Requires-Dist: redis>=5.0.0; extra == 'redis'
|
|
59
|
+
Provides-Extra: testing
|
|
60
|
+
Requires-Dist: faker>=20.0.0; extra == 'testing'
|
|
61
|
+
Requires-Dist: httpx>=0.26.0; extra == 'testing'
|
|
62
|
+
Requires-Dist: pytest-asyncio>=0.23.0; extra == 'testing'
|
|
63
|
+
Requires-Dist: pytest>=7.4.0; extra == 'testing'
|
|
59
64
|
Description-Content-Type: text/markdown
|
|
60
65
|
|
|
61
66
|
# Core Framework
|
|
@@ -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(
|
|
@@ -10,6 +10,8 @@ Comandos disponíveis:
|
|
|
10
10
|
- core run: Executa o servidor de desenvolvimento
|
|
11
11
|
- core shell: Abre shell interativo
|
|
12
12
|
- core routes: Lista rotas registradas
|
|
13
|
+
- core test: Executa testes com ambiente isolado
|
|
14
|
+
- core version: Mostra versão do framework
|
|
13
15
|
"""
|
|
14
16
|
|
|
15
17
|
from core.cli.main import cli, main
|
|
@@ -13,7 +13,16 @@ Comandos:
|
|
|
13
13
|
run Executa servidor de desenvolvimento
|
|
14
14
|
shell Abre shell interativo async
|
|
15
15
|
routes Lista rotas registradas
|
|
16
|
+
test Executa testes com ambiente isolado
|
|
16
17
|
version Mostra versão do framework
|
|
18
|
+
|
|
19
|
+
Comando test:
|
|
20
|
+
core test # Roda todos os testes em tests/
|
|
21
|
+
core test tests/test_auth.py # Roda arquivo específico
|
|
22
|
+
core test -v # Saída verbosa
|
|
23
|
+
core test --cov # Com cobertura de código
|
|
24
|
+
core test -k "test_login" # Filtrar por keyword
|
|
25
|
+
core test -m unit # Apenas testes unitários
|
|
17
26
|
"""
|
|
18
27
|
|
|
19
28
|
from __future__ import annotations
|
|
@@ -472,6 +481,112 @@ def cmd_version(args: argparse.Namespace) -> int:
|
|
|
472
481
|
return 0
|
|
473
482
|
|
|
474
483
|
|
|
484
|
+
def cmd_test(args: argparse.Namespace) -> int:
|
|
485
|
+
"""
|
|
486
|
+
Run tests with auto-discovery and isolated environment.
|
|
487
|
+
|
|
488
|
+
This command:
|
|
489
|
+
- Automatically sets up an isolated test environment
|
|
490
|
+
- Initializes database with in-memory SQLite
|
|
491
|
+
- Configures auth, settings, and middleware
|
|
492
|
+
- Runs pytest with appropriate options
|
|
493
|
+
- Supports coverage reporting
|
|
494
|
+
|
|
495
|
+
Usage:
|
|
496
|
+
core test # Run all tests in tests/
|
|
497
|
+
core test tests/test_auth.py # Run specific file
|
|
498
|
+
core test -v # Verbose output
|
|
499
|
+
core test --cov # With coverage
|
|
500
|
+
core test -k "test_login" # Filter by keyword
|
|
501
|
+
core test -m unit # Only unit tests
|
|
502
|
+
"""
|
|
503
|
+
import subprocess
|
|
504
|
+
import shutil
|
|
505
|
+
|
|
506
|
+
# Check if pytest is installed
|
|
507
|
+
pytest_path = shutil.which("pytest")
|
|
508
|
+
if not pytest_path:
|
|
509
|
+
print(error("pytest not installed. Install with:"))
|
|
510
|
+
print(info(" pip install pytest pytest-asyncio"))
|
|
511
|
+
return 1
|
|
512
|
+
|
|
513
|
+
print(bold("🧪 Core Framework Test Runner"))
|
|
514
|
+
print()
|
|
515
|
+
|
|
516
|
+
# Build pytest command
|
|
517
|
+
cmd = ["python", "-m", "pytest"]
|
|
518
|
+
|
|
519
|
+
# Add test path
|
|
520
|
+
cmd.append(args.path)
|
|
521
|
+
|
|
522
|
+
# Verbose
|
|
523
|
+
if args.verbose:
|
|
524
|
+
cmd.append("-v")
|
|
525
|
+
|
|
526
|
+
# Keyword filter
|
|
527
|
+
if args.keyword:
|
|
528
|
+
cmd.extend(["-k", args.keyword])
|
|
529
|
+
|
|
530
|
+
# Exit on first failure
|
|
531
|
+
if args.exitfirst:
|
|
532
|
+
cmd.append("-x")
|
|
533
|
+
|
|
534
|
+
# Marker filter
|
|
535
|
+
if args.marker:
|
|
536
|
+
cmd.extend(["-m", args.marker])
|
|
537
|
+
|
|
538
|
+
# No header
|
|
539
|
+
if args.no_header:
|
|
540
|
+
cmd.append("--no-header")
|
|
541
|
+
|
|
542
|
+
# Coverage
|
|
543
|
+
if args.cov:
|
|
544
|
+
# Check if pytest-cov is installed
|
|
545
|
+
if not shutil.which("pytest-cov") and not _check_pytest_cov():
|
|
546
|
+
print(warning("pytest-cov not installed. Install with:"))
|
|
547
|
+
print(info(" pip install pytest-cov"))
|
|
548
|
+
print()
|
|
549
|
+
|
|
550
|
+
cmd.append(f"--cov={args.cov}")
|
|
551
|
+
cmd.append(f"--cov-report={args.cov_report}")
|
|
552
|
+
|
|
553
|
+
# Always use asyncio mode auto
|
|
554
|
+
cmd.extend(["--asyncio-mode=auto"])
|
|
555
|
+
|
|
556
|
+
# Show short traceback
|
|
557
|
+
cmd.append("--tb=short")
|
|
558
|
+
|
|
559
|
+
print(info(f"Running: {' '.join(cmd)}"))
|
|
560
|
+
print()
|
|
561
|
+
|
|
562
|
+
# Set environment variables for isolated testing
|
|
563
|
+
env = os.environ.copy()
|
|
564
|
+
env["TESTING"] = "true"
|
|
565
|
+
env["DEBUG"] = "true"
|
|
566
|
+
env.setdefault("DATABASE_URL", "sqlite+aiosqlite:///:memory:")
|
|
567
|
+
env.setdefault("SECRET_KEY", "test-secret-key-for-testing-only")
|
|
568
|
+
|
|
569
|
+
# Run pytest
|
|
570
|
+
try:
|
|
571
|
+
result = subprocess.run(cmd, env=env)
|
|
572
|
+
return result.returncode
|
|
573
|
+
except KeyboardInterrupt:
|
|
574
|
+
print(warning("\nTests interrupted"))
|
|
575
|
+
return 130
|
|
576
|
+
except Exception as e:
|
|
577
|
+
print(error(f"Error running tests: {e}"))
|
|
578
|
+
return 1
|
|
579
|
+
|
|
580
|
+
|
|
581
|
+
def _check_pytest_cov() -> bool:
|
|
582
|
+
"""Check if pytest-cov is installed as a module."""
|
|
583
|
+
try:
|
|
584
|
+
import pytest_cov
|
|
585
|
+
return True
|
|
586
|
+
except ImportError:
|
|
587
|
+
return False
|
|
588
|
+
|
|
589
|
+
|
|
475
590
|
def check_uv_installed() -> bool:
|
|
476
591
|
"""Verifica se uv está instalado."""
|
|
477
592
|
import shutil
|
|
@@ -3747,6 +3862,54 @@ For more information, visit: https://github.com/SorPuti/core-framework
|
|
|
3747
3862
|
)
|
|
3748
3863
|
tasks_parser.set_defaults(func=cmd_tasks)
|
|
3749
3864
|
|
|
3865
|
+
# test
|
|
3866
|
+
test_parser = subparsers.add_parser(
|
|
3867
|
+
"test",
|
|
3868
|
+
help="Run tests with auto-discovery and isolated environment"
|
|
3869
|
+
)
|
|
3870
|
+
test_parser.add_argument(
|
|
3871
|
+
"path",
|
|
3872
|
+
nargs="?",
|
|
3873
|
+
default="tests",
|
|
3874
|
+
help="Test path or file (default: tests)"
|
|
3875
|
+
)
|
|
3876
|
+
test_parser.add_argument(
|
|
3877
|
+
"-v", "--verbose",
|
|
3878
|
+
action="store_true",
|
|
3879
|
+
help="Verbose output"
|
|
3880
|
+
)
|
|
3881
|
+
test_parser.add_argument(
|
|
3882
|
+
"-k", "--keyword",
|
|
3883
|
+
help="Only run tests matching keyword expression"
|
|
3884
|
+
)
|
|
3885
|
+
test_parser.add_argument(
|
|
3886
|
+
"-x", "--exitfirst",
|
|
3887
|
+
action="store_true",
|
|
3888
|
+
help="Exit on first failure"
|
|
3889
|
+
)
|
|
3890
|
+
test_parser.add_argument(
|
|
3891
|
+
"--cov",
|
|
3892
|
+
nargs="?",
|
|
3893
|
+
const=".",
|
|
3894
|
+
help="Enable coverage (optionally specify source)"
|
|
3895
|
+
)
|
|
3896
|
+
test_parser.add_argument(
|
|
3897
|
+
"--cov-report",
|
|
3898
|
+
choices=["term", "html", "xml", "json"],
|
|
3899
|
+
default="term",
|
|
3900
|
+
help="Coverage report format"
|
|
3901
|
+
)
|
|
3902
|
+
test_parser.add_argument(
|
|
3903
|
+
"-m", "--marker",
|
|
3904
|
+
help="Only run tests with this marker (e.g., 'unit', 'integration')"
|
|
3905
|
+
)
|
|
3906
|
+
test_parser.add_argument(
|
|
3907
|
+
"--no-header",
|
|
3908
|
+
action="store_true",
|
|
3909
|
+
help="Disable pytest header"
|
|
3910
|
+
)
|
|
3911
|
+
test_parser.set_defaults(func=cmd_test)
|
|
3912
|
+
|
|
3750
3913
|
return parser
|
|
3751
3914
|
|
|
3752
3915
|
|
|
@@ -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,99 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Testing utilities for core-framework applications.
|
|
3
|
+
|
|
4
|
+
This module provides a complete testing toolkit for applications built
|
|
5
|
+
with core-framework, including:
|
|
6
|
+
|
|
7
|
+
- TestClient: HTTP client with automatic test database setup
|
|
8
|
+
- AuthenticatedClient: Pre-authenticated HTTP client
|
|
9
|
+
- TestDatabase: Database utilities for testing
|
|
10
|
+
- MockKafka, MockRedis, MockHTTP: Mock services
|
|
11
|
+
- Factory: Data factory pattern for test data generation
|
|
12
|
+
|
|
13
|
+
Usage:
|
|
14
|
+
# In your conftest.py
|
|
15
|
+
import pytest
|
|
16
|
+
from your_app import app as _app
|
|
17
|
+
|
|
18
|
+
@pytest.fixture(scope="session")
|
|
19
|
+
def app():
|
|
20
|
+
return _app.app
|
|
21
|
+
|
|
22
|
+
# In your tests
|
|
23
|
+
class TestUsers:
|
|
24
|
+
async def test_register(self, client):
|
|
25
|
+
response = await client.post("/auth/register", json={...})
|
|
26
|
+
assert response.status_code == 201
|
|
27
|
+
|
|
28
|
+
async def test_profile(self, auth_client):
|
|
29
|
+
response = await auth_client.get("/auth/me")
|
|
30
|
+
assert response.status_code == 200
|
|
31
|
+
|
|
32
|
+
Quick Start:
|
|
33
|
+
1. Install test dependencies: pip install core-framework[testing]
|
|
34
|
+
2. Add to pyproject.toml:
|
|
35
|
+
[tool.pytest.ini_options]
|
|
36
|
+
asyncio_mode = "auto"
|
|
37
|
+
plugins = ["core.testing.plugin"]
|
|
38
|
+
3. Create conftest.py with your app fixture
|
|
39
|
+
4. Write tests using provided fixtures
|
|
40
|
+
"""
|
|
41
|
+
|
|
42
|
+
from core.testing.client import (
|
|
43
|
+
TestClient,
|
|
44
|
+
AuthenticatedClient,
|
|
45
|
+
create_test_client,
|
|
46
|
+
create_auth_client,
|
|
47
|
+
)
|
|
48
|
+
from core.testing.database import (
|
|
49
|
+
TestDatabase,
|
|
50
|
+
setup_test_db,
|
|
51
|
+
teardown_test_db,
|
|
52
|
+
get_test_session,
|
|
53
|
+
)
|
|
54
|
+
from core.testing.mocks import (
|
|
55
|
+
MockKafka,
|
|
56
|
+
MockRedis,
|
|
57
|
+
MockHTTP,
|
|
58
|
+
MockMessage,
|
|
59
|
+
MockHTTPResponse,
|
|
60
|
+
)
|
|
61
|
+
from core.testing.factories import (
|
|
62
|
+
Factory,
|
|
63
|
+
UserFactory,
|
|
64
|
+
fake,
|
|
65
|
+
)
|
|
66
|
+
from core.testing.assertions import (
|
|
67
|
+
assert_status,
|
|
68
|
+
assert_json_contains,
|
|
69
|
+
assert_error_code,
|
|
70
|
+
assert_validation_error,
|
|
71
|
+
)
|
|
72
|
+
|
|
73
|
+
__all__ = [
|
|
74
|
+
# Client
|
|
75
|
+
"TestClient",
|
|
76
|
+
"AuthenticatedClient",
|
|
77
|
+
"create_test_client",
|
|
78
|
+
"create_auth_client",
|
|
79
|
+
# Database
|
|
80
|
+
"TestDatabase",
|
|
81
|
+
"setup_test_db",
|
|
82
|
+
"teardown_test_db",
|
|
83
|
+
"get_test_session",
|
|
84
|
+
# Mocks
|
|
85
|
+
"MockKafka",
|
|
86
|
+
"MockRedis",
|
|
87
|
+
"MockHTTP",
|
|
88
|
+
"MockMessage",
|
|
89
|
+
"MockHTTPResponse",
|
|
90
|
+
# Factories
|
|
91
|
+
"Factory",
|
|
92
|
+
"UserFactory",
|
|
93
|
+
"fake",
|
|
94
|
+
# Assertions
|
|
95
|
+
"assert_status",
|
|
96
|
+
"assert_json_contains",
|
|
97
|
+
"assert_error_code",
|
|
98
|
+
"assert_validation_error",
|
|
99
|
+
]
|