""" Tests for authentication system. Tests login, logout, session management, and API authentication. """ import pytest from flask import url_for from web.auth.models import User from web.utils.settings import PasswordManager, SettingsManager class TestUserModel: """Tests for User model.""" def test_user_get_valid_id(self, db): """Test getting user with valid ID.""" user = User.get('1', db) assert user is not None assert user.id == '1' def test_user_get_invalid_id(self, db): """Test getting user with invalid ID.""" user = User.get('invalid', db) assert user is None def test_user_properties(self): """Test user properties.""" user = User('1') assert user.is_authenticated is True assert user.is_active is True assert user.is_anonymous is False assert user.get_id() == '1' def test_user_authenticate_success(self, db, app_password): """Test successful authentication.""" user = User.authenticate(app_password, db) assert user is not None assert user.id == '1' def test_user_authenticate_failure(self, db): """Test failed authentication with wrong password.""" user = User.authenticate('wrongpassword', db) assert user is None def test_user_has_password_set(self, db, app_password): """Test checking if password is set.""" # Password is set in fixture assert User.has_password_set(db) is True def test_user_has_password_not_set(self, db_no_password): """Test checking if password is not set.""" assert User.has_password_set(db_no_password) is False class TestAuthRoutes: """Tests for authentication routes.""" def test_login_page_renders(self, client): """Test that login page renders correctly.""" response = client.get('/auth/login') assert response.status_code == 200 # Note: This will fail until templates are created # assert b'login' in response.data.lower() def test_login_success(self, client, app_password): """Test successful login.""" response = client.post('/auth/login', data={ 'password': app_password }, follow_redirects=False) # Should redirect to dashboard (or main.dashboard) assert response.status_code == 302 def test_login_failure(self, client): """Test failed login with wrong password.""" response = client.post('/auth/login', data={ 'password': 'wrongpassword' }, follow_redirects=True) # Should stay on login page assert response.status_code == 200 def test_login_redirect_when_authenticated(self, authenticated_client): """Test that login page redirects when already logged in.""" response = authenticated_client.get('/auth/login', follow_redirects=False) # Should redirect to dashboard assert response.status_code == 302 def test_logout(self, authenticated_client): """Test logout functionality.""" response = authenticated_client.get('/auth/logout', follow_redirects=False) # Should redirect to login page assert response.status_code == 302 assert '/auth/login' in response.location def test_logout_when_not_authenticated(self, client): """Test logout when not authenticated.""" response = client.get('/auth/logout', follow_redirects=False) # Should redirect to login page anyway assert response.status_code == 302 def test_setup_page_renders_when_no_password(self, client_no_password): """Test that setup page renders when no password is set.""" response = client_no_password.get('/auth/setup') assert response.status_code == 200 def test_setup_redirects_when_password_set(self, client): """Test that setup page redirects when password already set.""" response = client.get('/auth/setup', follow_redirects=False) assert response.status_code == 302 assert '/auth/login' in response.location def test_setup_password_success(self, client_no_password): """Test setting password via setup page.""" response = client_no_password.post('/auth/setup', data={ 'password': 'newpassword123', 'confirm_password': 'newpassword123' }, follow_redirects=False) # Should redirect to login assert response.status_code == 302 assert '/auth/login' in response.location def test_setup_password_too_short(self, client_no_password): """Test that setup rejects password that's too short.""" response = client_no_password.post('/auth/setup', data={ 'password': 'short', 'confirm_password': 'short' }, follow_redirects=True) # Should stay on setup page assert response.status_code == 200 def test_setup_passwords_dont_match(self, client_no_password): """Test that setup rejects mismatched passwords.""" response = client_no_password.post('/auth/setup', data={ 'password': 'password123', 'confirm_password': 'different123' }, follow_redirects=True) # Should stay on setup page assert response.status_code == 200 class TestAPIAuthentication: """Tests for API endpoint authentication.""" def test_scans_list_requires_auth(self, client): """Test that listing scans requires authentication.""" response = client.get('/api/scans') assert response.status_code == 401 data = response.get_json() assert 'error' in data assert data['error'] == 'Authentication required' def test_scans_list_with_auth(self, authenticated_client): """Test that listing scans works when authenticated.""" response = authenticated_client.get('/api/scans') # Should succeed (200) even if empty assert response.status_code == 200 data = response.get_json() assert 'scans' in data def test_scan_trigger_requires_auth(self, client): """Test that triggering scan requires authentication.""" response = client.post('/api/scans', json={ 'config_file': '/app/configs/test.yaml' }) assert response.status_code == 401 def test_scan_get_requires_auth(self, client): """Test that getting scan details requires authentication.""" response = client.get('/api/scans/1') assert response.status_code == 401 def test_scan_delete_requires_auth(self, client): """Test that deleting scan requires authentication.""" response = client.delete('/api/scans/1') assert response.status_code == 401 def test_scan_status_requires_auth(self, client): """Test that getting scan status requires authentication.""" response = client.get('/api/scans/1/status') assert response.status_code == 401 def test_settings_get_requires_auth(self, client): """Test that getting settings requires authentication.""" response = client.get('/api/settings') assert response.status_code == 401 def test_settings_update_requires_auth(self, client): """Test that updating settings requires authentication.""" response = client.put('/api/settings', json={ 'settings': {'test_key': 'test_value'} }) assert response.status_code == 401 def test_settings_get_with_auth(self, authenticated_client): """Test that getting settings works when authenticated.""" response = authenticated_client.get('/api/settings') assert response.status_code == 200 data = response.get_json() assert 'settings' in data def test_schedules_list_requires_auth(self, client): """Test that listing schedules requires authentication.""" response = client.get('/api/schedules') assert response.status_code == 401 def test_alerts_list_requires_auth(self, client): """Test that listing alerts requires authentication.""" response = client.get('/api/alerts') assert response.status_code == 401 def test_health_check_no_auth_required(self, client): """Test that health check endpoints don't require authentication.""" # Health checks should be accessible without authentication response = client.get('/api/scans/health') assert response.status_code == 200 response = client.get('/api/settings/health') assert response.status_code == 200 response = client.get('/api/schedules/health') assert response.status_code == 200 response = client.get('/api/alerts/health') assert response.status_code == 200 class TestSessionManagement: """Tests for session management.""" def test_session_persists_across_requests(self, authenticated_client): """Test that session persists across multiple requests.""" # First request - should succeed response1 = authenticated_client.get('/api/scans') assert response1.status_code == 200 # Second request - should also succeed (session persists) response2 = authenticated_client.get('/api/settings') assert response2.status_code == 200 def test_remember_me_cookie(self, client, app_password): """Test remember me functionality.""" response = client.post('/auth/login', data={ 'password': app_password, 'remember': 'on' }, follow_redirects=False) # Should set remember_me cookie assert response.status_code == 302 # Note: Actual cookie checking would require inspecting response.headers class TestNextRedirect: """Tests for 'next' parameter redirect.""" def test_login_redirects_to_next(self, client, app_password): """Test that login redirects to 'next' parameter.""" response = client.post('/auth/login?next=/api/scans', data={ 'password': app_password }, follow_redirects=False) assert response.status_code == 302 assert '/api/scans' in response.location def test_login_without_next_redirects_to_dashboard(self, client, app_password): """Test that login without 'next' redirects to dashboard.""" response = client.post('/auth/login', data={ 'password': app_password }, follow_redirects=False) assert response.status_code == 302 # Should redirect to dashboard assert 'dashboard' in response.location or response.location == '/'