Files
Ebbe BaßandClaude Sonnet 5.5 73505ac717 feat: add audit, throttle and session foundations
Audit entries written only inside the caller's transaction, database
throttle counters that refuse to run inside a transaction that could roll
back, and a database session store with the user key in its own column,
end_sessions and idle and absolute limits. Ticket 1.3.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Sk72AxLwQnYttF6VFSxQLt
2026-10-15 15:43:43 +02:00

60 lines
2.2 KiB
Python

import pytest
from django.db import transaction
from portal.audit import services
from portal.audit.models import AuditEntry
@pytest.mark.django_db(transaction=True)
def test_record_rolls_back_with_the_change():
with pytest.raises(RuntimeError), transaction.atomic():
services.record("Alice", "profile.update", target="alice", ip="10.0.0.1")
raise RuntimeError("change failed")
assert AuditEntry.objects.count() == 0
@pytest.mark.django_db(transaction=True)
def test_record_persists_on_commit_and_lowercases_actor():
with transaction.atomic():
services.record("Alice", "profile.update", target="alice", ip="10.0.0.1", detail={"a": 1})
entry = AuditEntry.objects.get()
assert (entry.actor, entry.action, entry.ip, entry.detail) == (
"alice",
"profile.update",
"10.0.0.1",
{"a": 1},
)
@pytest.mark.django_db(transaction=True)
def test_record_outside_transaction_raises_and_writes_nothing():
with pytest.raises(services.AuditOutsideTransaction):
services.record("alice", "x")
assert AuditEntry.objects.count() == 0
@pytest.mark.django_db(transaction=True)
@pytest.mark.parametrize("key", ["password", "New_Token", "client_secret", "pwd", "oldPassword"])
def test_secret_keys_are_refused(key):
with pytest.raises(ValueError), transaction.atomic():
services.record("alice", "x", detail={"ok": 1, "nested": {key: "v"}})
with pytest.raises(ValueError):
services.record_failure("alice", "x", detail={key: "v"})
assert AuditEntry.objects.count() == 0
@pytest.mark.django_db(transaction=True)
def test_record_failure_inside_transaction_raises_and_writes_nothing():
with pytest.raises(services.AuditInsideTransaction), transaction.atomic():
services.record_failure("alice", "x.failed")
assert AuditEntry.objects.count() == 0
@pytest.mark.django_db(transaction=True)
def test_record_failure_persists_after_rollback():
with pytest.raises(RuntimeError), transaction.atomic():
services.record("alice", "x")
raise RuntimeError("boom")
services.record_failure("alice", "x.failed", detail={"reason": "ad"})
assert list(AuditEntry.objects.values_list("action", flat=True)) == ["x.failed"]