Skip to content

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

cd saas-backend
uv run pytest tests/ -x -q

Frontend

cd saas-fronted
uv run pytest test/ -v

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

496 passed in 68.80s

Frontend

254 tests passing (2 skipped)
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

mock_repo = AsyncMock(spec=AuthRepositoryProtocol)
mock_repo.login.return_value = ("token", user)

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

  1. Write/update tests for the feature
  2. Run uv run pytest
  3. Analyze failures
  4. Fix code following Dependency Rule
  5. 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:

  1. Unit Tests (tests/<module>/unit/): Focus on testing individual components, use cases, and business logic in isolation using mocks.
  2. Integration Tests (tests/<module>/integration/): Verify the interaction between different layers of the application, including database persistence and API endpoints.
  3. 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

  1. Async fixtures: All fixtures that use test_client must be declared as async.
  2. User registration: Integration tests must register a user before authenticated requests.
  3. Frozen dataclasses: Domain entities are frozen (immutable). Use dataclasses.replace() for updates, not setattr().
  4. UTC timezone: All datetime values in the backend use UTC timezone. Always use datetime.now(timezone.utc) instead of datetime.now(). Database columns use TIMESTAMP WITH TIME ZONE via 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