Files
ping-mee-self-service-platform/portal/tests/test_sessions.py
T
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

124 lines
4.3 KiB
Python

import time
from datetime import timedelta
import pytest
from django.conf import settings
from django.test import Client, RequestFactory
from django.utils import timezone
from portal.accounts.middleware import AbsoluteSessionLimitMiddleware
from portal.accounts.models import UserSession
from portal.accounts.session_store import CREATED_AT, SessionStore
from portal.accounts.sessions import SessionUser, current_user, end_sessions, start_session
pytestmark = pytest.mark.django_db
def make_session(sam):
request = RequestFactory().get("/")
request.session = SessionStore()
start_session(request, sam)
request.session.save()
return request
def test_username_column_is_lowercase_key_and_sam_keeps_case():
request = make_session("Alice")
row = UserSession.objects.get(session_key=request.session.session_key)
assert row.username == "alice"
assert current_user(request) == SessionUser(key="alice", sam="Alice")
def test_start_session_rotates_key_and_drops_old_data():
request = RequestFactory().get("/")
request.session = SessionStore()
request.session["stale"] = 1
request.session.save()
old_key = request.session.session_key
start_session(request, "alice")
request.session.save()
assert request.session.session_key != old_key
assert "stale" not in request.session
assert not UserSession.objects.filter(session_key=old_key).exists()
def test_end_sessions_only_target_user():
for _ in range(3):
make_session("a")
for _ in range(2):
make_session("b")
assert end_sessions("a") == 3
assert UserSession.objects.filter(username="a").count() == 0
assert UserSession.objects.filter(username="b").count() == 2
def test_end_sessions_except_session_key_and_unknown_user():
keep = make_session("a").session.session_key
make_session("a")
assert end_sessions(SessionUser("a", "A"), except_session_key=keep) == 1
assert list(UserSession.objects.values_list("session_key", flat=True)) == [keep]
assert end_sessions("nobody") == 0
def test_idle_limit_expired_session_is_gone():
request = make_session("a")
UserSession.objects.update(expire_date=timezone.now() - timedelta(seconds=1))
assert SessionStore(request.session.session_key).get(CREATED_AT) is None
def test_settings_defaults():
assert settings.SESSION_COOKIE_AGE == 3600
assert settings.SESSION_ABSOLUTE_SECONDS == 14400
assert settings.SESSION_SAVE_EVERY_REQUEST is True
def _client_with_session(created_at):
store = SessionStore()
request = RequestFactory().get("/")
request.session = store
start_session(request, "alice")
store[CREATED_AT] = created_at
store.save()
client = Client()
client.cookies[settings.SESSION_COOKIE_NAME] = store.session_key
return client, store.session_key
def test_absolute_limit_removes_session_and_clears_cookie():
assert any(m.endswith("AbsoluteSessionLimitMiddleware") for m in settings.MIDDLEWARE)
client, key = _client_with_session(time.time() - settings.SESSION_ABSOLUTE_SECONDS - 5)
response = client.get("/healthz")
assert not UserSession.objects.filter(session_key=key).exists()
assert response.cookies[settings.SESSION_COOKIE_NAME].value == ""
def test_session_within_limits_survives_a_request():
client, key = _client_with_session(time.time() - 60)
client.get("/healthz")
assert UserSession.objects.filter(session_key=key).exists()
def test_middleware_callable_directly():
request = make_session("alice")
AbsoluteSessionLimitMiddleware(lambda r: None)(request)
assert current_user(request) is not None
def test_request_renews_expire_date_to_one_hour_ahead():
client, key = _client_with_session(time.time() - 60)
UserSession.objects.filter(session_key=key).update(
expire_date=timezone.now() + timedelta(minutes=5)
)
client.get("/healthz")
remaining = UserSession.objects.get(session_key=key).expire_date - timezone.now()
assert timedelta(minutes=59) < remaining <= timedelta(hours=1)
def test_request_with_idle_expired_session_has_no_session_and_clears_cookie():
client, key = _client_with_session(time.time() - 60)
UserSession.objects.filter(session_key=key).update(
expire_date=timezone.now() - timedelta(seconds=1)
)
response = client.get("/healthz")
assert response.cookies[settings.SESSION_COOKIE_NAME].value == ""