""" Fixtures and configuration for pytest tests. """ import os import tempfile import pytest import pandas as pd from unittest.mock import Mock import logging import warnings import os as _os # Force a headless-friendly Matplotlib backend in tests _os.environ.setdefault("MPLBACKEND", "Agg") @pytest.fixture(autouse=True, scope="session") def _matplotlib_headless_backend(): """Force Matplotlib to use the Agg backend for all tests. Doing this at session scope ensures any pyplot usage in code under test doesn't try to initialize interactive Tk backends. """ try: import matplotlib as _mpl _mpl.use("Agg", force=True) except Exception: # If Matplotlib isn't available or already configured, ignore. pass @pytest.fixture(autouse=True) def _stub_pyplot_ui_calls(monkeypatch): """No-op pyplot UI calls that can be noisy or slow in CI. This reduces flicker and avoids timing issues without changing behavior. """ try: import matplotlib.pyplot as _plt monkeypatch.setattr(_plt, "pause", lambda *args, **kwargs: None, raising=False) monkeypatch.setattr(_plt, "draw", lambda *args, **kwargs: None, raising=False) except Exception: pass @pytest.fixture(autouse=True, scope="session") def _tune_reportlab_for_tests(): """Apply small ReportLab tweaks for stable tests without heavy font checks.""" try: from reportlab import rl_config # Disable glyph warnings which are irrelevant for our tests rl_config.warnOnMissingFontGlyphs = 0 # type: ignore[attr-defined] except Exception: pass # Test-only warning hygiene to keep output clean while preserving behavior # - Silence legacy deprecation shims that originate inside package internals warnings.filterwarnings( "ignore", message=r".*search_filter is deprecated.*", category=DeprecationWarning, ) # - Silence a Pillow deprecation surfaced via Matplotlib's Tk backend used by tests warnings.filterwarnings( "ignore", message=r".*'mode' parameter is deprecated and will be removed in Pillow 13.*", category=DeprecationWarning, ) # - Silence pandas parse fallback warning triggered intentionally by invalid test data warnings.filterwarnings( "ignore", message=r"Could not infer format, so each element will be parsed individually.*", category=UserWarning, ) from thechart.managers import MedicineManager, Medicine @pytest.fixture def temp_csv_file(): """Create a temporary CSV file for testing.""" fd, path = tempfile.mkstemp(suffix='.csv') os.close(fd) yield path # Cleanup if os.path.exists(path): os.unlink(path) @pytest.fixture def mock_medicine_manager(): """Create a mock medicine manager with default medicines for testing.""" mock_manager = Mock(spec=MedicineManager) # Default medicines matching the original system default_medicines = { "bupropion": Medicine( key="bupropion", display_name="Bupropion", dosage_info="150/300 mg", quick_doses=["150", "300"], color="#FF6B6B", default_enabled=True ), "hydroxyzine": Medicine( key="hydroxyzine", display_name="Hydroxyzine", dosage_info="25 mg", quick_doses=["25", "50"], color="#4ECDC4", default_enabled=False ), "gabapentin": Medicine( key="gabapentin", display_name="Gabapentin", dosage_info="100 mg", quick_doses=["100", "300", "600"], color="#45B7D1", default_enabled=False ), "propranolol": Medicine( key="propranolol", display_name="Propranolol", dosage_info="10 mg", quick_doses=["10", "20", "40"], color="#96CEB4", default_enabled=True ), "quetiapine": Medicine( key="quetiapine", display_name="Quetiapine", dosage_info="25 mg", quick_doses=["25", "50", "100"], color="#FFEAA7", default_enabled=False ) } mock_manager.get_medicine_keys.return_value = list(default_medicines.keys()) mock_manager.get_all_medicines.return_value = default_medicines mock_manager.get_medicine.side_effect = lambda key: default_medicines.get(key) mock_manager.get_graph_colors.return_value = {k: v.color for k, v in default_medicines.items()} mock_manager.get_quick_doses.side_effect = lambda key: default_medicines.get(key, Medicine("", "", "", [], "", False)).quick_doses return mock_manager @pytest.fixture def mock_pathology_manager(): """Create a mock pathology manager with default pathologies for testing.""" mock_manager = Mock() # Default pathologies matching the original system mock_manager.get_pathology_keys.return_value = ["depression", "anxiety", "sleep", "appetite"] return mock_manager @pytest.fixture def sample_data(): """Sample data for testing.""" return [ ["2024-01-01", 3, 2, 4, 3, 1, "", 0, "", 2, "", 1, "", 0, "", "Test note 1"], ["2024-01-02", 2, 3, 3, 4, 1, "", 1, "", 2, "", 0, "", 1, "", "Test note 2"], ["2024-01-03", 4, 1, 5, 2, 0, "", 0, "", 1, "", 1, "", 0, "", ""], ] @pytest.fixture def sample_dataframe(): """Sample DataFrame for testing.""" return pd.DataFrame({ 'date': ['2024-01-01', '2024-01-02', '2024-01-03'], 'depression': [3, 2, 4], 'anxiety': [2, 3, 1], 'sleep': [4, 3, 5], 'appetite': [3, 4, 2], 'bupropion': [1, 1, 0], 'bupropion_doses': ['2024-01-01 08:00:00:150mg', '2024-01-02 08:00:00:300mg', ''], 'hydroxyzine': [0, 1, 0], 'hydroxyzine_doses': ['', '2024-01-02 20:00:00:25mg', ''], 'gabapentin': [2, 2, 1], 'gabapentin_doses': ['2024-01-01 12:00:00:100mg|2024-01-01 20:00:00:100mg', '2024-01-02 12:00:00:100mg|2024-01-02 20:00:00:100mg', '2024-01-03 12:00:00:100mg'], 'propranolol': [1, 0, 1], 'propranolol_doses': ['2024-01-01 12:00:00:10mg', '', '2024-01-03 12:00:00:20mg'], 'quetiapine': [0, 1, 0], 'quetiapine_doses': ['', '2024-01-02 22:00:00:50mg', ''], 'note': ['Test note 1', 'Test note 2', ''] }) @pytest.fixture def mock_logger(): """Mock logger for testing.""" return Mock(spec=logging.Logger) @pytest.fixture def temp_log_dir(): """Create a temporary directory for log files.""" with tempfile.TemporaryDirectory() as temp_dir: yield temp_dir @pytest.fixture def mock_env_vars(monkeypatch): """Mock environment variables.""" monkeypatch.setenv("LOG_LEVEL", "DEBUG") monkeypatch.setenv("LOG_PATH", "/tmp/test_logs") monkeypatch.setenv("LOG_CLEAR", "False") @pytest.fixture def sample_dose_data(): """Sample dose data for testing dose calculation.""" return { 'standard_format': '2025-07-28 18:59:45:150mg|2025-07-28 19:34:19:75mg', # Should sum to 225 'with_bullets': '• • • • 2025-07-30 07:50:00:300', # Should be 300 'decimal_doses': '2025-07-28 18:59:45:12.5mg|2025-07-28 19:34:19:7.5mg', # Should sum to 20 'no_timestamp': '100mg|50mg', # Should sum to 150 'mixed_format': '• 2025-07-30 22:50:00:10|75mg', # Should sum to 85 'empty_string': '', # Should be 0 'nan_value': 'nan', # Should be 0 'no_units': '2025-07-28 18:59:45:10|2025-07-28 19:34:19:5', # Should sum to 15 } @pytest.fixture def legend_test_dataframe(): """DataFrame specifically designed for testing legend functionality.""" return pd.DataFrame({ 'date': ['2024-01-01', '2024-01-02', '2024-01-03'], 'depression': [3, 2, 4], 'anxiety': [2, 3, 1], 'sleep': [4, 3, 5], 'appetite': [3, 4, 2], # Medicine with consistent doses for average testing 'bupropion': [1, 1, 1], 'bupropion_doses': ['2024-01-01 08:00:00:100mg', '2024-01-02 08:00:00:200mg', '2024-01-03 08:00:00:150mg'], # Average: 150mg # Medicine with varying doses 'propranolol': [1, 1, 0], 'propranolol_doses': ['2024-01-01 12:00:00:10mg', '2024-01-02 12:00:00:20mg', ''], # Average: 15mg (10+20)/2 # Medicines without dose data 'hydroxyzine': [0, 0, 0], 'hydroxyzine_doses': ['', '', ''], 'gabapentin': [0, 0, 0], 'gabapentin_doses': ['', '', ''], 'quetiapine': [0, 0, 0], 'quetiapine_doses': ['', '', ''], 'note': ['Test note 1', 'Test note 2', 'Test note 3'] })