90 lines
2.3 KiB
Python
90 lines
2.3 KiB
Python
"""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
|