yksa-django-kit/tests/test_auth.py
2026-08-17 22:50:10 +08:00

85 lines
2.7 KiB
Python

from __future__ import annotations
import pytest
from django.test import RequestFactory
from yksa_kit.auth import (
extract_token,
request_can_ingest,
request_has_valid_token,
resolve_token,
store_session_token,
)
from yksa_kit.models import ApiToken, TokenKind
@pytest.fixture
def rf() -> RequestFactory:
return RequestFactory()
@pytest.mark.django_db
def test_returns_none_when_no_credentials(rf):
assert extract_token(rf.get("/api/v1/things/")) is None
assert request_has_valid_token(rf.get("/api/v1/things/")) is False
@pytest.mark.django_db
def test_authorization_bearer_header(rf):
token = ApiToken.objects.create(name="ci-job")
resolved = extract_token(rf.get("/", HTTP_AUTHORIZATION=f"Bearer {token.token}"))
assert resolved is not None and resolved.pk == token.pk
@pytest.mark.django_db
def test_authorization_raw_header_accepted(rf):
"""Plain header value without the 'Bearer ' prefix is tolerated."""
token = ApiToken.objects.create(name="legacy-client")
assert extract_token(rf.get("/", HTTP_AUTHORIZATION=token.token)) is not None
@pytest.mark.django_db
def test_query_string_token(rf):
token = ApiToken.objects.create(name="browser")
assert extract_token(rf.get("/", {"api_token": token.token})) is not None
@pytest.mark.django_db
def test_revoked_token_is_anonymous(rf):
token = ApiToken.objects.create(name="revoked", is_active=False)
assert extract_token(rf.get("/", HTTP_AUTHORIZATION=f"Bearer {token.token}")) is None
@pytest.mark.django_db
def test_unknown_token_is_anonymous(rf):
assert extract_token(rf.get("/", HTTP_AUTHORIZATION="Bearer nope")) is None
@pytest.mark.django_db
def test_extract_token_updates_last_used(rf):
token = ApiToken.objects.create(name="track-me")
assert token.last_used_at is None
extract_token(rf.get("/", HTTP_AUTHORIZATION=f"Bearer {token.token}"))
token.refresh_from_db()
assert token.last_used_at is not None
@pytest.mark.django_db
def test_only_ingest_tokens_may_push(rf):
read = ApiToken.objects.create(name="reader")
ingest = ApiToken.objects.create(name="station", kind=TokenKind.INGEST)
assert request_can_ingest(rf.get("/", {"api_token": read.token})) is False
assert request_can_ingest(rf.get("/", {"api_token": ingest.token})) is True
@pytest.mark.django_db
def test_session_token_is_ignored_by_the_api_path(rf):
"""The session carrier is for browser pages only; an API controller asking
request_has_valid_token() must not be authenticated by a stale session."""
token = ApiToken.objects.create(name="browser")
request = rf.get("/")
request.session = {}
store_session_token(request, token)
assert request_has_valid_token(request) is False
assert resolve_token(request) is not None