Files
2026-06-03 10:40:00 +03:00

79 lines
2.6 KiB
Python

"""Unit tests for the security adapters (no DB, no network)."""
import datetime as dt
import uuid
import jwt
import pytest
from app.core.config import Settings
from app.core.security import Argon2PasswordHasher, JwtTokenService
from app.domain.errors import AuthenticationError
from app.domain.tokens import TokenType
def _settings(**overrides: object) -> Settings:
base: dict[str, object] = {"jwt_secret": "unit-test-secret", "access_token_ttl_seconds": 900}
base.update(overrides)
return Settings(**base) # type: ignore[arg-type]
def test_password_hash_roundtrip() -> None:
hasher = Argon2PasswordHasher()
hashed = hasher.hash("correct horse battery staple")
assert hashed != "correct horse battery staple"
valid, updated = hasher.verify_and_update("correct horse battery staple", hashed)
assert valid is True
assert updated is None # fresh hash, no rehash needed
wrong, _ = hasher.verify_and_update("wrong password", hashed)
assert wrong is False
def test_jwt_issue_and_decode_roundtrip() -> None:
svc = JwtTokenService(_settings())
subject = uuid.uuid4()
issued = svc.issue(subject=subject, token_type=TokenType.ACCESS)
claims = svc.decode(issued.encoded)
assert claims.subject == subject
assert claims.token_type is TokenType.ACCESS
assert claims.jti == issued.jti
def test_jwt_rejects_tampered_token() -> None:
svc = JwtTokenService(_settings())
issued = svc.issue(subject=uuid.uuid4(), token_type=TokenType.ACCESS)
tampered = issued.encoded[:-2] + ("aa" if issued.encoded[-2:] != "aa" else "bb")
with pytest.raises(AuthenticationError):
svc.decode(tampered)
def test_jwt_rejects_wrong_secret() -> None:
issuer = JwtTokenService(_settings(jwt_secret="secret-a"))
verifier = JwtTokenService(_settings(jwt_secret="secret-b"))
issued = issuer.issue(subject=uuid.uuid4(), token_type=TokenType.ACCESS)
with pytest.raises(AuthenticationError):
verifier.decode(issued.encoded)
def test_jwt_rejects_expired_token() -> None:
settings = _settings()
secret = settings.jwt_secret.get_secret_value()
expired = jwt.encode(
{
"sub": str(uuid.uuid4()),
"type": "access",
"jti": str(uuid.uuid4()),
"iat": int((dt.datetime.now(dt.UTC) - dt.timedelta(hours=2)).timestamp()),
"exp": int((dt.datetime.now(dt.UTC) - dt.timedelta(hours=1)).timestamp()),
},
secret,
algorithm=settings.jwt_algorithm,
)
with pytest.raises(AuthenticationError):
JwtTokenService(settings).decode(expired)