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
60 lines
2.2 KiB
Python
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"]
|