280 lines
10 KiB
Python
280 lines
10 KiB
Python
"""
|
|
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 == '/'
|