Testing¶
Test Structure¶
tests/
├── conftest.py # Shared fixtures
├── auth/
│ ├── unit/
│ │ ├── test_user_entity.py
│ │ └── test_login_usecase.py
│ ├── integration/
│ └── security/
├── shipments/
│ ├── unit/
│ └── integration/
└── security/
├── test_sql_injection.py
└── test_tenant_isolation.py
Running Tests¶
Backend¶
Frontend¶
Test Types¶
Unit Tests¶
Test individual components in isolation:
@pytest.mark.asyncio
async def test_login_usecase_success():
mock_repository = AsyncMock()
mock_user = User(id="u1", email="a@b.com", name="T", role="user", tenant_id="t1")
mock_repository.login.return_value = ("token", mock_user)
usecase = LoginUseCase(mock_repository)
result = await usecase.execute("a@b.com", "password")
assert result is not None
Integration Tests¶
Test API endpoints:
@pytest.mark.asyncio
async def test_create_shipment():
response = await client.post(
"/api/v1/shipments",
headers={"Authorization": f"Bearer {token}"},
json={...}
)
assert response.status_code == 201
Security Tests¶
# Tenant isolation
uv run pytest tests/security/test_tenant_isolation.py -v
# SQL injection prevention
uv run pytest tests/security/test_sql_injection.py -v
Test Coverage¶
Backend¶
Frontend¶
| Module | Count |
|---|---|
| core/error | 3 |
| core/theme | 6 |
| auth/domain | 22 |
| core/storage | 8 |
| validators | 13 |
| translations | 20 |
| core/security | 37 |
| modules/tracking | 37 |
| modules/shipments | 18 |
| core/realtime | 12 |
Mocking¶
Repository Mock¶
HTTP Mock¶
from httpx import AsyncClient, MockTransport
async def test_api():
async with AsyncClient(transport=MockTransport(handler)) as client:
response = await client.get("/api/v1/shipments")
Test Fixtures¶
@pytest.fixture
def auth_headers():
return {"Authorization": f"Bearer {test_token}"}
@pytest.fixture
async def db_session():
async with async_session() as session:
yield session
TDD Protocol¶
- Write/update tests for the feature
- Run
uv run pytest - Analyze failures
- Fix code following Dependency Rule
- Repeat until passing
Test Catalog¶
This section provides a detailed catalog of all test suites implemented in the backend.
Test Organization¶
Tests are organized into three main categories:
- Unit Tests (
tests/<module>/unit/): Focus on testing individual components, use cases, and business logic in isolation using mocks. - Integration Tests (
tests/<module>/integration/): Verify the interaction between different layers of the application, including database persistence and API endpoints. - Security Tests (
tests/security/): Specifically target authorization, permission logic, tenant isolation, and security scenarios.
Auth Module¶
Unit Tests (tests/auth/unit/)¶
tests/auth/unit/test_register_user_unit.py¶
Tests for the RegisterUserUseCase business logic:
- test_register_user_success: Verifies that the user registration use case correctly processes valid data, hashes the password, and persists the user.
- test_register_user_failure_on_repository: Ensures that if the repository fails (e.g., due to a duplicate email), the use case correctly propagates the error.
tests/auth/unit/test_jwt_service.py¶
Tests for the JWTService:
- test_create_access_token: Verifies access token creation with correct payload and claims.
- test_create_refresh_token: Verifies refresh token creation.
- test_decode_invalid_token_returns_empty: Verifies that invalid tokens return empty dict.
- test_get_user_id_from_token: Verifies extraction of user ID from token.
- test_get_tenant_id_from_token: Verifies extraction of tenant ID from token.
Integration Tests (tests/auth/integration/)¶
tests/auth/integration/test_auth_flow.py¶
Tests complete authentication lifecycle:
- test_auth_lifecycle_flow: Validates user registration -> login -> token refresh -> token invalidation.
- test_login_invalid_credentials: Ensures login with wrong password returns 401.
tests/auth/integration/test_register_integration.py¶
Tests the /auth/register endpoint:
- test_register_endpoint_integration: Tests successful HTTP POST /auth/register.
- test_register_duplicate_email_integration: Verifies duplicate email handling.
- test_register_malformed_payload_integration: Validates input schema validation.
tests/auth/integration/test_session.py¶
Tests session management:
- test_session_revocation: Tests session creation and rotation with revocation.
tests/auth/integration/test_concurrency.py¶
Tests concurrency handling:
- test_registration_race_condition: Tests race conditions during registration.
- test_session_contention_refresh_rotation: Tests session contention during refresh.
tests/auth/integration/test_resilience.py¶
Tests resilience and error handling:
- test_auth_lifecycle_e2e: Complete end-to-end auth flow.
- test_resilience_malformed_payloads: Tests handling of malformed payloads.
- test_resilience_database_error_simulation: Tests database error handling.
tests/auth/integration/test_get_authenticated_user.py¶
Tests authenticated user retrieval:
- test_get_authenticated_user: Tests retrieving the authenticated user.
tests/auth/integration/test_validation.py¶
Tests input validation:
- test_login_user_not_exists: Tests login with non-existent user.
- test_login_user_inactive: Tests login with inactive user.
- test_register_password_validation_too_short: Tests password length validation.
- test_register_email_validation_invalid: Tests email format validation.
- test_refresh_expired_token: Tests refresh with invalid/expired token.
tests/auth/integration/test_logout.py¶
Tests the /auth/logout endpoint:
- test_logout_success: Tests successful logout with valid refresh token.
- test_logout_invalid_token: Tests logout with invalid token returns 401.
tests/auth/integration/test_auth_endpoints.py¶
Tests for all auth endpoints:
- test_login_success: Tests successful login.
- test_login_invalid_password: Tests 401 for wrong password.
- test_login_nonexistent_user: Tests 401 for non-existent user.
- test_logout_success: Tests successful logout with refresh token in body.
- test_logout_without_token: Tests 401/422 for missing token.
- test_refresh_success: Tests token refresh.
- test_refresh_invalid_token: Tests 401 for invalid token.
- test_refresh_expired_token: Tests 401 for expired token.
- test_send_verification_success: Tests sending verification email.
- test_verify_email_success: Tests email verification with valid code.
- test_verify_email_invalid_code: Tests 404 for non-existent email.
- test_verify_email_nonexistent_user: Tests 404 for non-existent user.
- test_send_reset_password_success: Tests sending reset password email.
- test_send_reset_password_nonexistent_user: Tests 200 for security (no enum).
- test_send_reset_password_invalid_email: Tests 422 for invalid email.
- test_reset_password_success: Tests successful password reset.
- test_reset_password_invalid_code: Tests 400 for invalid code.
- test_reset_password_expired_code: Tests 400 for expired code.
- test_reset_password_nonexistent_user: Tests 400 for non-existent user.
- test_reset_password_invalid_email: Tests 422 for invalid email.
- test_reset_password_password_too_short: Tests 422 for short password.
- test_register_success: Tests successful registration.
- test_register_duplicate_email: Tests 400 for duplicate email.
- test_register_invalid_email: Tests 422 for invalid email.
- test_register_password_too_short: Tests registration behavior with short password.
tests/auth/integration/test_password_reset.py¶
Tests for password reset endpoints:
- test_send_reset_password_success: Tests successful reset email send.
- test_send_reset_password_user_not_found: Tests 200 for security (no enum).
- test_send_reset_password_invalid_email: Tests 422 for invalid email.
- test_reset_password_success: Tests successful password reset.
- test_reset_password_invalid_code: Tests 400 for invalid code.
- test_reset_password_expired_code: Tests 400 for expired code.
- test_reset_password_user_not_found: Tests 400 for non-existent user.
- test_reset_password_invalid_email: Tests 422 for invalid email.
- test_reset_password_password_too_short: Tests 422 for short password.
Security Tests (tests/auth/security/)¶
tests/auth/security/test_authorize.py¶
Tests authorization:
- test_authorize_success: Confirms authorized access with correct permissions.
- test_authorize_fail: Confirms denied access with insufficient permissions.
tests/auth/security/test_rbac.py¶
Tests Role-Based Access Control:
- test_authorize_with_single_permission: Tests single permission authorization.
- test_authorize_with_multiple_permissions: Tests multiple permission authorization.
- test_authorize_insufficient_permissions_raises: Tests insufficient permissions error.
- test_authorize_empty_permissions_raises: Tests empty permissions error.
tests/auth/security/test_security_advanced.py¶
Advanced security tests:
- test_tenant_isolation_cross_tenant_access: Tests tenant isolation.
- test_sql_injection_payloads: Tests SQL injection and XSS prevention.
Shipments Module¶
Unit Tests (tests/shipments/unit/)¶
tests/shipments/unit/test_shipment_repository.py¶
Tests for the ShipmentRepository:
- test_to_entity: Verifies mapping from model to entity.
- test_create_shipment: Tests shipment creation.
- test_get_by_id_found: Tests retrieving existing shipment.
- test_get_by_id_not_found: Tests 404 for missing shipment.
- test_get_by_tracking_id: Tests tracking ID lookup.
- test_get_all_empty: Tests empty list response.
- test_get_all_with_status_filter: Tests filtering by status.
- test_delete_shipment: Tests shipment deletion.
- test_delete_shipment_not_found: Tests deletion of non-existent shipment.
tests/shipments/unit/test_bag_repository.py¶
Tests for the BagRepository:
- test_to_entity: Verifies mapping from model to entity.
- test_create_bag: Tests bag creation.
- test_get_by_id_found: Tests retrieving existing bag.
- test_get_by_id_not_found: Tests 404 for missing bag.
- test_get_all: Tests listing all bags.
- test_get_by_user: Tests filtering bags by user.
- test_delete_bag: Tests bag deletion.
- test_delete_bag_not_found: Tests deletion of non-existent bag.
- test_update_weight: Tests weight update after shipment assignment.
tests/shipments/unit/test_status_transitions.py¶
Tests for shipment status transitions:
- test_create_shipment_has_received_status: Verifies new shipments start as RECEIVED.
- test_tracking_id_is_generated: Verifies tracking ID format (TRK-XXXXXXXX).
- test_valid_status_transitions: Tests all valid status values.
- test_payment_status_values: Tests payment status enum values.
- test_shipment_immutable_fields: Verifies ID and tracking_id cannot be changed.
- test_shipment_status_enum_values: Verifies all status enum strings.
- test_shipment_serialization: Tests entity serialization to dict.
tests/shipments/unit/test_bag_flight_repository.py¶
Tests for the BagFlightRepository (layovers support):
- test_to_entity: Verifies mapping from model to entity.
- test_assign_flight_to_bag: Tests assigning flight to bag.
- test_assign_flight_to_bag_duplicate: Tests duplicate assignment handling.
- test_remove_flight_from_bag: Tests removing flight from bag.
- test_remove_flight_from_bag_not_found: Tests removing non-existent association.
- test_get_flights_for_bag: Tests retrieving flights for a bag.
- test_get_flights_for_bag_empty: Tests empty itinerary response.
- test_get_flights_for_bag_ordered: Verifies flights ordered by departure_date.
Integration Tests (tests/shipments/integration/)¶
tests/shipments/integration/test_shipments_api.py¶
Tests for shipments CRUD API:
- test_list_shipments_empty: Tests empty list response.
- test_list_shipments_with_data: Tests list with data.
- test_list_shipments_with_status_filter: Tests status filtering.
- test_create_shipment: Tests shipment creation with 201 response.
- test_create_shipment_validation_error: Tests 422 for invalid data.
- test_get_shipment_by_id: Tests getting shipment by ID.
- test_get_shipment_not_found: Tests 404 for missing shipment.
- test_update_shipment: Tests updating shipment fields.
- test_delete_shipment: Tests deleting shipment with 204 response.
- test_unauthorized_access: Tests 401 without token.
- test_invalid_token: Tests 401 with invalid token.
tests/shipments/integration/test_tracking_api.py¶
Tests for public tracking API:
- test_track_shipment_success: Tests successful tracking lookup.
- test_track_shipment_not_found: Tests 404 for invalid tracking ID.
- test_track_no_auth_required: Verifies public endpoint doesn't require auth.
- test_track_response_fields: Verifies all required fields in response.
tests/shipments/integration/test_bags_api.py¶
Tests for bags CRUD API:
- test_list_bags_empty: Tests empty list response.
- test_list_bags_with_data: Tests list with data.
- test_create_bag: Tests bag creation with 201 response.
- test_create_bag_validation_error_missing_fields: Tests 422 for invalid data.
- test_create_bag_invalid_capacity: Tests 422 for negative capacity.
- test_get_bag_by_id: Tests getting bag by ID.
- test_get_bag_not_found: Tests 404 for missing bag.
- test_update_bag: Tests updating bag fields.
- test_update_bag_not_found: Tests 404 for updating non-existent bag.
- test_delete_bag: Tests deleting bag with 204 response.
- test_delete_bag_not_found: Tests 404 for deleting non-existent bag.
- test_get_bag_shipments: Tests getting shipments in a bag.
- test_unauthorized_access: Tests 401 without token.
- test_invalid_token: Tests 401 with invalid token.
tests/shipments/integration/test_flights_api.py¶
Tests for flights CRUD API:
- test_list_flights_empty: Tests empty list response.
- test_list_flights_with_data: Tests list with data.
- test_create_flight: Tests flight creation with 201 response.
- test_create_flight_minimal_data: Tests flight creation with only required fields.
- test_create_flight_validation_error_missing_fields: Tests 422 for invalid data.
- test_get_flight_by_id: Tests getting flight by ID.
- test_get_flight_not_found: Tests 404 for missing flight.
- test_update_flight: Tests updating flight fields.
- test_update_flight_not_found: Tests 404 for updating non-existent flight.
- test_delete_flight: Tests deleting flight with 204 response.
- test_delete_flight_not_found: Tests 404 for deleting non-existent flight.
- test_unauthorized_access: Tests 401 without token.
- test_invalid_token: Tests 401 with invalid token.
tests/shipments/integration/test_courier_api.py¶
Tests for courier-specific API endpoints:
- test_list_courier_bags_empty: Tests empty list for courier with no bags.
- test_list_courier_bags_with_data: Tests listing courier's bags.
- test_list_courier_bags_only_own_bags: Tests that courier only sees their own bags.
- test_get_courier_bag_shipments: Tests getting shipments in courier's bag.
- test_get_courier_bag_shipments_bag_not_found: Tests 404 for missing bag.
- test_get_courier_bag_shipments_not_own_bag: Tests 403 when accessing another courier's bag.
- test_mark_delivered_success: Tests marking shipment as delivered with proof.
- test_mark_delivered_without_proof: Tests marking shipment as delivered without proof.
- test_mark_delivered_shipment_not_found: Tests 404 for missing shipment.
- test_mark_delivered_shipment_not_in_bag: Tests 400 for shipment not assigned to bag.
- test_mark_delivered_own_shipment: Tests 200 when delivering own shipment.
- test_unauthorized_access: Tests 401 without token.
- test_invalid_token: Tests 401 with invalid token.
tests/shipments/integration/test_bag_flights_api.py¶
Tests for bag flight itinerary API (layovers support):
- test_assign_flight_to_bag: Tests assigning flight to bag.
- test_assign_flight_to_bag_already_assigned: Tests 400 for duplicate assignment.
- test_assign_flight_bag_not_found: Tests 404 for non-existent bag.
- test_assign_flight_flight_not_found: Tests 404 for non-existent flight.
- test_remove_flight_from_bag: Tests removing flight from bag.
- test_remove_flight_from_bag_not_found: Tests 404 for non-existent association.
- test_get_bag_itinerary_empty: Tests empty itinerary response.
- test_get_bag_itinerary_single_flight: Tests itinerary with one flight.
- test_get_bag_itinerary_multiple_flights: Tests itinerary with multiple flights ordered by departure_date.
- test_get_bag_itinerary_bag_not_found: Tests 404 for non-existent bag.
- test_itinerary_preserves_order: Verifies flights are ordered by departure_date.
Notifications Module¶
Unit Tests (tests/notifications/)¶
tests/notifications/test_websocket_manager.py¶
Tests for the ConnectionManager WebSocket handling:
- test_connect: Tests WebSocket connection establishment.
- test_connect_multiple_connections_same_user: Tests multiple connections per user.
- test_disconnect: Tests WebSocket disconnection.
- test_disconnect_one_of_multiple: Tests disconnecting one of multiple connections.
- test_send_to_user_success: Tests sending message to connected user.
- test_send_to_user_not_connected: Tests sending to non-connected user.
- test_broadcast: Tests broadcasting to all connected users.
- test_active_users_count: Tests active users counter.
- test_total_connections_count: Tests total connections counter.
- test_disconnect_nonexistent_user: Tests disconnecting non-existent user.
tests/notifications/integration/test_webhook_endpoints.py¶
Tests for webhook endpoints:
- test_whatsapp_webhook_get_missing_params: Tests 400 for missing verification params.
- test_whatsapp_webhook_get_wrong_token: Tests 403 for wrong verify token.
- test_whatsapp_webhook_get_valid_token: Tests 200 for valid verification.
- test_whatsapp_webhook_get_invalid_mode: Tests 400 for invalid subscription mode.
- test_whatsapp_webhook_post_invalid_json: Tests 400 for invalid JSON body.
- test_whatsapp_webhook_post_valid_message: Tests 200 for valid WhatsApp message.
- test_flights_webhook_get_health_check: Tests webhook health check.
- test_flights_webhook_post_missing_secret: Tests 422 for missing secret param.
- test_flights_webhook_post_wrong_secret: Tests 403 for wrong secret.
- test_flights_webhook_post_invalid_json: Tests 422 for invalid JSON body.
- test_flights_webhook_post_valid_secret: Tests 200 for valid secret.
Security Tests (tests/security/)¶
tests/security/test_tenant_isolation.py¶
test_tenant_a_cannot_see_tenant_b_shipments: Verifies shipments are filtered by tenant.test_tenant_a_cannot_access_tenant_b_shipment: Verifies GET by ID respects tenant.test_tenant_a_cannot_update_tenant_b_shipment: Verifies PUT respects tenant boundaries.test_tenant_a_cannot_see_tenant_b_bags: Verifies bags are filtered by user_id.test_tenant_a_cannot_see_tenant_b_flights: Verifies flights are filtered by user_id.
tests/security/test_token_security.py¶
test_access_token_invalid_after_logout: Verifies tokens invalidated on logout.test_refresh_token_invalid_after_logout: Verifies refresh tokens invalidated.test_refresh_token_returns_new_tokens: Verifies token rotation on refresh.test_old_refresh_token_invalid_after_rotation: Verifies old token rejected after rotation.test_missing_bearer_prefix_rejected: Verifies malformed auth headers rejected.test_empty_token_rejected: Verifies empty bearer tokens rejected.test_random_string_rejected: Verifies random strings rejected.test_malformed_jwt_rejected: Verifies malformed JWTs rejected.test_access_token_contains_required_claims: Verifies JWT has required claims.test_refresh_token_contains_required_claims: Verifies refresh token has required claims.test_access_token_exp_is_set: Verifies expiration is set.test_decode_expired_token_returns_empty_payload: Verifies expired tokens handled.test_password_not_in_token_payload: Verifies sensitive fields blocked from JWT.test_hashed_password_not_in_token_payload: Verifies hashed_password blocked.test_token_payload_tampering_detected: Verifies tampered tokens rejected.
tests/security/test_cors_security.py¶
test_cors_origins_configured: Verifies CORS origins set.test_cors_credentials_set: Verifies credentials flag set.test_cors_methods_configured: Verifies allowed methods configured.test_cors_headers_configured: Verifies allowed headers configured.test_whitelisted_origin_in_config: Verifies whitelist has valid origins.test_localhost_origins_for_development: Verifies localhost allowed in dev.test_production_should_not_have_localhost: Verifies prod config safe.test_wildcard_origin_not_used: Verifies wildcard not used.test_null_origin_not_in_list: Verifies null origin blocked.test_credentials_requires_explicit_origin: Verifies credentials need explicit origin.test_dangerous_methods_not_allowed: Verifies dangerous methods blocked.test_authorization_header_allowed: Verifies auth header allowed.test_content_type_header_allowed: Verifies content-type allowed.test_dangerous_headers_not_allowed: Verifies dangerous headers blocked.
tests/security/test_rate_limiting.py¶
test_rate_limit_headers_present: Verifies rate limit headers in response.test_login_rate_limited_after_excess: Verifies rate limiting enforced.test_rate_limiter_configured: Verifies rate limiter exists.test_rate_limit_exceeded_handler_registered: Verifies handler registered.test_rate_limit_tier_configuration: Verifies tier limits configured.test_starter_tier_has_lowest_limit: Verifies STARTER tier has lowest limit.test_enterprise_tier_has_highest_or_unlimited: Verifies ENTERPRISE tier best limits.test_rate_limit_key_function_exists: Verifies key function exists.test_limiter_is_configured: Verifies limiter configured.
tests/security/test_sql_injection_prevention.py¶
test_tenant_context_uses_parameterized_query: Verifies parameterized queries used.test_tenant_context_with_malicious_input: Verifies malicious input handled safely.test_set_current_tenant_accepts_uuid: Verifies UUID accepted.test_set_current_tenant_accepts_string: Verifies string accepted.test_clear_current_tenant: Verifies tenant cleared after request.
Test Results Summary¶
Auth Module¶
| Type | Tests |
|---|---|
| Unit | 25 |
| Integration | 53 |
| Security | 7 |
| Total | 85 |
Shipments Module¶
| Type | Tests |
|---|---|
| Unit | 33 |
| Integration | 66 |
| Total | 99 |
Notifications Module¶
| Type | Tests |
|---|---|
| Unit | 10 |
| Integration | 11 |
| Total | 21 |
Core¶
| Type | Tests |
|---|---|
| Versioning | 23 |
| Total | 23 |
Security (New)¶
| Type | Tests |
|---|---|
| Tenant Isolation | 5 |
| Token Security | 15 |
| CORS Security | 14 |
| Rate Limiting | 9 |
| SQL Injection Prevention | 5 |
| Total | 48 |
Overall¶
| Module | Tests |
|---|---|
| Auth | 85 |
| Shipments | 99 |
| Notifications | 21 |
| Core | 23 |
| Security | 48 |
| Storage | 53 |
| Total | 471 |
Fixtures Overview¶
tests/conftest.py¶
Provides reusable fixtures:
- cleanup_test_db: Cleans up SQLite test database before/after test session.
- async_db_engine: Creates async database engine for tests.
- async_session_factory: Creates async session factory.
- session: Provides a database session for tests.
- client: Provides HTTP client with session override.
- test_client: Async test client using httpx with ASGITransport.
Key Notes for Integration Tests¶
- Async fixtures: All fixtures that use
test_clientmust be declared asasync. - User registration: Integration tests must register a user before authenticated requests.
- Frozen dataclasses: Domain entities are frozen (immutable). Use
dataclasses.replace()for updates, notsetattr(). - UTC timezone: All datetime values in the backend use UTC timezone. Always use
datetime.now(timezone.utc)instead ofdatetime.now(). Database columns useTIMESTAMP WITH TIME ZONEvia SQLModel's_utc_timestamp()helper.
Quality Gates¶
| Gate | Command | Criteria |
|---|---|---|
| Tests | cd saas-backend && uv run pytest tests/ -x -q |
100% passing |
| Linting | cd saas-backend && uv run ruff check . |
0 errors |
| Security | cd saas-backend && uv run bandit -r . |
0 vulnerabilities |
| Frontend Tests | cd saas-fronted && uv run pytest test/ -v |
100% passing |
| Visual | cd saas-fronted && uv run python tests/visual/run_snapshots.py |
0 regressions |
Ver también: Quality Gates · CI/CD