feat: add Django project scaffold, configuration and container

Environment-variable configuration that fails fast, database-only health
check, JSON request logs with a request id, security headers with a strict
CSP, and a multi-stage non-root Docker image with compose file. Ticket 1.1.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Sk72AxLwQnYttF6VFSxQLt
This commit is contained in:
Ebbe Baß
2026-10-15 13:44:07 +02:00
co-authored by Claude Sonnet 5.5
parent 24de5878ed
commit f8b0584687
31 changed files with 1134 additions and 2 deletions
+39
View File
@@ -0,0 +1,39 @@
import pytest
@pytest.mark.django_db
def test_security_headers_present(client):
response = client.get("/healthz")
csp = response["Content-Security-Policy"]
assert "script-src 'self'" in csp
assert "default-src 'self'" in csp
assert "frame-ancestors 'none'" in csp
assert "unsafe-inline" not in csp
assert "http" not in csp
assert response["X-Content-Type-Options"] == "nosniff"
assert response["X-Frame-Options"] == "DENY"
assert response["Referrer-Policy"] == "same-origin"
assert response["X-Request-ID"]
def test_headers_also_on_error_responses(client):
response = client.get("/missing")
assert response.status_code == 404
assert "Content-Security-Policy" in response
assert response["X-Request-ID"]
def test_valid_incoming_request_id_is_kept(client):
response = client.get("/missing", headers={"X-Request-ID": "abc12345-req"})
assert response["X-Request-ID"] == "abc12345-req"
def test_invalid_incoming_request_id_is_replaced(client):
response = client.get("/missing", headers={"X-Request-ID": "bad id\twith junk"})
assert response["X-Request-ID"] != "bad id\twith junk"
def test_csp_on_bad_host_response(client):
response = client.get("/healthz", HTTP_HOST="unlisted.example")
assert response.status_code == 400
assert "script-src 'self'" in response["Content-Security-Policy"]