core-framework 0.12.6__py3-none-any.whl → 0.12.8__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- core/__init__.py +1 -1
- core/auth/decorators.py +28 -7
- core/cli/__init__.py +2 -0
- core/cli/main.py +163 -0
- core/permissions.py +29 -8
- core/testing/__init__.py +99 -0
- core/testing/assertions.py +347 -0
- core/testing/client.py +247 -0
- core/testing/database.py +307 -0
- core/testing/factories.py +393 -0
- core/testing/mocks.py +658 -0
- core/testing/plugin.py +635 -0
- {core_framework-0.12.6.dist-info → core_framework-0.12.8.dist-info}/METADATA +6 -1
- {core_framework-0.12.6.dist-info → core_framework-0.12.8.dist-info}/RECORD +16 -9
- {core_framework-0.12.6.dist-info → core_framework-0.12.8.dist-info}/entry_points.txt +3 -0
- {core_framework-0.12.6.dist-info → core_framework-0.12.8.dist-info}/WHEEL +0 -0
core/testing/plugin.py
ADDED
|
@@ -0,0 +1,635 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Pytest plugin for core-framework applications.
|
|
3
|
+
|
|
4
|
+
This plugin provides:
|
|
5
|
+
- Automatic test environment setup (no manual configuration needed)
|
|
6
|
+
- Isolated database with in-memory SQLite
|
|
7
|
+
- Mocked external services (Kafka, Redis, HTTP)
|
|
8
|
+
- Auto-discovery of tests
|
|
9
|
+
- Pre-configured fixtures for common use cases
|
|
10
|
+
|
|
11
|
+
The plugin automatically initializes:
|
|
12
|
+
- Database session factories
|
|
13
|
+
- Auth configuration
|
|
14
|
+
- Settings with test defaults
|
|
15
|
+
- Middleware registry
|
|
16
|
+
|
|
17
|
+
Usage:
|
|
18
|
+
# Just run pytest - everything is auto-configured!
|
|
19
|
+
pytest tests/
|
|
20
|
+
|
|
21
|
+
# With coverage
|
|
22
|
+
pytest tests/ --cov=src --cov-report=html
|
|
23
|
+
|
|
24
|
+
# For integration tests that need your app:
|
|
25
|
+
# Create conftest.py with app fixture
|
|
26
|
+
|
|
27
|
+
@pytest.fixture(scope="session")
|
|
28
|
+
def app():
|
|
29
|
+
from your_app.main import app
|
|
30
|
+
return app
|
|
31
|
+
"""
|
|
32
|
+
|
|
33
|
+
from __future__ import annotations
|
|
34
|
+
|
|
35
|
+
import logging
|
|
36
|
+
import os
|
|
37
|
+
import sys
|
|
38
|
+
from typing import TYPE_CHECKING, Any
|
|
39
|
+
|
|
40
|
+
import pytest
|
|
41
|
+
|
|
42
|
+
if TYPE_CHECKING:
|
|
43
|
+
from httpx import AsyncClient
|
|
44
|
+
from sqlalchemy.ext.asyncio import AsyncSession
|
|
45
|
+
from core.testing.mocks import MockKafka, MockRedis, MockHTTP
|
|
46
|
+
|
|
47
|
+
logger = logging.getLogger("core.testing")
|
|
48
|
+
|
|
49
|
+
# Track if environment has been initialized
|
|
50
|
+
_environment_initialized = False
|
|
51
|
+
|
|
52
|
+
|
|
53
|
+
# =============================================================================
|
|
54
|
+
# Environment Setup (runs once before any tests)
|
|
55
|
+
# =============================================================================
|
|
56
|
+
|
|
57
|
+
def _setup_test_environment():
|
|
58
|
+
"""
|
|
59
|
+
Setup isolated test environment.
|
|
60
|
+
|
|
61
|
+
This initializes all core-framework components with test-safe defaults,
|
|
62
|
+
preventing errors like "Database not initialized" or "Auth not configured".
|
|
63
|
+
"""
|
|
64
|
+
global _environment_initialized
|
|
65
|
+
|
|
66
|
+
if _environment_initialized:
|
|
67
|
+
return
|
|
68
|
+
|
|
69
|
+
logger.info("Setting up core-framework test environment...")
|
|
70
|
+
|
|
71
|
+
# Set environment variables for test mode
|
|
72
|
+
os.environ.setdefault("TESTING", "true")
|
|
73
|
+
os.environ.setdefault("DEBUG", "true")
|
|
74
|
+
os.environ.setdefault("DATABASE_URL", "sqlite+aiosqlite:///:memory:")
|
|
75
|
+
os.environ.setdefault("SECRET_KEY", "test-secret-key-for-testing-only-do-not-use-in-production")
|
|
76
|
+
|
|
77
|
+
# Initialize settings with test defaults
|
|
78
|
+
_init_test_settings()
|
|
79
|
+
|
|
80
|
+
# Initialize database factories
|
|
81
|
+
_init_test_database()
|
|
82
|
+
|
|
83
|
+
# Initialize auth with test defaults
|
|
84
|
+
_init_test_auth()
|
|
85
|
+
|
|
86
|
+
# Clear any existing middleware
|
|
87
|
+
_init_test_middleware()
|
|
88
|
+
|
|
89
|
+
_environment_initialized = True
|
|
90
|
+
logger.info("Test environment ready")
|
|
91
|
+
|
|
92
|
+
|
|
93
|
+
def _init_test_settings():
|
|
94
|
+
"""Initialize settings module with test defaults."""
|
|
95
|
+
try:
|
|
96
|
+
from core.config import Settings
|
|
97
|
+
|
|
98
|
+
# Create test settings instance
|
|
99
|
+
class TestSettings(Settings):
|
|
100
|
+
"""Test settings with safe defaults."""
|
|
101
|
+
|
|
102
|
+
debug: bool = True
|
|
103
|
+
testing: bool = True
|
|
104
|
+
database_url: str = "sqlite+aiosqlite:///:memory:"
|
|
105
|
+
secret_key: str = "test-secret-key-for-testing-only"
|
|
106
|
+
|
|
107
|
+
class Config:
|
|
108
|
+
env_prefix = ""
|
|
109
|
+
|
|
110
|
+
# Override get_settings to return test settings
|
|
111
|
+
import core.config as config_module
|
|
112
|
+
|
|
113
|
+
_test_settings = None
|
|
114
|
+
|
|
115
|
+
def get_test_settings() -> Settings:
|
|
116
|
+
nonlocal _test_settings
|
|
117
|
+
if _test_settings is None:
|
|
118
|
+
_test_settings = TestSettings()
|
|
119
|
+
return _test_settings
|
|
120
|
+
|
|
121
|
+
config_module.get_settings = get_test_settings
|
|
122
|
+
config_module._settings = None # Clear cache
|
|
123
|
+
|
|
124
|
+
logger.debug("Test settings initialized")
|
|
125
|
+
except ImportError:
|
|
126
|
+
logger.debug("core.config not available, skipping settings init")
|
|
127
|
+
except Exception as e:
|
|
128
|
+
logger.debug(f"Could not initialize test settings: {e}")
|
|
129
|
+
|
|
130
|
+
|
|
131
|
+
def _init_test_database():
|
|
132
|
+
"""Initialize database module with test factories."""
|
|
133
|
+
try:
|
|
134
|
+
from sqlalchemy.ext.asyncio import create_async_engine, async_sessionmaker, AsyncSession
|
|
135
|
+
import core.database as db_module
|
|
136
|
+
|
|
137
|
+
# Create in-memory SQLite engine
|
|
138
|
+
test_engine = create_async_engine(
|
|
139
|
+
"sqlite+aiosqlite:///:memory:",
|
|
140
|
+
echo=False,
|
|
141
|
+
connect_args={"check_same_thread": False},
|
|
142
|
+
)
|
|
143
|
+
|
|
144
|
+
# Create session factory
|
|
145
|
+
test_session_factory = async_sessionmaker(
|
|
146
|
+
test_engine,
|
|
147
|
+
class_=AsyncSession,
|
|
148
|
+
expire_on_commit=False,
|
|
149
|
+
)
|
|
150
|
+
|
|
151
|
+
# Set both read and write to same factory (no replicas in tests)
|
|
152
|
+
db_module._write_session_factory = test_session_factory
|
|
153
|
+
db_module._read_session_factory = test_session_factory
|
|
154
|
+
|
|
155
|
+
# Store engine for table creation
|
|
156
|
+
db_module._test_engine = test_engine
|
|
157
|
+
|
|
158
|
+
logger.debug("Test database factories initialized")
|
|
159
|
+
except ImportError:
|
|
160
|
+
logger.debug("core.database not available, skipping database init")
|
|
161
|
+
except Exception as e:
|
|
162
|
+
logger.debug(f"Could not initialize test database: {e}")
|
|
163
|
+
|
|
164
|
+
|
|
165
|
+
def _init_test_auth():
|
|
166
|
+
"""Initialize auth module with test defaults."""
|
|
167
|
+
try:
|
|
168
|
+
from core.auth.base import configure_auth, AuthConfig
|
|
169
|
+
|
|
170
|
+
# Configure auth with test defaults
|
|
171
|
+
configure_auth(
|
|
172
|
+
secret_key="test-secret-key-for-testing-only",
|
|
173
|
+
algorithm="HS256",
|
|
174
|
+
access_token_expire_minutes=30,
|
|
175
|
+
refresh_token_expire_days=7,
|
|
176
|
+
warn_missing_middleware=False, # Don't warn in tests
|
|
177
|
+
)
|
|
178
|
+
|
|
179
|
+
logger.debug("Test auth initialized")
|
|
180
|
+
except ImportError:
|
|
181
|
+
logger.debug("core.auth not available, skipping auth init")
|
|
182
|
+
except Exception as e:
|
|
183
|
+
logger.debug(f"Could not initialize test auth: {e}")
|
|
184
|
+
|
|
185
|
+
|
|
186
|
+
def _init_test_middleware():
|
|
187
|
+
"""Clear middleware registry for clean test state."""
|
|
188
|
+
try:
|
|
189
|
+
from core.middleware import clear_middleware_registry
|
|
190
|
+
clear_middleware_registry()
|
|
191
|
+
logger.debug("Middleware registry cleared")
|
|
192
|
+
except ImportError:
|
|
193
|
+
logger.debug("core.middleware not available")
|
|
194
|
+
except Exception as e:
|
|
195
|
+
logger.debug(f"Could not clear middleware: {e}")
|
|
196
|
+
|
|
197
|
+
|
|
198
|
+
async def _create_test_tables():
|
|
199
|
+
"""Create all database tables for testing."""
|
|
200
|
+
try:
|
|
201
|
+
import core.database as db_module
|
|
202
|
+
from core.models import Model
|
|
203
|
+
|
|
204
|
+
engine = getattr(db_module, '_test_engine', None)
|
|
205
|
+
if engine:
|
|
206
|
+
async with engine.begin() as conn:
|
|
207
|
+
await conn.run_sync(Model.metadata.create_all)
|
|
208
|
+
logger.debug("Test tables created")
|
|
209
|
+
except Exception as e:
|
|
210
|
+
logger.debug(f"Could not create test tables: {e}")
|
|
211
|
+
|
|
212
|
+
|
|
213
|
+
async def _drop_test_tables():
|
|
214
|
+
"""Drop all database tables after testing."""
|
|
215
|
+
try:
|
|
216
|
+
import core.database as db_module
|
|
217
|
+
from core.models import Model
|
|
218
|
+
|
|
219
|
+
engine = getattr(db_module, '_test_engine', None)
|
|
220
|
+
if engine:
|
|
221
|
+
async with engine.begin() as conn:
|
|
222
|
+
await conn.run_sync(Model.metadata.drop_all)
|
|
223
|
+
logger.debug("Test tables dropped")
|
|
224
|
+
except Exception as e:
|
|
225
|
+
logger.debug(f"Could not drop test tables: {e}")
|
|
226
|
+
|
|
227
|
+
|
|
228
|
+
# =============================================================================
|
|
229
|
+
# Pytest Hooks
|
|
230
|
+
# =============================================================================
|
|
231
|
+
|
|
232
|
+
def pytest_configure(config):
|
|
233
|
+
"""
|
|
234
|
+
Configure pytest for core-framework testing.
|
|
235
|
+
|
|
236
|
+
This runs before any tests are collected.
|
|
237
|
+
"""
|
|
238
|
+
# Register markers
|
|
239
|
+
config.addinivalue_line(
|
|
240
|
+
"markers",
|
|
241
|
+
"integration: mark as integration test (may require external services)"
|
|
242
|
+
)
|
|
243
|
+
config.addinivalue_line(
|
|
244
|
+
"markers",
|
|
245
|
+
"slow: mark as slow test"
|
|
246
|
+
)
|
|
247
|
+
config.addinivalue_line(
|
|
248
|
+
"markers",
|
|
249
|
+
"auth: mark as requiring authentication"
|
|
250
|
+
)
|
|
251
|
+
config.addinivalue_line(
|
|
252
|
+
"markers",
|
|
253
|
+
"database: mark as requiring database"
|
|
254
|
+
)
|
|
255
|
+
config.addinivalue_line(
|
|
256
|
+
"markers",
|
|
257
|
+
"unit: mark as unit test (no external dependencies)"
|
|
258
|
+
)
|
|
259
|
+
|
|
260
|
+
# Setup test environment immediately
|
|
261
|
+
_setup_test_environment()
|
|
262
|
+
|
|
263
|
+
|
|
264
|
+
def pytest_collection_modifyitems(config, items):
|
|
265
|
+
"""
|
|
266
|
+
Modify collected test items.
|
|
267
|
+
|
|
268
|
+
Auto-discovers and marks tests based on their location/name.
|
|
269
|
+
"""
|
|
270
|
+
for item in items:
|
|
271
|
+
# Auto-mark tests based on path
|
|
272
|
+
if "integration" in str(item.fspath):
|
|
273
|
+
item.add_marker(pytest.mark.integration)
|
|
274
|
+
if "unit" in str(item.fspath):
|
|
275
|
+
item.add_marker(pytest.mark.unit)
|
|
276
|
+
|
|
277
|
+
|
|
278
|
+
def pytest_sessionstart(session):
|
|
279
|
+
"""Called after the Session object has been created."""
|
|
280
|
+
logger.debug("Pytest session starting")
|
|
281
|
+
|
|
282
|
+
|
|
283
|
+
def pytest_sessionfinish(session, exitstatus):
|
|
284
|
+
"""Called after whole test run finished."""
|
|
285
|
+
logger.debug(f"Pytest session finished with status {exitstatus}")
|
|
286
|
+
|
|
287
|
+
|
|
288
|
+
# =============================================================================
|
|
289
|
+
# App Fixture
|
|
290
|
+
# =============================================================================
|
|
291
|
+
|
|
292
|
+
@pytest.fixture(scope="session")
|
|
293
|
+
def app():
|
|
294
|
+
"""
|
|
295
|
+
Application instance fixture.
|
|
296
|
+
|
|
297
|
+
Override this in your conftest.py if you need to test HTTP endpoints:
|
|
298
|
+
|
|
299
|
+
@pytest.fixture(scope="session")
|
|
300
|
+
def app():
|
|
301
|
+
from your_app.main import app
|
|
302
|
+
return app
|
|
303
|
+
|
|
304
|
+
For unit tests that don't need HTTP, you don't need to override this.
|
|
305
|
+
"""
|
|
306
|
+
# Return a minimal app for unit tests
|
|
307
|
+
try:
|
|
308
|
+
from fastapi import FastAPI
|
|
309
|
+
return FastAPI(title="Test App")
|
|
310
|
+
except ImportError:
|
|
311
|
+
return None
|
|
312
|
+
|
|
313
|
+
|
|
314
|
+
# =============================================================================
|
|
315
|
+
# Database Fixtures
|
|
316
|
+
# =============================================================================
|
|
317
|
+
|
|
318
|
+
@pytest.fixture(scope="session")
|
|
319
|
+
async def test_engine():
|
|
320
|
+
"""
|
|
321
|
+
Database engine for the test session.
|
|
322
|
+
|
|
323
|
+
Creates tables at start, drops at end.
|
|
324
|
+
"""
|
|
325
|
+
await _create_test_tables()
|
|
326
|
+
|
|
327
|
+
try:
|
|
328
|
+
import core.database as db_module
|
|
329
|
+
yield getattr(db_module, '_test_engine', None)
|
|
330
|
+
except ImportError:
|
|
331
|
+
yield None
|
|
332
|
+
|
|
333
|
+
await _drop_test_tables()
|
|
334
|
+
|
|
335
|
+
|
|
336
|
+
@pytest.fixture
|
|
337
|
+
async def db(test_engine) -> "AsyncSession":
|
|
338
|
+
"""
|
|
339
|
+
Database session for tests.
|
|
340
|
+
|
|
341
|
+
Each test gets a fresh session that's rolled back after the test.
|
|
342
|
+
|
|
343
|
+
Usage:
|
|
344
|
+
async def test_create_user(db):
|
|
345
|
+
user = User(email="test@example.com")
|
|
346
|
+
db.add(user)
|
|
347
|
+
await db.commit()
|
|
348
|
+
|
|
349
|
+
assert user.id is not None
|
|
350
|
+
"""
|
|
351
|
+
try:
|
|
352
|
+
import core.database as db_module
|
|
353
|
+
|
|
354
|
+
factory = db_module._write_session_factory
|
|
355
|
+
if factory is None:
|
|
356
|
+
pytest.skip("Database not initialized")
|
|
357
|
+
|
|
358
|
+
session = factory()
|
|
359
|
+
try:
|
|
360
|
+
yield session
|
|
361
|
+
await session.commit()
|
|
362
|
+
except Exception:
|
|
363
|
+
await session.rollback()
|
|
364
|
+
raise
|
|
365
|
+
finally:
|
|
366
|
+
await session.close()
|
|
367
|
+
except ImportError:
|
|
368
|
+
pytest.skip("core.database not available")
|
|
369
|
+
|
|
370
|
+
|
|
371
|
+
@pytest.fixture
|
|
372
|
+
async def clean_db(db) -> "AsyncSession":
|
|
373
|
+
"""
|
|
374
|
+
Database session with clean tables.
|
|
375
|
+
|
|
376
|
+
Truncates all tables before the test.
|
|
377
|
+
"""
|
|
378
|
+
try:
|
|
379
|
+
from core.models import Model
|
|
380
|
+
import core.database as db_module
|
|
381
|
+
|
|
382
|
+
engine = getattr(db_module, '_test_engine', None)
|
|
383
|
+
if engine:
|
|
384
|
+
async with engine.begin() as conn:
|
|
385
|
+
for table in reversed(Model.metadata.sorted_tables):
|
|
386
|
+
await conn.execute(table.delete())
|
|
387
|
+
except Exception:
|
|
388
|
+
pass
|
|
389
|
+
|
|
390
|
+
yield db
|
|
391
|
+
|
|
392
|
+
|
|
393
|
+
# =============================================================================
|
|
394
|
+
# HTTP Client Fixtures
|
|
395
|
+
# =============================================================================
|
|
396
|
+
|
|
397
|
+
@pytest.fixture
|
|
398
|
+
async def client(app, test_engine) -> "AsyncClient":
|
|
399
|
+
"""
|
|
400
|
+
HTTP test client with initialized database.
|
|
401
|
+
|
|
402
|
+
Usage:
|
|
403
|
+
async def test_health(client):
|
|
404
|
+
response = await client.get("/health")
|
|
405
|
+
assert response.status_code == 200
|
|
406
|
+
"""
|
|
407
|
+
if app is None:
|
|
408
|
+
pytest.skip("No app fixture defined")
|
|
409
|
+
|
|
410
|
+
try:
|
|
411
|
+
from httpx import AsyncClient, ASGITransport
|
|
412
|
+
|
|
413
|
+
async with AsyncClient(
|
|
414
|
+
transport=ASGITransport(app=app),
|
|
415
|
+
base_url="http://test",
|
|
416
|
+
follow_redirects=True,
|
|
417
|
+
) as c:
|
|
418
|
+
yield c
|
|
419
|
+
except ImportError:
|
|
420
|
+
pytest.skip("httpx not installed")
|
|
421
|
+
|
|
422
|
+
|
|
423
|
+
@pytest.fixture
|
|
424
|
+
async def auth_client(app, test_engine) -> "AsyncClient":
|
|
425
|
+
"""
|
|
426
|
+
Authenticated HTTP test client.
|
|
427
|
+
|
|
428
|
+
Creates a test user and includes auth token in requests.
|
|
429
|
+
|
|
430
|
+
Usage:
|
|
431
|
+
async def test_profile(auth_client):
|
|
432
|
+
response = await auth_client.get("/api/v1/auth/me")
|
|
433
|
+
assert response.status_code == 200
|
|
434
|
+
"""
|
|
435
|
+
if app is None:
|
|
436
|
+
pytest.skip("No app fixture defined")
|
|
437
|
+
|
|
438
|
+
try:
|
|
439
|
+
from core.testing import AuthenticatedClient
|
|
440
|
+
|
|
441
|
+
async with AuthenticatedClient(app) as c:
|
|
442
|
+
yield c
|
|
443
|
+
except ImportError:
|
|
444
|
+
pytest.skip("core.testing.AuthenticatedClient not available")
|
|
445
|
+
except Exception as e:
|
|
446
|
+
pytest.skip(f"Could not create authenticated client: {e}")
|
|
447
|
+
|
|
448
|
+
|
|
449
|
+
@pytest.fixture
|
|
450
|
+
async def client_factory(app, test_engine):
|
|
451
|
+
"""
|
|
452
|
+
Factory for creating multiple authenticated clients.
|
|
453
|
+
|
|
454
|
+
Usage:
|
|
455
|
+
async def test_two_users(client_factory):
|
|
456
|
+
user1 = await client_factory("user1@example.com")
|
|
457
|
+
user2 = await client_factory("user2@example.com")
|
|
458
|
+
"""
|
|
459
|
+
if app is None:
|
|
460
|
+
pytest.skip("No app fixture defined")
|
|
461
|
+
|
|
462
|
+
from core.testing import AuthenticatedClient
|
|
463
|
+
|
|
464
|
+
clients = []
|
|
465
|
+
|
|
466
|
+
async def factory(
|
|
467
|
+
email: str = "test@example.com",
|
|
468
|
+
password: str = "TestPass123!",
|
|
469
|
+
) -> "AsyncClient":
|
|
470
|
+
client = AuthenticatedClient(app, email=email, password=password)
|
|
471
|
+
c = await client.__aenter__()
|
|
472
|
+
clients.append(client)
|
|
473
|
+
return c
|
|
474
|
+
|
|
475
|
+
yield factory
|
|
476
|
+
|
|
477
|
+
for client in clients:
|
|
478
|
+
try:
|
|
479
|
+
await client.__aexit__(None, None, None)
|
|
480
|
+
except Exception:
|
|
481
|
+
pass
|
|
482
|
+
|
|
483
|
+
|
|
484
|
+
# =============================================================================
|
|
485
|
+
# Mock Fixtures
|
|
486
|
+
# =============================================================================
|
|
487
|
+
|
|
488
|
+
@pytest.fixture
|
|
489
|
+
def mock_kafka() -> "MockKafka":
|
|
490
|
+
"""
|
|
491
|
+
Mock Kafka producer/consumer.
|
|
492
|
+
|
|
493
|
+
Usage:
|
|
494
|
+
async def test_event(mock_kafka):
|
|
495
|
+
await mock_kafka.send("events", {"type": "test"})
|
|
496
|
+
mock_kafka.assert_sent("events", count=1)
|
|
497
|
+
"""
|
|
498
|
+
from core.testing.mocks import MockKafka
|
|
499
|
+
|
|
500
|
+
kafka = MockKafka()
|
|
501
|
+
yield kafka
|
|
502
|
+
kafka.clear()
|
|
503
|
+
|
|
504
|
+
|
|
505
|
+
@pytest.fixture
|
|
506
|
+
def mock_redis() -> "MockRedis":
|
|
507
|
+
"""
|
|
508
|
+
Mock Redis client.
|
|
509
|
+
|
|
510
|
+
Usage:
|
|
511
|
+
async def test_cache(mock_redis):
|
|
512
|
+
await mock_redis.set("key", "value")
|
|
513
|
+
assert await mock_redis.get("key") == "value"
|
|
514
|
+
"""
|
|
515
|
+
from core.testing.mocks import MockRedis
|
|
516
|
+
|
|
517
|
+
redis = MockRedis()
|
|
518
|
+
yield redis
|
|
519
|
+
redis.clear()
|
|
520
|
+
|
|
521
|
+
|
|
522
|
+
@pytest.fixture
|
|
523
|
+
def mock_http() -> "MockHTTP":
|
|
524
|
+
"""
|
|
525
|
+
Mock HTTP client.
|
|
526
|
+
|
|
527
|
+
Usage:
|
|
528
|
+
def test_api(mock_http):
|
|
529
|
+
mock_http.when("GET", "https://api.example.com/data").respond(
|
|
530
|
+
status=200, json={"result": "ok"}
|
|
531
|
+
)
|
|
532
|
+
"""
|
|
533
|
+
from core.testing.mocks import MockHTTP
|
|
534
|
+
|
|
535
|
+
http = MockHTTP()
|
|
536
|
+
yield http
|
|
537
|
+
http.clear()
|
|
538
|
+
|
|
539
|
+
|
|
540
|
+
# =============================================================================
|
|
541
|
+
# Utility Fixtures
|
|
542
|
+
# =============================================================================
|
|
543
|
+
|
|
544
|
+
@pytest.fixture
|
|
545
|
+
def fake():
|
|
546
|
+
"""Faker instance for test data generation."""
|
|
547
|
+
from core.testing.factories import fake
|
|
548
|
+
return fake
|
|
549
|
+
|
|
550
|
+
|
|
551
|
+
@pytest.fixture
|
|
552
|
+
def user_factory():
|
|
553
|
+
"""Factory for creating test users."""
|
|
554
|
+
from core.testing.factories import UserFactory
|
|
555
|
+
return UserFactory
|
|
556
|
+
|
|
557
|
+
|
|
558
|
+
@pytest.fixture
|
|
559
|
+
def assert_status():
|
|
560
|
+
"""HTTP status assertion helper."""
|
|
561
|
+
from core.testing.assertions import assert_status
|
|
562
|
+
return assert_status
|
|
563
|
+
|
|
564
|
+
|
|
565
|
+
@pytest.fixture
|
|
566
|
+
def assert_json():
|
|
567
|
+
"""JSON assertion helper."""
|
|
568
|
+
from core.testing.assertions import assert_json_contains
|
|
569
|
+
return assert_json_contains
|
|
570
|
+
|
|
571
|
+
|
|
572
|
+
@pytest.fixture
|
|
573
|
+
async def logged_in_user(auth_client):
|
|
574
|
+
"""Info about the authenticated test user."""
|
|
575
|
+
return {
|
|
576
|
+
"email": "test@example.com",
|
|
577
|
+
"password": "TestPass123!",
|
|
578
|
+
}
|
|
579
|
+
|
|
580
|
+
|
|
581
|
+
# =============================================================================
|
|
582
|
+
# Settings Fixture
|
|
583
|
+
# =============================================================================
|
|
584
|
+
|
|
585
|
+
@pytest.fixture
|
|
586
|
+
def settings():
|
|
587
|
+
"""
|
|
588
|
+
Test settings instance.
|
|
589
|
+
|
|
590
|
+
Usage:
|
|
591
|
+
def test_config(settings):
|
|
592
|
+
assert settings.testing == True
|
|
593
|
+
"""
|
|
594
|
+
try:
|
|
595
|
+
from core.config import get_settings
|
|
596
|
+
return get_settings()
|
|
597
|
+
except ImportError:
|
|
598
|
+
return None
|
|
599
|
+
|
|
600
|
+
|
|
601
|
+
@pytest.fixture
|
|
602
|
+
def override_settings():
|
|
603
|
+
"""
|
|
604
|
+
Context manager to temporarily override settings.
|
|
605
|
+
|
|
606
|
+
Usage:
|
|
607
|
+
def test_with_custom_setting(override_settings):
|
|
608
|
+
with override_settings(debug=False):
|
|
609
|
+
# Test with debug=False
|
|
610
|
+
pass
|
|
611
|
+
"""
|
|
612
|
+
from contextlib import contextmanager
|
|
613
|
+
|
|
614
|
+
@contextmanager
|
|
615
|
+
def _override(**overrides):
|
|
616
|
+
try:
|
|
617
|
+
from core.config import get_settings
|
|
618
|
+
settings = get_settings()
|
|
619
|
+
|
|
620
|
+
# Store original values
|
|
621
|
+
original = {}
|
|
622
|
+
for key, value in overrides.items():
|
|
623
|
+
if hasattr(settings, key):
|
|
624
|
+
original[key] = getattr(settings, key)
|
|
625
|
+
setattr(settings, key, value)
|
|
626
|
+
|
|
627
|
+
yield settings
|
|
628
|
+
|
|
629
|
+
# Restore original values
|
|
630
|
+
for key, value in original.items():
|
|
631
|
+
setattr(settings, key, value)
|
|
632
|
+
except ImportError:
|
|
633
|
+
yield None
|
|
634
|
+
|
|
635
|
+
return _override
|
|
@@ -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
|