114 lines
4.0 KiB
Python
114 lines
4.0 KiB
Python
"""Unit tests for security module"""
|
|
|
|
import pytest
|
|
from datetime import datetime, timedelta
|
|
from jose import jwt
|
|
from unittest.mock import MagicMock
|
|
|
|
from app.core.security import (
|
|
create_access_token,
|
|
create_refresh_token,
|
|
verify_password,
|
|
get_password_hash,
|
|
)
|
|
from app.core.config import settings
|
|
|
|
|
|
class TestPasswordHashing:
|
|
"""Tests for password hashing functions"""
|
|
|
|
def test_hash_password(self):
|
|
"""Test password hashing"""
|
|
password = "test_password_123"
|
|
hashed = get_password_hash(password)
|
|
assert hashed != password
|
|
assert len(hashed) > 0
|
|
|
|
def test_verify_correct_password(self):
|
|
"""Test verification of correct password"""
|
|
password = "test_password_123"
|
|
hashed = get_password_hash(password)
|
|
assert verify_password(password, hashed) is True
|
|
|
|
def test_verify_incorrect_password(self):
|
|
"""Test verification of incorrect password"""
|
|
password = "test_password_123"
|
|
hashed = get_password_hash(password)
|
|
assert verify_password("wrong_password", hashed) is False
|
|
|
|
def test_hash_is_unique(self):
|
|
"""Test that hashes are unique for same password"""
|
|
password = "test_password_123"
|
|
hash1 = get_password_hash(password)
|
|
hash2 = get_password_hash(password)
|
|
assert hash1 != hash2 # bcrypt adds salt
|
|
|
|
|
|
class TestTokenCreation:
|
|
"""Tests for token creation functions"""
|
|
|
|
def test_create_access_token(self):
|
|
"""Test access token creation"""
|
|
data = {"sub": "123", "username": "testuser"}
|
|
token = create_access_token(data)
|
|
assert token is not None
|
|
assert len(token) > 0
|
|
|
|
payload = jwt.decode(token, settings.SECRET_KEY, algorithms=[settings.ALGORITHM])
|
|
assert payload["sub"] == "123"
|
|
assert payload["username"] == "testuser"
|
|
assert payload["type"] == "access"
|
|
|
|
def test_create_refresh_token(self):
|
|
"""Test refresh token creation"""
|
|
data = {"sub": "123"}
|
|
token = create_refresh_token(data)
|
|
assert token is not None
|
|
|
|
payload = jwt.decode(token, settings.SECRET_KEY, algorithms=[settings.ALGORITHM])
|
|
assert payload["sub"] == "123"
|
|
assert payload["type"] == "refresh"
|
|
|
|
def test_access_token_expiration(self):
|
|
"""Test access token has correct expiration"""
|
|
data = {"sub": "123"}
|
|
token = create_access_token(data)
|
|
payload = jwt.decode(token, settings.SECRET_KEY, algorithms=[settings.ALGORITHM])
|
|
exp_timestamp = payload["exp"]
|
|
# Token should expire in approximately 15 minutes (accounting for timezone)
|
|
expected_minutes = settings.ACCESS_TOKEN_EXPIRE_MINUTES
|
|
# The timestamp is in seconds since epoch
|
|
import time
|
|
|
|
now_timestamp = time.time()
|
|
minutes_diff = (exp_timestamp - now_timestamp) / 60
|
|
assert expected_minutes - 1 < minutes_diff < expected_minutes + 1
|
|
|
|
def test_refresh_token_expiration(self):
|
|
"""Test refresh token has correct expiration"""
|
|
data = {"sub": "123"}
|
|
token = create_refresh_token(data)
|
|
payload = jwt.decode(token, settings.SECRET_KEY, algorithms=[settings.ALGORITHM])
|
|
exp = datetime.fromtimestamp(payload["exp"])
|
|
now = datetime.utcnow()
|
|
# Token should expire in approximately 7 days (with some tolerance)
|
|
delta = exp - now
|
|
assert delta.days >= 6 # At least 6 days
|
|
assert delta.days <= 8 # Less than 8 days
|
|
|
|
|
|
class TestJWTSecurity:
|
|
"""Tests for JWT security features"""
|
|
|
|
def test_invalid_token_raises_error(self):
|
|
"""Test that invalid token raises JWTError"""
|
|
with pytest.raises(jwt.JWTError):
|
|
jwt.decode("invalid_token", settings.SECRET_KEY, algorithms=[settings.ALGORITHM])
|
|
|
|
def test_token_with_wrong_secret_raises_error(self):
|
|
"""Test that token with wrong secret raises error"""
|
|
data = {"sub": "123"}
|
|
token = create_access_token(data)
|
|
with pytest.raises(jwt.JWTError):
|
|
jwt.decode(token, "wrong_secret", algorithms=[settings.ALGORITHM])
|