"""Tests for password hashing service - T13. Tests for bcrypt password hashing with strength validation. """ import pytest pytestmark = [pytest.mark.unit, pytest.mark.security, pytest.mark.slow] class TestPasswordHashing: """Test suite for password hashing service.""" def test_hash_password_returns_string(self): """Test that hash_password returns a string.""" # Arrange from src.openrouter_monitor.services.password import hash_password password = "SecurePass123!" # Act hashed = hash_password(password) # Assert assert isinstance(hashed, str) assert len(hashed) > 0 def test_hash_password_generates_different_hash_each_time(self): """Test that hashing same password produces different hashes (due to salt).""" # Arrange from src.openrouter_monitor.services.password import hash_password password = "SamePassword123!" # Act hash1 = hash_password(password) hash2 = hash_password(password) # Assert assert hash1 != hash2 assert hash1.startswith("$2b$") assert hash2.startswith("$2b$") def test_verify_password_correct_returns_true(self): """Test that verify_password returns True for correct password.""" # Arrange from src.openrouter_monitor.services.password import hash_password, verify_password password = "MySecurePass123!" hashed = hash_password(password) # Act result = verify_password(password, hashed) # Assert assert result is True def test_verify_password_incorrect_returns_false(self): """Test that verify_password returns False for incorrect password.""" # Arrange from src.openrouter_monitor.services.password import hash_password, verify_password password = "CorrectPass123!" wrong_password = "WrongPass123!" hashed = hash_password(password) # Act result = verify_password(wrong_password, hashed) # Assert assert result is False def test_verify_password_with_different_hash_fails(self): """Test that verify_password fails with a hash from different password.""" # Arrange from src.openrouter_monitor.services.password import hash_password, verify_password password1 = "PasswordOne123!" password2 = "PasswordTwo123!" hashed1 = hash_password(password1) # Act result = verify_password(password2, hashed1) # Assert assert result is False class TestPasswordStrengthValidation: """Test suite for password strength validation.""" def test_validate_password_strong_returns_true(self): """Test that strong password passes validation.""" # Arrange from src.openrouter_monitor.services.password import validate_password_strength passwords = [ "SecurePass123!", "MyP@ssw0rd2024", "C0mpl3x!Pass#", "Valid-Password-123!", ] # Act & Assert for password in passwords: assert validate_password_strength(password) is True, f"Failed for: {password}" def test_validate_password_too_short_returns_false(self): """Test that password less than 12 chars fails validation.""" # Arrange from src.openrouter_monitor.services.password import validate_password_strength passwords = [ "Short1!", "Abc123!", "NoSpecial1", "OnlyLower!", ] # Act & Assert for password in passwords: assert validate_password_strength(password) is False, f"Should fail for: {password}" def test_validate_password_no_uppercase_returns_false(self): """Test that password without uppercase fails validation.""" # Arrange from src.openrouter_monitor.services.password import validate_password_strength password = "lowercase123!" # Act result = validate_password_strength(password) # Assert assert result is False def test_validate_password_no_lowercase_returns_false(self): """Test that password without lowercase fails validation.""" # Arrange from src.openrouter_monitor.services.password import validate_password_strength password = "UPPERCASE123!" # Act result = validate_password_strength(password) # Assert assert result is False def test_validate_password_no_digit_returns_false(self): """Test that password without digit fails validation.""" # Arrange from src.openrouter_monitor.services.password import validate_password_strength password = "NoDigitsHere!" # Act result = validate_password_strength(password) # Assert assert result is False def test_validate_password_no_special_returns_false(self): """Test that password without special char fails validation.""" # Arrange from src.openrouter_monitor.services.password import validate_password_strength password = "NoSpecialChar1" # Act result = validate_password_strength(password) # Assert assert result is False def test_validate_password_only_special_chars_returns_false(self): """Test that password with only special chars fails validation.""" # Arrange from src.openrouter_monitor.services.password import validate_password_strength password = "!@#$%^&*()_+" # Act result = validate_password_strength(password) # Assert assert result is False def test_validate_password_empty_returns_false(self): """Test that empty password fails validation.""" # Arrange from src.openrouter_monitor.services.password import validate_password_strength password = "" # Act result = validate_password_strength(password) # Assert assert result is False def test_validate_password_unicode_handled_correctly(self): """Test that unicode password is handled correctly.""" # Arrange from src.openrouter_monitor.services.password import validate_password_strength password = "日本語パスワード123!" # Act result = validate_password_strength(password) # Assert - Unicode chars are not special chars in regex sense # but the password has uppercase/lowercase (in unicode), digits, and special # This depends on implementation, but should not crash assert isinstance(result, bool) def test_hash_and_verify_integration(self): """Test full hash and verify workflow.""" # Arrange from src.openrouter_monitor.services.password import ( hash_password, verify_password, validate_password_strength, ) password = "Str0ng!Passw0rd" # Act & Assert assert validate_password_strength(password) is True hashed = hash_password(password) assert verify_password(password, hashed) is True assert verify_password("WrongPass", hashed) is False class TestPasswordTypeValidation: """Test suite for type validation in password functions.""" def test_hash_password_non_string_raises_type_error(self): """Test that hash_password with non-string raises TypeError.""" # Arrange from src.openrouter_monitor.services.password import hash_password # Act & Assert with pytest.raises(TypeError, match="password must be a string"): hash_password(12345) def test_verify_password_non_string_plain_raises_type_error(self): """Test that verify_password with non-string plain raises TypeError.""" # Arrange from src.openrouter_monitor.services.password import verify_password # Act & Assert with pytest.raises(TypeError, match="plain_password must be a string"): verify_password(12345, "hashed_password") def test_verify_password_non_string_hash_raises_type_error(self): """Test that verify_password with non-string hash raises TypeError.""" # Arrange from src.openrouter_monitor.services.password import verify_password # Act & Assert with pytest.raises(TypeError, match="hashed_password must be a string"): verify_password("plain_password", 12345) def test_validate_password_strength_non_string_returns_false(self): """Test that validate_password_strength with non-string returns False.""" # Arrange from src.openrouter_monitor.services.password import validate_password_strength # Act & Assert assert validate_password_strength(12345) is False assert validate_password_strength(None) is False assert validate_password_strength([]) is False