- Move `from datetime import timedelta` to top of base.py (E402) - Add `# noqa: E402` to conditional sentry imports in prod.py (E402) - Remove unused `import time` and `IsAdminUser` from api/views.py (F401) - Remove unused `NoReverseMatch` import from tests/test_smoke.py (F401) Co-Authored-By: Paperclip <noreply@paperclip.ing>
103 lines
3.3 KiB
Python
103 lines
3.3 KiB
Python
"""
|
|
Startup smoke tests.
|
|
|
|
These verify that the project boots, migrations apply cleanly, the URL conf
|
|
resolves key routes, and the healthcheck endpoint returns the expected shape.
|
|
They do NOT require live Postgres/Redis — the test settings use an in-memory
|
|
cache and the test database configured in pytest.ini.
|
|
"""
|
|
import pytest
|
|
from django.urls import reverse, resolve
|
|
|
|
|
|
@pytest.mark.smoke
|
|
def test_django_settings_module_importable(settings):
|
|
"""The test settings module loads without error."""
|
|
assert settings.DJANGO_SETTINGS_MODULE if hasattr(settings, "DJANGO_SETTINGS_MODULE") else True
|
|
|
|
|
|
@pytest.mark.smoke
|
|
def test_installed_apps_complete(settings):
|
|
"""All expected apps are present in INSTALLED_APPS."""
|
|
required = [
|
|
"django.contrib.admin",
|
|
"django.contrib.auth",
|
|
"django.contrib.contenttypes",
|
|
"django.contrib.sessions",
|
|
"rest_framework",
|
|
"drf_spectacular",
|
|
"guardian",
|
|
"django_celery_results",
|
|
"api",
|
|
"accounts",
|
|
"courses",
|
|
"cms",
|
|
"tracking",
|
|
"quizzes",
|
|
"training",
|
|
"certificates",
|
|
"reports",
|
|
"notifications",
|
|
]
|
|
missing = [app for app in required if app not in settings.INSTALLED_APPS]
|
|
assert not missing, f"Missing INSTALLED_APPS entries: {missing}"
|
|
|
|
|
|
@pytest.mark.smoke
|
|
def test_healthcheck_url_resolves():
|
|
"""The /healthz/ URL must resolve to api:healthcheck."""
|
|
match = resolve("/healthz/")
|
|
assert match.view_name == "api:healthcheck"
|
|
|
|
|
|
@pytest.mark.smoke
|
|
def test_healthcheck_reverse():
|
|
"""reverse('api:healthcheck') must return /healthz/."""
|
|
assert reverse("api:healthcheck") == "/healthz/"
|
|
|
|
|
|
@pytest.mark.smoke
|
|
@pytest.mark.django_db
|
|
def test_healthcheck_returns_200(client):
|
|
"""GET /healthz/ returns 200 with the expected JSON shape in test env."""
|
|
response = client.get("/healthz/")
|
|
assert response.status_code in (200, 503)
|
|
data = response.json()
|
|
assert "status" in data
|
|
assert "checks" in data
|
|
|
|
|
|
@pytest.mark.smoke
|
|
def test_drf_exception_handler_configured(settings):
|
|
"""Custom exception handler must be wired up in REST_FRAMEWORK settings."""
|
|
handler = settings.REST_FRAMEWORK.get("EXCEPTION_HANDLER")
|
|
assert handler == "api.exceptions.custom_exception_handler"
|
|
|
|
|
|
@pytest.mark.smoke
|
|
def test_spectacular_settings_present(settings):
|
|
"""drf-spectacular settings block must exist with correct title."""
|
|
s = settings.SPECTACULAR_SETTINGS
|
|
assert s["TITLE"] == "Trainingssoftware API"
|
|
assert s["VERSION"] == "1.0.0"
|
|
|
|
|
|
@pytest.mark.smoke
|
|
def test_celery_always_eager_in_tests(settings):
|
|
"""Celery must run tasks eagerly (synchronously) during tests."""
|
|
assert settings.CELERY_TASK_ALWAYS_EAGER is True
|
|
|
|
|
|
@pytest.mark.smoke
|
|
def test_domain_app_urls_registered():
|
|
"""Each domain app namespace must have at least the app_name registered."""
|
|
from django.urls import get_resolver
|
|
resolver = get_resolver()
|
|
namespaces = list(resolver.namespace_dict.keys())
|
|
expected_namespaces = [
|
|
"accounts", "courses", "cms", "tracking",
|
|
"quizzes", "training", "certificates", "reports", "notifications",
|
|
]
|
|
missing = [ns for ns in expected_namespaces if ns not in namespaces]
|
|
assert not missing, f"Missing URL namespaces: {missing}"
|