""" Unit tests for AuthService These tests will FAIL until AuthService is implemented """ import pytest from datetime import datetime, timedelta import uuid from services.auth_service import AuthService from models.user import User, UserRole @pytest.mark.asyncio class TestAuthServiceLogin: """Unit tests for AuthService.login()""" async def test_login_success(self, test_db_session, test_admin_user): """Test successful login with valid credentials""" auth_service = AuthService(test_db_session) result = await auth_service.login("admin", "admin123", ip_address="127.0.0.1") assert result is not None assert "access_token" in result assert "refresh_token" in result assert "token_type" in result assert result["token_type"] == "bearer" assert "expires_in" in result assert "user" in result assert result["user"]["username"] == "admin" assert result["user"]["role"] == "administrator" async def test_login_invalid_username(self, test_db_session): """Test login with non-existent username""" auth_service = AuthService(test_db_session) result = await auth_service.login("nonexistent", "somepassword", ip_address="127.0.0.1") assert result is None async def test_login_invalid_password(self, test_db_session, test_admin_user): """Test login with incorrect password""" auth_service = AuthService(test_db_session) result = await auth_service.login("admin", "wrongpassword", ip_address="127.0.0.1") assert result is None async def test_login_operator(self, test_db_session, test_operator_user): """Test successful login for operator role""" auth_service = AuthService(test_db_session) result = await auth_service.login("operator", "operator123", ip_address="127.0.0.1") assert result is not None assert result["user"]["role"] == "operator" async def test_login_viewer(self, test_db_session, test_viewer_user): """Test successful login for viewer role""" auth_service = AuthService(test_db_session) result = await auth_service.login("viewer", "viewer123", ip_address="127.0.0.1") assert result is not None assert result["user"]["role"] == "viewer" @pytest.mark.asyncio class TestAuthServiceLogout: """Unit tests for AuthService.logout()""" async def test_logout_success(self, test_db_session, test_admin_user, auth_token): """Test successful logout""" auth_service = AuthService(test_db_session) # Logout should add token to blacklist result = await auth_service.logout(auth_token, ip_address="127.0.0.1") assert result is True async def test_logout_invalid_token(self, test_db_session): """Test logout with invalid token""" auth_service = AuthService(test_db_session) result = await auth_service.logout("invalid_token", ip_address="127.0.0.1") assert result is False async def test_logout_expired_token(self, test_db_session, expired_token): """Test logout with expired token""" auth_service = AuthService(test_db_session) result = await auth_service.logout(expired_token, ip_address="127.0.0.1") assert result is False @pytest.mark.asyncio class TestAuthServiceValidateToken: """Unit tests for AuthService.validate_token()""" async def test_validate_token_success(self, test_db_session, test_admin_user, auth_token): """Test validation of valid token""" auth_service = AuthService(test_db_session) user = await auth_service.validate_token(auth_token) assert user is not None assert isinstance(user, User) assert user.username == "admin" assert user.role == UserRole.ADMINISTRATOR async def test_validate_token_invalid(self, test_db_session): """Test validation of invalid token""" auth_service = AuthService(test_db_session) user = await auth_service.validate_token("invalid_token") assert user is None async def test_validate_token_expired(self, test_db_session, expired_token): """Test validation of expired token""" auth_service = AuthService(test_db_session) user = await auth_service.validate_token(expired_token) assert user is None async def test_validate_token_blacklisted(self, test_db_session, test_admin_user, auth_token): """Test validation of blacklisted token (after logout)""" auth_service = AuthService(test_db_session) # First logout to blacklist the token await auth_service.logout(auth_token, ip_address="127.0.0.1") # Then try to validate it user = await auth_service.validate_token(auth_token) assert user is None @pytest.mark.asyncio class TestAuthServicePasswordHashing: """Unit tests for password hashing and verification""" async def test_hash_password(self, test_db_session): """Test password hashing""" auth_service = AuthService(test_db_session) plain_password = "mypassword123" hashed = await auth_service.hash_password(plain_password) # Hash should not equal plain text assert hashed != plain_password # Hash should start with bcrypt identifier assert hashed.startswith("$2b$") async def test_verify_password_success(self, test_db_session): """Test successful password verification""" auth_service = AuthService(test_db_session) plain_password = "mypassword123" hashed = await auth_service.hash_password(plain_password) # Verification should succeed result = await auth_service.verify_password(plain_password, hashed) assert result is True async def test_verify_password_failure(self, test_db_session): """Test failed password verification""" auth_service = AuthService(test_db_session) plain_password = "mypassword123" hashed = await auth_service.hash_password(plain_password) # Verification with wrong password should fail result = await auth_service.verify_password("wrongpassword", hashed) assert result is False async def test_hash_password_different_each_time(self, test_db_session): """Test that same password produces different hashes (due to salt)""" auth_service = AuthService(test_db_session) plain_password = "mypassword123" hash1 = await auth_service.hash_password(plain_password) hash2 = await auth_service.hash_password(plain_password) # Hashes should be different (bcrypt uses random salt) assert hash1 != hash2 # But both should verify successfully assert await auth_service.verify_password(plain_password, hash1) assert await auth_service.verify_password(plain_password, hash2) @pytest.mark.asyncio class TestAuthServiceAuditLogging: """Unit tests for audit logging in AuthService""" async def test_login_success_creates_audit_log(self, test_db_session, test_admin_user): """Test that successful login creates audit log entry""" from models.audit_log import AuditLog from sqlalchemy import select auth_service = AuthService(test_db_session) # Perform login await auth_service.login("admin", "admin123", ip_address="192.168.1.100") # Check audit log was created result = await test_db_session.execute( select(AuditLog).where(AuditLog.action == "auth.login") ) audit_logs = result.scalars().all() assert len(audit_logs) >= 1 audit_log = audit_logs[-1] # Get most recent assert audit_log.action == "auth.login" assert audit_log.target == "admin" assert audit_log.outcome == "success" assert audit_log.ip_address == "192.168.1.100" async def test_login_failure_creates_audit_log(self, test_db_session): """Test that failed login creates audit log entry""" from models.audit_log import AuditLog from sqlalchemy import select auth_service = AuthService(test_db_session) # Attempt login with invalid credentials await auth_service.login("admin", "wrongpassword", ip_address="192.168.1.100") # Check audit log was created result = await test_db_session.execute( select(AuditLog).where(AuditLog.action == "auth.login").where(AuditLog.outcome == "failure") ) audit_logs = result.scalars().all() assert len(audit_logs) >= 1 audit_log = audit_logs[-1] assert audit_log.action == "auth.login" assert audit_log.target == "admin" assert audit_log.outcome == "failure" assert audit_log.ip_address == "192.168.1.100" async def test_logout_creates_audit_log(self, test_db_session, test_admin_user, auth_token): """Test that logout creates audit log entry""" from models.audit_log import AuditLog from sqlalchemy import select auth_service = AuthService(test_db_session) # Perform logout await auth_service.logout(auth_token, ip_address="192.168.1.100") # Check audit log was created result = await test_db_session.execute( select(AuditLog).where(AuditLog.action == "auth.logout") ) audit_logs = result.scalars().all() assert len(audit_logs) >= 1 audit_log = audit_logs[-1] assert audit_log.action == "auth.logout" assert audit_log.outcome == "success" assert audit_log.ip_address == "192.168.1.100"