""" Shared pytest fixtures for the PIC test suite. """ import os import sys import json import tempfile import shutil from unittest.mock import patch import pytest # Ensure api/ is on the path for all tests sys.path.insert(0, os.path.join(os.path.dirname(__file__), '..', 'api')) # ── directory helpers ───────────────────────────────────────────────────────── @pytest.fixture def tmp_dir(): """Temporary directory that is cleaned up after each test.""" d = tempfile.mkdtemp() yield d shutil.rmtree(d, ignore_errors=True) @pytest.fixture def tmp_config_dir(tmp_dir): """Temporary config dir with the sub-directories expected by managers.""" for sub in ('api', 'caddy', 'dns', 'dhcp', 'ntp', 'wireguard'): os.makedirs(os.path.join(tmp_dir, sub), exist_ok=True) return tmp_dir @pytest.fixture def tmp_data_dir(tmp_dir): """Temporary data dir with the sub-directories expected by managers.""" for sub in ('dns', 'mail', 'calendar', 'files', 'wireguard'): os.makedirs(os.path.join(tmp_dir, sub), exist_ok=True) return tmp_dir # ── auth helpers ────────────────────────────────────────────────────────────── def create_test_users(auth_mgr): """Seed an AuthManager with the standard admin + peer test accounts. Safe to call multiple times — AuthManager silently ignores duplicate usernames, so calling this on an already-seeded store is a no-op. Args: auth_mgr: An AuthManager instance (real or mock). Returns: The same auth_mgr instance for convenience. """ auth_mgr.create_user('admin', 'AdminPass123!', 'admin') auth_mgr.create_user('alice', 'AlicePass123!', 'peer') return auth_mgr def _do_login(client, username, password): """POST to /api/auth/login and return the response.""" return client.post( '/api/auth/login', data=json.dumps({'username': username, 'password': password}), content_type='application/json', ) def _make_auth_manager_at(base_path): """Create an AuthManager pointing at base_path/data and base_path/config.""" from auth_manager import AuthManager data_dir = os.path.join(base_path, 'data') config_dir = os.path.join(base_path, 'config') os.makedirs(data_dir, exist_ok=True) os.makedirs(config_dir, exist_ok=True) return AuthManager(data_dir=data_dir, config_dir=config_dir) # ── Flask client fixtures ───────────────────────────────────────────────────── @pytest.fixture def flask_client(tmp_dir): """Flask test client that is pre-authenticated as admin. All existing tests that relied on the old unauthenticated flask_client will continue to work because the before_request auth hook (when present) checks the session — and this fixture establishes a valid admin session before yielding. When auth_routes is not yet registered (backend in progress), the login POST simply returns a non-200 status; in that case the fixture still yields the client so tests that do not need auth can still run. """ from app import app auth_mgr = _make_auth_manager_at(tmp_dir) create_test_users(auth_mgr) app.config['TESTING'] = True app.config['SECRET_KEY'] = 'test-secret' patches = [patch('app.auth_manager', auth_mgr)] try: import auth_routes patches.append( patch.object(auth_routes, 'auth_manager', auth_mgr, create=True) ) except (ImportError, AttributeError): pass started = [p.start() for p in patches] try: with app.test_client() as client: # Best-effort login; if auth routes are not registered yet the # post simply 404s / 405s and tests that need auth will fail # explicitly rather than mysteriously. _do_login(client, 'admin', 'AdminPass123!') yield client finally: for p in patches: p.stop() @pytest.fixture def admin_headers(tmp_dir): """Authenticated admin Flask test client (alias kept for new auth tests).""" from app import app auth_mgr = _make_auth_manager_at(tmp_dir) create_test_users(auth_mgr) app.config['TESTING'] = True app.config['SECRET_KEY'] = 'test-secret' patches = [patch('app.auth_manager', auth_mgr)] try: import auth_routes patches.append( patch.object(auth_routes, 'auth_manager', auth_mgr, create=True) ) except (ImportError, AttributeError): pass started = [p.start() for p in patches] try: with app.test_client() as client: r = _do_login(client, 'admin', 'AdminPass123!') assert r.status_code == 200, ( f'admin_headers fixture: login failed {r.status_code} {r.data}' ) yield client finally: for p in patches: p.stop() @pytest.fixture def peer_headers(tmp_dir): """Authenticated peer (alice) Flask test client.""" from app import app auth_mgr = _make_auth_manager_at(tmp_dir) create_test_users(auth_mgr) app.config['TESTING'] = True app.config['SECRET_KEY'] = 'test-secret' patches = [patch('app.auth_manager', auth_mgr)] try: import auth_routes patches.append( patch.object(auth_routes, 'auth_manager', auth_mgr, create=True) ) except (ImportError, AttributeError): pass started = [p.start() for p in patches] try: with app.test_client() as client: r = _do_login(client, 'alice', 'AlicePass123!') assert r.status_code == 200, ( f'peer_headers fixture: login failed {r.status_code} {r.data}' ) yield client finally: for p in patches: p.stop()