video-accessibility/backend/tests/unit/test_security.py
2025-08-24 16:28:33 -05:00

233 lines
No EOL
9.3 KiB
Python

from datetime import datetime, timedelta
from unittest.mock import patch
import pytest
from fastapi import HTTPException
from jose import jwt
from app.core.security import (
create_access_token,
create_refresh_token,
decode_token,
get_password_hash,
verify_password,
)
class TestJWTTokens:
"""Test JWT token creation and validation"""
def test_create_access_token_default_expiry(self):
"""Test creating access token with default expiry"""
with patch('app.core.security.settings') as mock_settings:
mock_settings.jwt_secret = "test_secret"
mock_settings.jwt_alg = "HS256"
mock_settings.jwt_access_ttl_min = 15
token = create_access_token("user123")
# Decode to verify contents
payload = jwt.decode(token, "test_secret", algorithms=["HS256"])
assert payload["sub"] == "user123"
assert "exp" in payload
assert "type" not in payload # Access tokens don't have type
def test_create_access_token_custom_expiry(self):
"""Test creating access token with custom expiry"""
with patch('app.core.security.settings') as mock_settings:
mock_settings.jwt_secret = "test_secret"
mock_settings.jwt_alg = "HS256"
custom_delta = timedelta(minutes=30)
token = create_access_token("user123", expires_delta=custom_delta)
payload = jwt.decode(token, "test_secret", algorithms=["HS256"])
# Check expiry is approximately 30 minutes from now
exp_time = datetime.utcfromtimestamp(payload["exp"])
expected_exp = datetime.utcnow() + custom_delta
assert abs((exp_time - expected_exp).total_seconds()) < 5 # 5 second tolerance
def test_create_refresh_token_default_expiry(self):
"""Test creating refresh token with default expiry"""
with patch('app.core.security.settings') as mock_settings:
mock_settings.jwt_secret = "test_secret"
mock_settings.jwt_alg = "HS256"
mock_settings.jwt_refresh_ttl_days = 7
token = create_refresh_token("user123")
payload = jwt.decode(token, "test_secret", algorithms=["HS256"])
assert payload["sub"] == "user123"
assert payload["type"] == "refresh"
assert "exp" in payload
def test_create_refresh_token_custom_expiry(self):
"""Test creating refresh token with custom expiry"""
with patch('app.core.security.settings') as mock_settings:
mock_settings.jwt_secret = "test_secret"
mock_settings.jwt_alg = "HS256"
custom_delta = timedelta(days=14)
token = create_refresh_token("user123", expires_delta=custom_delta)
payload = jwt.decode(token, "test_secret", algorithms=["HS256"])
assert payload["type"] == "refresh"
# Check expiry is approximately 14 days from now
exp_time = datetime.utcfromtimestamp(payload["exp"])
expected_exp = datetime.utcnow() + custom_delta
assert abs((exp_time - expected_exp).total_seconds()) < 5
def test_decode_token_valid(self):
"""Test decoding valid JWT token"""
with patch('app.core.security.settings') as mock_settings:
mock_settings.jwt_secret = "test_secret"
mock_settings.jwt_alg = "HS256"
# Create a token first
original_payload = {"sub": "user123", "exp": datetime.utcnow() + timedelta(minutes=15)}
token = jwt.encode(original_payload, "test_secret", algorithm="HS256")
# Decode it
payload = decode_token(token)
assert payload["sub"] == "user123"
def test_decode_token_invalid(self):
"""Test decoding invalid JWT token"""
with patch('app.core.security.settings') as mock_settings:
mock_settings.jwt_secret = "test_secret"
mock_settings.jwt_alg = "HS256"
with pytest.raises(HTTPException) as exc_info:
decode_token("invalid.jwt.token")
assert exc_info.value.status_code == 401
assert "Could not validate credentials" in str(exc_info.value.detail)
def test_decode_token_expired(self):
"""Test decoding expired JWT token"""
with patch('app.core.security.settings') as mock_settings:
mock_settings.jwt_secret = "test_secret"
mock_settings.jwt_alg = "HS256"
# Create expired token
expired_payload = {"sub": "user123", "exp": datetime.utcnow() - timedelta(minutes=1)}
expired_token = jwt.encode(expired_payload, "test_secret", algorithm="HS256")
with pytest.raises(HTTPException) as exc_info:
decode_token(expired_token)
assert exc_info.value.status_code == 401
def test_decode_token_wrong_secret(self):
"""Test decoding token with wrong secret"""
with patch('app.core.security.settings') as mock_settings:
mock_settings.jwt_secret = "wrong_secret"
mock_settings.jwt_alg = "HS256"
# Create token with different secret
payload = {"sub": "user123", "exp": datetime.utcnow() + timedelta(minutes=15)}
token = jwt.encode(payload, "correct_secret", algorithm="HS256")
with pytest.raises(HTTPException) as exc_info:
decode_token(token)
assert exc_info.value.status_code == 401
class TestPasswordHashing:
"""Test password hashing and verification"""
def test_get_password_hash(self):
"""Test password hashing"""
password = "test_password_123"
hashed = get_password_hash(password)
# Hash should be different from original password
assert hashed != password
# Hash should start with bcrypt identifier
assert hashed.startswith("$2b$")
def test_verify_password_correct(self):
"""Test password verification with correct password"""
password = "test_password_123"
hashed = get_password_hash(password)
assert verify_password(password, hashed) is True
def test_verify_password_incorrect(self):
"""Test password verification with incorrect password"""
password = "test_password_123"
wrong_password = "wrong_password"
hashed = get_password_hash(password)
assert verify_password(wrong_password, hashed) is False
def test_password_hash_uniqueness(self):
"""Test that same password generates different hashes (due to salt)"""
password = "test_password_123"
hash1 = get_password_hash(password)
hash2 = get_password_hash(password)
# Hashes should be different due to salt
assert hash1 != hash2
# But both should verify correctly
assert verify_password(password, hash1) is True
assert verify_password(password, hash2) is True
def test_verify_empty_password(self):
"""Test password verification with empty password"""
hashed = get_password_hash("real_password")
assert verify_password("", hashed) is False
def test_verify_empty_hash(self):
"""Test password verification with empty hash"""
# This should handle gracefully without crashing
assert verify_password("test_password", "") is False
class TestTokenSecurity:
"""Test token security properties"""
def test_token_contains_no_sensitive_data(self):
"""Test that tokens don't contain sensitive information"""
with patch('app.core.security.settings') as mock_settings:
mock_settings.jwt_secret = "test_secret"
mock_settings.jwt_alg = "HS256"
token = create_access_token("user123")
payload = jwt.decode(token, "test_secret", algorithms=["HS256"])
# Token should only contain safe fields
assert "password" not in payload
assert "hashed_password" not in payload
assert "email" not in payload
assert payload["sub"] == "user123"
def test_refresh_token_has_type_field(self):
"""Test that refresh tokens have type field"""
with patch('app.core.security.settings') as mock_settings:
mock_settings.jwt_secret = "test_secret"
mock_settings.jwt_alg = "HS256"
mock_settings.jwt_refresh_ttl_days = 7
token = create_refresh_token("user123")
payload = jwt.decode(token, "test_secret", algorithms=["HS256"])
assert payload["type"] == "refresh"
def test_access_token_has_no_type_field(self):
"""Test that access tokens don't have type field"""
with patch('app.core.security.settings') as mock_settings:
mock_settings.jwt_secret = "test_secret"
mock_settings.jwt_alg = "HS256"
mock_settings.jwt_access_ttl_min = 15
token = create_access_token("user123")
payload = jwt.decode(token, "test_secret", algorithms=["HS256"])
assert "type" not in payload