restructure of dirs, huge docs update
This commit is contained in:
279
app/tests/test_authentication.py
Normal file
279
app/tests/test_authentication.py
Normal file
@@ -0,0 +1,279 @@
|
||||
"""
|
||||
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 == '/'
|
||||
Reference in New Issue
Block a user