"""Shared test fixtures for Flask client tests. Creates a Flask test client and mocks the API client (requests to FastAPI server). """ import sys from pathlib import Path from unittest.mock import MagicMock, patch import pytest # Ensure the client package is importable sys.path.insert(0, str(Path(__file__).resolve().parent.parent)) from app import create_app # All blueprint modules that import api_client at module level. # We must patch at the *use-site* (blueprint namespace) so the local name # ``api_client`` in each module points to the mock, not the real singleton. _API_CLIENT_PATCH_TARGETS = [ "blueprints.auth.api_client", "blueprints.maker.api_client", "blueprints.measure.api_client", "blueprints.statistics.api_client", ] @pytest.fixture def flask_app(): """Create a Flask application configured for testing.""" app = create_app() app.config["TESTING"] = True app.config["WTF_CSRF_ENABLED"] = False app.config["SECRET_KEY"] = "test-secret-key" # Provide default template variables that are normally set inside # specific Jinja2 blocks and therefore not visible to sibling blocks. @app.context_processor def _inject_template_defaults(): return { "versions": [], "current_version": None, } return app @pytest.fixture def client(flask_app): """Yield a Flask test client.""" with flask_app.test_client() as test_client: yield test_client @pytest.fixture def logged_in_client(flask_app): """Yield a Flask test client with a logged-in session.""" with flask_app.test_client() as test_client: with test_client.session_transaction() as sess: sess["api_key"] = "test-api-key-12345" sess["user"] = { "id": 1, "username": "testuser", "display_name": "Test User", "roles": ["Maker", "MeasurementTec", "Metrologist"], "is_admin": True, "language_pref": "en", "theme_pref": "light", "active": True, } sess["user_id"] = 1 sess["language"] = "en" sess["theme"] = "light" yield test_client @pytest.fixture def mock_api_client(): """Patch the api_client singleton used in blueprints. Patches every blueprint module that does ``from services.api_client import api_client`` so the local name in each module resolves to the same MagicMock instance. Yields the mocked APIClient instance so tests can configure responses: mock_api_client.get.return_value = {"items": [...], "total": 1} mock_api_client.post.return_value = {"user": {...}, "api_key": "..."} """ mock = MagicMock() # Default: all methods return empty success dict mock.get.return_value = {} mock.post.return_value = {} mock.put.return_value = {} mock.delete.return_value = {} # Stack patches for every blueprint that imports api_client patchers = [patch(target, mock) for target in _API_CLIENT_PATCH_TARGETS] for p in patchers: p.start() yield mock for p in patchers: p.stop()