Files
training-software/tests/test_settings_matrix.py
Paperclip CTO 8054c1e1e4 feat(TRA-233): Django M1 foundation scaffold
- Environment-split settings: base/local/test/prod with django-environ
- Postgres + Redis + Celery wiring (broker, beat, result backend)
- All 9 domain app stubs: accounts, courses, cms, tracking, quizzes,
  training, certificates, reports, notifications
- api app: /healthz/ endpoint, custom DRF exception handler,
  SecurityAuditMiddleware, permissions/throttle/upload-validation stubs
- DRF global baseline: JWT+session auth, closed-by-default permissions,
  cursor/page pagination, drf-spectacular schema generation
- Dockerfile (multi-env build arg), docker-compose.yml (local),
  docker-compose.test.yml (CI-friendly tmpfs Postgres)
- pytest.ini with smoke + settings marker definitions
- tests/test_smoke.py: startup, URL resolution, healthcheck shape
- tests/test_settings_matrix.py: per-profile security assertions
- .github/workflows/ci.yml: test, lint, schema CI jobs
- .env.example with all required vars documented
- .gitignore

Co-Authored-By: Paperclip <noreply@paperclip.ing>
2026-05-07 09:11:23 +02:00

121 lines
4.2 KiB
Python

"""
Settings matrix tests.
Verify that each settings profile has the required keys and safe
defaults — without needing to boot each profile as a full Django app.
These run under the test settings module and import the other modules
directly for inspection.
"""
import importlib
import os
import pytest
from unittest.mock import patch
def _load_settings(module_path: str):
"""Import a settings module with minimal env vars patched in."""
env_patch = {
"DJANGO_SECRET_KEY": "test-secret-key",
"DJANGO_ALLOWED_HOSTS": "localhost",
"DATABASE_URL": "postgres://training:training@localhost:5432/training",
"REDIS_URL": "redis://localhost:6379/0",
# prod extras
"AWS_STORAGE_BUCKET_NAME": "test-bucket",
"EMAIL_HOST": "smtp.example.com",
"EMAIL_HOST_USER": "user@example.com",
"EMAIL_HOST_PASSWORD": "password",
"DEFAULT_FROM_EMAIL": "noreply@example.com",
}
with patch.dict(os.environ, env_patch, clear=False):
if module_path in [mod for mod in list(importlib._bootstrap._installed_safeguards or [])]:
importlib.reload(importlib.import_module(module_path))
return importlib.import_module(module_path)
@pytest.mark.settings
class TestBaseSettings:
def test_secret_key_required(self):
"""SECRET_KEY must be set from environment (not a default fallback)."""
mod = _load_settings("config.settings.base")
assert mod.SECRET_KEY == "test-secret-key"
def test_debug_is_false(self):
mod = _load_settings("config.settings.base")
assert mod.DEBUG is False
def test_rest_framework_has_exception_handler(self):
mod = _load_settings("config.settings.base")
assert mod.REST_FRAMEWORK["EXCEPTION_HANDLER"] == "api.exceptions.custom_exception_handler"
def test_rest_framework_default_closed(self):
mod = _load_settings("config.settings.base")
perms = mod.REST_FRAMEWORK["DEFAULT_PERMISSION_CLASSES"]
assert "rest_framework.permissions.IsAuthenticated" in perms
def test_celery_serializer_json(self):
mod = _load_settings("config.settings.base")
assert mod.CELERY_TASK_SERIALIZER == "json"
assert mod.CELERY_RESULT_SERIALIZER == "json"
assert "json" in mod.CELERY_ACCEPT_CONTENT
def test_language_code_german(self):
mod = _load_settings("config.settings.base")
assert mod.LANGUAGE_CODE == "de-de"
def test_timezone_europe_berlin(self):
mod = _load_settings("config.settings.base")
assert mod.TIME_ZONE == "Europe/Berlin"
@pytest.mark.settings
class TestLocalSettings:
def test_debug_is_true(self):
mod = _load_settings("config.settings.local")
assert mod.DEBUG is True
def test_debug_toolbar_in_installed_apps(self):
mod = _load_settings("config.settings.local")
assert "debug_toolbar" in mod.INSTALLED_APPS
def test_email_console_backend(self):
mod = _load_settings("config.settings.local")
assert "console" in mod.EMAIL_BACKEND.lower()
@pytest.mark.settings
class TestTestSettings:
def test_celery_always_eager(self):
mod = _load_settings("config.settings.test")
assert getattr(mod, "CELERY_TASK_ALWAYS_EAGER", False) is True
def test_debug_is_false(self):
mod = _load_settings("config.settings.test")
assert mod.DEBUG is False
def test_fast_password_hasher(self):
mod = _load_settings("config.settings.test")
assert any("MD5" in h for h in mod.PASSWORD_HASHERS)
@pytest.mark.settings
class TestProdSettings:
def test_debug_is_false(self):
mod = _load_settings("config.settings.prod")
assert mod.DEBUG is False
def test_ssl_redirect_enabled(self):
mod = _load_settings("config.settings.prod")
assert mod.SECURE_SSL_REDIRECT is True
def test_hsts_configured(self):
mod = _load_settings("config.settings.prod")
assert mod.SECURE_HSTS_SECONDS >= 31536000
def test_session_cookie_secure(self):
mod = _load_settings("config.settings.prod")
assert mod.SESSION_COOKIE_SECURE is True
def test_csrf_cookie_secure(self):
mod = _load_settings("config.settings.prod")
assert mod.CSRF_COOKIE_SECURE is True