"""Tests for `app.core.security` — JWT and password hashing.""" from __future__ import annotations import time import uuid import pytest from jose import JWTError from app.core.security import ( create_access_token, create_refresh_token, decode_token, hash_password, validate_password_strength, verify_password, ) def test_password_hash_and_verify(): plain = "S3cretPass!" hashed = hash_password(plain) assert hashed != plain assert verify_password(plain, hashed) is True assert verify_password("wrong", hashed) is False def test_password_hash_is_bcrypt(): hashed = hash_password("S3cretPass!") # bcrypt hashes start with $2b$ or $2a$ assert hashed.startswith("$2") def test_validate_password_strength_strong(): errors = validate_password_strength("GoodPass123") assert errors == [] def test_validate_password_strength_short(): errors = validate_password_strength("abc12") assert any("8 characters" in e for e in errors) def test_validate_password_strength_no_digit(): errors = validate_password_strength("NoDigitsHere") assert any("digit" in e for e in errors) def test_validate_password_strength_no_letter(): errors = validate_password_strength("12345678") assert any("letter" in e for e in errors) def test_validate_password_strength_blacklist(): errors = validate_password_strength("password1") assert any("common" in e for e in errors) def test_create_and_decode_access_token(): uid = uuid.uuid4() token = create_access_token(uid, extra_claims={"is_admin": True}) payload = decode_token(token) assert payload["sub"] == str(uid) assert payload["type"] == "access" assert payload["is_admin"] is True assert "iat" in payload and "exp" in payload def test_create_and_decode_refresh_token(): uid = uuid.uuid4() token = create_refresh_token(uid) payload = decode_token(token) assert payload["sub"] == str(uid) assert payload["type"] == "refresh" def test_decode_invalid_token(): with pytest.raises(JWTError): decode_token("not-a-valid-jwt") def test_token_expiry_in_future(): uid = uuid.uuid4() token = create_access_token(uid, expires_in_minutes=60) payload = decode_token(token) now = int(time.time()) assert payload["exp"] > now assert payload["iat"] <= now