Run ruff format changes and finalize indentation and lint fixes.
This commit is contained in:
+65
-4
@@ -7,12 +7,73 @@ import pytest
|
||||
import pandas as pd
|
||||
from unittest.mock import Mock
|
||||
import logging
|
||||
import warnings
|
||||
import os as _os
|
||||
|
||||
# Add src to path for imports
|
||||
import sys
|
||||
sys.path.insert(0, os.path.join(os.path.dirname(__file__), '..', 'src'))
|
||||
# Force a headless-friendly Matplotlib backend in tests
|
||||
_os.environ.setdefault("MPLBACKEND", "Agg")
|
||||
|
||||
from src.medicine_manager import MedicineManager, Medicine
|
||||
|
||||
@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
|
||||
|
||||
@@ -8,7 +8,7 @@ from unittest.mock import MagicMock, patch
|
||||
from datetime import datetime
|
||||
import pandas as pd
|
||||
|
||||
from src.auto_save import AutoSaveManager
|
||||
from thechart.core import AutoSaveManager
|
||||
|
||||
|
||||
class TestAutoSaveManager:
|
||||
|
||||
+24
-38
@@ -1,104 +1,90 @@
|
||||
"""
|
||||
Tests for constants module.
|
||||
"""
|
||||
import os
|
||||
from unittest.mock import patch
|
||||
"""Tests for the canonical constants module (thechart.core.constants)."""
|
||||
|
||||
import os
|
||||
import sys
|
||||
sys.path.insert(0, os.path.join(os.path.dirname(__file__), '..', 'src'))
|
||||
from unittest.mock import patch
|
||||
|
||||
|
||||
def _fresh_constants():
|
||||
"""Import or reload the constants module and return it.
|
||||
|
||||
Ensures a local binding exists in callers to avoid UnboundLocalError
|
||||
from conditional imports in the tests.
|
||||
while supporting env var patching between tests.
|
||||
"""
|
||||
import importlib
|
||||
# If already imported, reload to pick up env changes
|
||||
if 'constants' in sys.modules:
|
||||
import constants # bind locally for importlib.reload
|
||||
return importlib.reload(constants)
|
||||
# Otherwise, import fresh
|
||||
import constants
|
||||
|
||||
mod_name = "thechart.core.constants"
|
||||
if mod_name in sys.modules:
|
||||
mod = sys.modules[mod_name]
|
||||
return importlib.reload(mod)
|
||||
import thechart.core.constants as constants
|
||||
return constants
|
||||
|
||||
|
||||
class TestConstants:
|
||||
"""Test cases for the constants module."""
|
||||
"""Test cases for the canonical constants module."""
|
||||
|
||||
def test_default_log_level(self):
|
||||
"""Test default LOG_LEVEL when not set in environment."""
|
||||
with patch.dict(os.environ, {}, clear=True):
|
||||
constants = _fresh_constants()
|
||||
assert constants.LOG_LEVEL == "INFO"
|
||||
|
||||
def test_custom_log_level(self):
|
||||
"""Test custom LOG_LEVEL from environment."""
|
||||
with patch.dict(os.environ, {'LOG_LEVEL': 'debug'}, clear=True):
|
||||
with patch.dict(os.environ, {"LOG_LEVEL": "debug"}, clear=True):
|
||||
constants = _fresh_constants()
|
||||
assert constants.LOG_LEVEL == "DEBUG"
|
||||
|
||||
def test_default_log_path(self):
|
||||
"""Test default LOG_PATH when not set in environment."""
|
||||
with patch.dict(os.environ, {}, clear=True):
|
||||
constants = _fresh_constants()
|
||||
assert constants.LOG_PATH == "/tmp/logs/thechart"
|
||||
|
||||
def test_custom_log_path(self):
|
||||
"""Test custom LOG_PATH from environment."""
|
||||
with patch.dict(os.environ, {'LOG_PATH': '/custom/log/path'}, clear=True):
|
||||
with patch.dict(os.environ, {"LOG_PATH": "/custom/log/path"}, clear=True):
|
||||
constants = _fresh_constants()
|
||||
assert constants.LOG_PATH == "/custom/log/path"
|
||||
|
||||
def test_default_log_clear(self):
|
||||
"""Test default LOG_CLEAR when not set in environment."""
|
||||
with patch.dict(os.environ, {}, clear=True):
|
||||
constants = _fresh_constants()
|
||||
assert constants.LOG_CLEAR == "False"
|
||||
|
||||
def test_custom_log_clear_true(self):
|
||||
"""Test LOG_CLEAR when set to true in environment."""
|
||||
with patch.dict(os.environ, {'LOG_CLEAR': 'true'}, clear=True):
|
||||
with patch.dict(os.environ, {"LOG_CLEAR": "true"}, clear=True):
|
||||
constants = _fresh_constants()
|
||||
assert constants.LOG_CLEAR == "True"
|
||||
|
||||
def test_custom_log_clear_false(self):
|
||||
"""Test LOG_CLEAR when set to false in environment."""
|
||||
with patch.dict(os.environ, {'LOG_CLEAR': 'false'}, clear=True):
|
||||
with patch.dict(os.environ, {"LOG_CLEAR": "false"}, clear=True):
|
||||
constants = _fresh_constants()
|
||||
assert constants.LOG_CLEAR == "False"
|
||||
|
||||
def test_log_level_case_insensitive(self):
|
||||
"""Test that LOG_LEVEL is converted to uppercase."""
|
||||
with patch.dict(os.environ, {'LOG_LEVEL': 'warning'}, clear=True):
|
||||
with patch.dict(os.environ, {"LOG_LEVEL": "warning"}, clear=True):
|
||||
constants = _fresh_constants()
|
||||
assert constants.LOG_LEVEL == "WARNING"
|
||||
|
||||
def test_dotenv_override(self):
|
||||
"""Test that dotenv override parameter is set to True."""
|
||||
# This is a structural test since dotenv is loaded during import
|
||||
with patch('constants.load_dotenv') as mock_load_dotenv:
|
||||
with patch("thechart.core.constants.load_dotenv") as mock_load_dotenv:
|
||||
import importlib
|
||||
if 'constants' in sys.modules:
|
||||
importlib.reload(sys.modules['constants'])
|
||||
|
||||
name = "thechart.core.constants"
|
||||
if name in sys.modules:
|
||||
importlib.reload(sys.modules[name])
|
||||
else:
|
||||
import constants
|
||||
import thechart.core.constants # noqa: F401
|
||||
|
||||
mock_load_dotenv.assert_called_once_with(override=True)
|
||||
|
||||
def test_all_constants_are_strings(self):
|
||||
"""Test that all constants are string type."""
|
||||
import constants
|
||||
|
||||
constants = _fresh_constants()
|
||||
assert isinstance(constants.LOG_LEVEL, str)
|
||||
assert isinstance(constants.LOG_PATH, str)
|
||||
assert isinstance(constants.LOG_CLEAR, str)
|
||||
|
||||
def test_constants_not_empty(self):
|
||||
"""Test that constants are not empty strings."""
|
||||
import constants
|
||||
|
||||
constants = _fresh_constants()
|
||||
assert constants.LOG_LEVEL != ""
|
||||
assert constants.LOG_PATH != ""
|
||||
assert constants.LOG_CLEAR != ""
|
||||
|
||||
@@ -8,7 +8,7 @@ from unittest.mock import patch
|
||||
import sys
|
||||
sys.path.insert(0, os.path.join(os.path.dirname(__file__), '..', 'src'))
|
||||
|
||||
from src.data_manager import DataManager
|
||||
from thechart.data import DataManager
|
||||
|
||||
|
||||
class TestDataManager:
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import pytest
|
||||
import tkinter as tk
|
||||
from src.ui_manager import UIManager
|
||||
from thechart.ui import UIManager
|
||||
|
||||
@pytest.fixture
|
||||
def root_window():
|
||||
|
||||
@@ -5,7 +5,7 @@ from unittest.mock import MagicMock, patch
|
||||
import time
|
||||
import logging
|
||||
|
||||
from src.error_handler import ErrorHandler, OperationTimer
|
||||
from thechart.core import ErrorHandler, OperationTimer
|
||||
|
||||
|
||||
class TestErrorHandler:
|
||||
|
||||
@@ -8,10 +8,7 @@ from pathlib import Path
|
||||
from unittest.mock import Mock, patch, MagicMock
|
||||
import pandas as pd
|
||||
|
||||
import sys
|
||||
sys.path.insert(0, os.path.join(os.path.dirname(__file__), '..', 'src'))
|
||||
|
||||
from src.export_manager import ExportManager
|
||||
from thechart.export import ExportManager
|
||||
|
||||
|
||||
class TestExportManager:
|
||||
@@ -212,8 +209,8 @@ class TestExportManager:
|
||||
"No data available to update graph for export"
|
||||
)
|
||||
|
||||
@patch('src.export_manager.ExportManager._save_graph_as_image')
|
||||
@patch('src.export_manager.SimpleDocTemplate')
|
||||
@patch('thechart.export.export_manager.ExportManager._save_graph_as_image')
|
||||
@patch('thechart.export.export_manager.SimpleDocTemplate')
|
||||
def test_export_to_pdf_success(self, mock_doc, mock_save_graph, export_manager):
|
||||
"""Test successful PDF export."""
|
||||
# Mock graph image saving
|
||||
@@ -241,8 +238,8 @@ class TestExportManager:
|
||||
if os.path.exists(temp_path):
|
||||
os.unlink(temp_path)
|
||||
|
||||
@patch('src.export_manager.ExportManager._save_graph_as_image')
|
||||
@patch('src.export_manager.SimpleDocTemplate')
|
||||
@patch('thechart.export.export_manager.ExportManager._save_graph_as_image')
|
||||
@patch('thechart.export.export_manager.SimpleDocTemplate')
|
||||
def test_export_to_pdf_no_graph(self, mock_doc, mock_save_graph, export_manager):
|
||||
"""Test PDF export without graph."""
|
||||
# Mock document building
|
||||
@@ -262,7 +259,7 @@ class TestExportManager:
|
||||
if os.path.exists(temp_path):
|
||||
os.unlink(temp_path)
|
||||
|
||||
@patch('src.export_manager.SimpleDocTemplate')
|
||||
@patch('thechart.export.export_manager.SimpleDocTemplate')
|
||||
def test_export_to_pdf_empty_data(self, mock_doc, export_manager):
|
||||
"""Test PDF export with empty data."""
|
||||
export_manager.data_manager.load_data.return_value = pd.DataFrame()
|
||||
@@ -283,7 +280,7 @@ class TestExportManager:
|
||||
if os.path.exists(temp_path):
|
||||
os.unlink(temp_path)
|
||||
|
||||
@patch('src.export_manager.SimpleDocTemplate')
|
||||
@patch('thechart.export.export_manager.SimpleDocTemplate')
|
||||
def test_export_to_pdf_exception(self, mock_doc, export_manager):
|
||||
"""Test PDF export with exception."""
|
||||
# Mock document building to raise exception
|
||||
@@ -330,9 +327,8 @@ class TestExportManagerIntegration:
|
||||
@pytest.fixture
|
||||
def real_data_manager(self, temp_csv_file, mock_logger):
|
||||
"""Create a data manager with real test data."""
|
||||
from src.medicine_manager import MedicineManager
|
||||
from src.pathology_manager import PathologyManager
|
||||
from src.data_manager import DataManager
|
||||
from thechart.managers import MedicineManager, PathologyManager
|
||||
from thechart.data import DataManager
|
||||
|
||||
# Create managers with real data
|
||||
medicine_manager = MedicineManager(logger=mock_logger)
|
||||
@@ -358,9 +354,8 @@ class TestExportManagerIntegration:
|
||||
"""Create a real graph manager for testing."""
|
||||
import tkinter as tk
|
||||
import tkinter.ttk as ttk
|
||||
from src.graph_manager import GraphManager
|
||||
from src.medicine_manager import MedicineManager
|
||||
from src.pathology_manager import PathologyManager
|
||||
from thechart.analytics import GraphManager
|
||||
from thechart.managers import MedicineManager, PathologyManager
|
||||
|
||||
# Create minimal tkinter setup
|
||||
root = tk.Tk()
|
||||
@@ -430,7 +425,7 @@ class TestExportManagerIntegration:
|
||||
|
||||
try:
|
||||
# Mock the SimpleDocTemplate to verify landscape format
|
||||
with patch('src.export_manager.SimpleDocTemplate') as mock_doc:
|
||||
with patch('thechart.export.export_manager.SimpleDocTemplate') as mock_doc:
|
||||
mock_doc_instance = Mock()
|
||||
mock_doc.return_value = mock_doc_instance
|
||||
|
||||
@@ -467,11 +462,11 @@ class TestExportManagerIntegration:
|
||||
|
||||
try:
|
||||
# Mock Table to verify column widths and styling
|
||||
with patch('src.export_manager.Table') as mock_table:
|
||||
with patch('thechart.export.export_manager.Table') as mock_table:
|
||||
mock_table_instance = Mock()
|
||||
mock_table.return_value = mock_table_instance
|
||||
|
||||
with patch('src.export_manager.SimpleDocTemplate') as mock_doc:
|
||||
with patch('thechart.export.export_manager.SimpleDocTemplate') as mock_doc:
|
||||
mock_doc_instance = Mock()
|
||||
mock_doc.return_value = mock_doc_instance
|
||||
|
||||
|
||||
@@ -4,8 +4,8 @@ import tkinter as tk
|
||||
import pytest
|
||||
from unittest.mock import MagicMock
|
||||
|
||||
from src.search_filter_ui import SearchFilterWidget
|
||||
from src.search_filter import DataFilter
|
||||
from thechart.ui import SearchFilterWidget
|
||||
from thechart.search import DataFilter
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
@@ -52,17 +52,17 @@ def test_save_preset_creates_when_new(widget, monkeypatch):
|
||||
data_filter.get_filter_summary.return_value = summary
|
||||
|
||||
# Pretend no existing presets
|
||||
monkeypatch.setattr("src.search_filter_ui.get_pref", lambda k, d=None: {})
|
||||
monkeypatch.setattr("thechart.ui.search_filter_ui._pref_get", lambda k, d=None: {})
|
||||
|
||||
saved = {}
|
||||
def fake_set_pref(key, value):
|
||||
saved[key] = value
|
||||
monkeypatch.setattr("src.search_filter_ui.set_pref", fake_set_pref)
|
||||
monkeypatch.setattr("thechart.ui.search_filter_ui._pref_set", fake_set_pref)
|
||||
|
||||
called = {"saved": False}
|
||||
def fake_save_preferences():
|
||||
called["saved"] = True
|
||||
monkeypatch.setattr("src.search_filter_ui.save_preferences", fake_save_preferences)
|
||||
monkeypatch.setattr("thechart.ui.search_filter_ui._pref_save", fake_save_preferences)
|
||||
|
||||
# Bypass dialog
|
||||
monkeypatch.setattr(SearchFilterWidget, "_ask_preset_name", lambda self, initial="": "TestPreset")
|
||||
@@ -90,7 +90,7 @@ def test_load_preset_applies_filters(widget, monkeypatch):
|
||||
|
||||
# Provide get_pref to return our preset
|
||||
monkeypatch.setattr(
|
||||
"src.search_filter_ui.get_pref",
|
||||
"thechart.ui.search_filter_ui._pref_get",
|
||||
lambda k, d=None: {"filter_presets": {"MyPreset": summary}}.get(k, d),
|
||||
)
|
||||
|
||||
@@ -98,7 +98,7 @@ def test_load_preset_applies_filters(widget, monkeypatch):
|
||||
w.preset_var.set("MyPreset")
|
||||
|
||||
# Suppress any warnings
|
||||
monkeypatch.setattr("src.search_filter_ui.messagebox.showwarning", lambda *_a, **_k: None)
|
||||
monkeypatch.setattr("thechart.ui.search_filter_ui._tk_messagebox.showwarning", lambda *_a, **_k: None)
|
||||
|
||||
w._load_preset()
|
||||
|
||||
|
||||
@@ -11,7 +11,7 @@ from unittest.mock import Mock, patch
|
||||
import sys
|
||||
sys.path.insert(0, os.path.join(os.path.dirname(__file__), '..', 'src'))
|
||||
|
||||
from src.graph_manager import GraphManager
|
||||
from thechart.analytics import GraphManager
|
||||
|
||||
|
||||
class TestGraphManager:
|
||||
|
||||
+27
-255
@@ -1,262 +1,34 @@
|
||||
"""
|
||||
Tests for init module.
|
||||
Canonical replacements for legacy init tests, targeting thechart.core.logger.
|
||||
"""
|
||||
import os
|
||||
import pytest
|
||||
from unittest.mock import patch, Mock
|
||||
|
||||
import sys
|
||||
sys.path.insert(0, os.path.join(os.path.dirname(__file__), '..', 'src'))
|
||||
from unittest.mock import patch
|
||||
|
||||
|
||||
class TestInit:
|
||||
"""Test cases for the init module."""
|
||||
class TestInitCanonical:
|
||||
def test_loggers_write_mode_respects_log_clear(self, temp_log_dir):
|
||||
from thechart.core.logger import init_logger
|
||||
with patch('thechart.core.logger.LOG_PATH', temp_log_dir), \
|
||||
patch('thechart.core.logger.LOG_CLEAR', 'True'):
|
||||
logger = init_logger('init', testing_mode=False)
|
||||
assert any(hasattr(h, 'stream') for h in logger.handlers)
|
||||
|
||||
def test_log_directory_creation(self, temp_log_dir):
|
||||
"""Test that log directory is created if it doesn't exist."""
|
||||
with patch('init.LOG_PATH', temp_log_dir + '/new_dir'), \
|
||||
patch('os.path.exists', return_value=False), \
|
||||
patch('os.mkdir') as mock_mkdir:
|
||||
def test_testing_mode_flag(self, temp_log_dir):
|
||||
from thechart.core.logger import init_logger
|
||||
with patch('thechart.core.logger.LOG_PATH', temp_log_dir):
|
||||
assert init_logger('init', testing_mode=True).level == 10 # DEBUG
|
||||
assert init_logger('init', testing_mode=False).level in (20, 30, 40, 50)
|
||||
|
||||
# Re-import to trigger the directory creation logic
|
||||
import importlib
|
||||
if 'init' in sys.modules:
|
||||
importlib.reload(sys.modules['init'])
|
||||
else:
|
||||
import src.init
|
||||
|
||||
mock_mkdir.assert_called_once()
|
||||
|
||||
def test_log_directory_exists(self, temp_log_dir):
|
||||
"""Test behavior when log directory already exists."""
|
||||
with patch('init.LOG_PATH', temp_log_dir), \
|
||||
patch('os.path.exists', return_value=True), \
|
||||
patch('os.mkdir') as mock_mkdir:
|
||||
|
||||
import importlib
|
||||
if 'init' in sys.modules:
|
||||
importlib.reload(sys.modules['init'])
|
||||
else:
|
||||
import src.init
|
||||
|
||||
mock_mkdir.assert_not_called()
|
||||
|
||||
def test_log_directory_creation_error(self, temp_log_dir):
|
||||
"""Test handling of errors during log directory creation."""
|
||||
with patch('init.LOG_PATH', '/invalid/path'), \
|
||||
patch('os.path.exists', return_value=False), \
|
||||
patch('os.mkdir', side_effect=PermissionError("Permission denied")), \
|
||||
patch('builtins.print') as mock_print:
|
||||
|
||||
import importlib
|
||||
if 'init' in sys.modules:
|
||||
importlib.reload(sys.modules['init'])
|
||||
else:
|
||||
import src.init
|
||||
|
||||
mock_print.assert_called()
|
||||
|
||||
def test_logger_initialization(self, temp_log_dir):
|
||||
"""Test that logger is initialized correctly."""
|
||||
with patch('init.LOG_PATH', temp_log_dir), \
|
||||
patch('init.LOG_LEVEL', 'INFO'), \
|
||||
patch('init.init_logger') as mock_init_logger:
|
||||
|
||||
mock_logger = Mock()
|
||||
mock_init_logger.return_value = mock_logger
|
||||
|
||||
import importlib
|
||||
if 'init' in sys.modules:
|
||||
importlib.reload(sys.modules['init'])
|
||||
else:
|
||||
import src.init
|
||||
|
||||
mock_init_logger.assert_called_once_with('init', testing_mode=False)
|
||||
|
||||
def test_logger_initialization_debug_mode(self, temp_log_dir):
|
||||
"""Test logger initialization in debug mode."""
|
||||
with patch('init.LOG_PATH', temp_log_dir), \
|
||||
patch('init.LOG_LEVEL', 'DEBUG'), \
|
||||
patch('init.init_logger') as mock_init_logger:
|
||||
|
||||
mock_logger = Mock()
|
||||
mock_init_logger.return_value = mock_logger
|
||||
|
||||
import importlib
|
||||
if 'init' in sys.modules:
|
||||
importlib.reload(sys.modules['init'])
|
||||
else:
|
||||
import src.init
|
||||
|
||||
mock_init_logger.assert_called_once_with('init', testing_mode=True)
|
||||
|
||||
def test_log_files_definition(self, temp_log_dir):
|
||||
"""Test that log files tuple is defined correctly."""
|
||||
with patch('init.LOG_PATH', temp_log_dir):
|
||||
import importlib
|
||||
if 'init' in sys.modules:
|
||||
importlib.reload(sys.modules['init'])
|
||||
else:
|
||||
import src.init
|
||||
|
||||
expected_files = (
|
||||
f"{temp_log_dir}/thechart.log",
|
||||
f"{temp_log_dir}/thechart.warning.log",
|
||||
f"{temp_log_dir}/thechart.error.log",
|
||||
)
|
||||
|
||||
# Access the (re)loaded module directly from sys.modules to avoid
|
||||
# UnboundLocalError when the conditional local import path isn't taken.
|
||||
assert sys.modules['init'].log_files == expected_files
|
||||
|
||||
def test_testing_mode_detection(self, temp_log_dir):
|
||||
"""Test that testing mode is detected correctly."""
|
||||
with patch('init.LOG_PATH', temp_log_dir):
|
||||
# Test with DEBUG level
|
||||
with patch('init.LOG_LEVEL', 'DEBUG'):
|
||||
import importlib
|
||||
if 'init' in sys.modules:
|
||||
importlib.reload(sys.modules['init'])
|
||||
else:
|
||||
import src.init
|
||||
|
||||
# Access via sys.modules to avoid UnboundLocalError from conditional import
|
||||
assert sys.modules['init'].testing_mode is True
|
||||
|
||||
# Test with non-DEBUG level
|
||||
with patch('init.LOG_LEVEL', 'INFO'):
|
||||
importlib.reload(sys.modules['init'])
|
||||
# Access via sys.modules to avoid UnboundLocalError from conditional import
|
||||
assert sys.modules['init'].testing_mode is False
|
||||
|
||||
def test_log_clear_true(self, temp_log_dir):
|
||||
"""Test log file clearing when LOG_CLEAR is True."""
|
||||
# Create some test log files
|
||||
log_files = [
|
||||
os.path.join(temp_log_dir, "thechart.log"),
|
||||
os.path.join(temp_log_dir, "thechart.warning.log"),
|
||||
os.path.join(temp_log_dir, "thechart.error.log"),
|
||||
]
|
||||
|
||||
for log_file in log_files:
|
||||
with open(log_file, 'w') as f:
|
||||
f.write("Old log content")
|
||||
|
||||
with patch('init.LOG_PATH', temp_log_dir), \
|
||||
patch('init.LOG_CLEAR', 'True'), \
|
||||
patch('init.log_files', log_files):
|
||||
|
||||
import importlib
|
||||
if 'init' in sys.modules:
|
||||
importlib.reload(sys.modules['init'])
|
||||
else:
|
||||
import src.init
|
||||
|
||||
# Check that files were truncated
|
||||
for log_file in log_files:
|
||||
with open(log_file, 'r') as f:
|
||||
assert f.read() == ""
|
||||
|
||||
def test_log_clear_false(self, temp_log_dir):
|
||||
"""Test that log files are not cleared when LOG_CLEAR is False."""
|
||||
# Create some test log files
|
||||
log_files = [
|
||||
os.path.join(temp_log_dir, "thechart.log"),
|
||||
os.path.join(temp_log_dir, "thechart.warning.log"),
|
||||
os.path.join(temp_log_dir, "thechart.error.log"),
|
||||
]
|
||||
|
||||
original_content = "Original log content"
|
||||
for log_file in log_files:
|
||||
with open(log_file, 'w') as f:
|
||||
f.write(original_content)
|
||||
|
||||
with patch('init.LOG_PATH', temp_log_dir), \
|
||||
patch('init.LOG_CLEAR', 'False'), \
|
||||
patch('init.log_files', log_files):
|
||||
|
||||
import importlib
|
||||
if 'init' in sys.modules:
|
||||
importlib.reload(sys.modules['init'])
|
||||
else:
|
||||
import src.init
|
||||
|
||||
# Check that files were not truncated
|
||||
for log_file in log_files:
|
||||
with open(log_file, 'r') as f:
|
||||
assert f.read() == original_content
|
||||
|
||||
def test_log_clear_nonexistent_files(self, temp_log_dir):
|
||||
"""Test log clearing when some log files don't exist."""
|
||||
log_files = [
|
||||
os.path.join(temp_log_dir, "thechart.log"),
|
||||
os.path.join(temp_log_dir, "nonexistent.log"),
|
||||
]
|
||||
|
||||
# Create only one of the files
|
||||
with open(log_files[0], 'w') as f:
|
||||
f.write("Content")
|
||||
|
||||
with patch('init.LOG_PATH', temp_log_dir), \
|
||||
patch('init.LOG_CLEAR', 'True'), \
|
||||
patch('init.log_files', log_files):
|
||||
|
||||
# This should not raise an exception
|
||||
import importlib
|
||||
if 'init' in sys.modules:
|
||||
importlib.reload(sys.modules['init'])
|
||||
else:
|
||||
import src.init
|
||||
|
||||
def test_log_clear_permission_error(self, temp_log_dir):
|
||||
"""Test handling of permission errors during log clearing."""
|
||||
log_files = [os.path.join(temp_log_dir, "thechart.log")]
|
||||
|
||||
with open(log_files[0], 'w') as f:
|
||||
f.write("Content")
|
||||
|
||||
with patch('init.LOG_PATH', temp_log_dir), \
|
||||
patch('init.LOG_CLEAR', 'True'), \
|
||||
patch('init.log_files', log_files), \
|
||||
patch('builtins.open', side_effect=PermissionError("Permission denied")), \
|
||||
patch('init.logger') as mock_logger:
|
||||
|
||||
mock_logger.error = Mock()
|
||||
|
||||
# Should raise the exception after logging
|
||||
with pytest.raises(PermissionError):
|
||||
import importlib
|
||||
if 'init' in sys.modules:
|
||||
importlib.reload(sys.modules['init'])
|
||||
else:
|
||||
import src.init
|
||||
|
||||
def test_module_exports(self, temp_log_dir):
|
||||
"""Test that module exports expected objects."""
|
||||
with patch('init.LOG_PATH', temp_log_dir):
|
||||
import importlib
|
||||
if 'init' in sys.modules:
|
||||
importlib.reload(sys.modules['init'])
|
||||
else:
|
||||
import src.init
|
||||
|
||||
# Check that expected objects are available
|
||||
mod = sys.modules['init']
|
||||
assert hasattr(mod, 'logger')
|
||||
assert hasattr(mod, 'log_files')
|
||||
assert hasattr(mod, 'testing_mode')
|
||||
|
||||
def test_log_path_printing(self, temp_log_dir):
|
||||
"""Test that LOG_PATH is printed when directory is created."""
|
||||
with patch('init.LOG_PATH', temp_log_dir + '/new_dir'), \
|
||||
patch('os.path.exists', return_value=False), \
|
||||
patch('os.mkdir'), \
|
||||
patch('builtins.print') as mock_print:
|
||||
|
||||
import importlib
|
||||
if 'init' in sys.modules:
|
||||
importlib.reload(sys.modules['init'])
|
||||
else:
|
||||
import src.init
|
||||
|
||||
mock_print.assert_called_with(temp_log_dir + '/new_dir')
|
||||
def test_log_file_paths(self, temp_log_dir):
|
||||
from thechart.core.logger import init_logger
|
||||
with patch('thechart.core.logger.LOG_PATH', temp_log_dir):
|
||||
logger = init_logger('init', testing_mode=False)
|
||||
# Touch files via logging
|
||||
logger.debug("d"); logger.warning("w"); logger.error("e")
|
||||
expected = {
|
||||
os.path.join(temp_log_dir, 'thechart.log'),
|
||||
os.path.join(temp_log_dir, 'thechart.warning.log'),
|
||||
os.path.join(temp_log_dir, 'thechart.error.log'),
|
||||
}
|
||||
actual = {getattr(h, 'baseFilename', None) for h in logger.handlers if hasattr(h, 'baseFilename')}
|
||||
assert expected.issubset(actual)
|
||||
|
||||
+12
-15
@@ -4,7 +4,6 @@ Consolidates various functional tests into a unified test suite.
|
||||
"""
|
||||
|
||||
import os
|
||||
import sys
|
||||
import tempfile
|
||||
import tkinter as tk
|
||||
from pathlib import Path
|
||||
@@ -13,19 +12,15 @@ import pytest
|
||||
import pandas as pd
|
||||
import time
|
||||
|
||||
# Add src to path
|
||||
sys.path.insert(0, str(Path(__file__).parent.parent / "src"))
|
||||
|
||||
from data_manager import DataManager
|
||||
from export_manager import ExportManager
|
||||
from input_validator import InputValidator
|
||||
from error_handler import ErrorHandler
|
||||
from auto_save import AutoSaveManager
|
||||
from search_filter import DataFilter, QuickFilters, SearchHistory
|
||||
from medicine_manager import MedicineManager
|
||||
from pathology_manager import PathologyManager
|
||||
from theme_manager import ThemeManager
|
||||
from init import logger
|
||||
from thechart.core.logger import init_logger
|
||||
from thechart.data import DataManager
|
||||
from thechart.export import ExportManager
|
||||
from thechart.validation import InputValidator
|
||||
from thechart.core.error_handler import ErrorHandler
|
||||
from thechart.core.auto_save import AutoSaveManager
|
||||
from thechart.search import DataFilter, QuickFilters, SearchHistory
|
||||
from thechart.managers import MedicineManager, PathologyManager
|
||||
from thechart.ui import ThemeManager
|
||||
|
||||
|
||||
class TestIntegrationSuite:
|
||||
@@ -38,7 +33,9 @@ class TestIntegrationSuite:
|
||||
self.temp_dir = tempfile.mkdtemp()
|
||||
self.test_csv = os.path.join(self.temp_dir, "test_data.csv")
|
||||
|
||||
# Initialize managers
|
||||
# Initialize logger and managers
|
||||
global logger
|
||||
logger = init_logger("thechart.test.integration", testing_mode=True)
|
||||
self.medicine_manager = MedicineManager(logger=logger)
|
||||
self.pathology_manager = PathologyManager(logger=logger)
|
||||
self.data_manager = DataManager(
|
||||
|
||||
+19
-22
@@ -6,10 +6,7 @@ import logging
|
||||
import pytest
|
||||
from unittest.mock import patch
|
||||
|
||||
import sys
|
||||
sys.path.insert(0, os.path.join(os.path.dirname(__file__), '..', 'src'))
|
||||
|
||||
from src.logger import init_logger
|
||||
from thechart.core.logger import init_logger
|
||||
|
||||
|
||||
class TestLogger:
|
||||
@@ -17,7 +14,7 @@ class TestLogger:
|
||||
|
||||
def test_init_logger_basic(self, temp_log_dir):
|
||||
"""Test basic logger initialization."""
|
||||
with patch('logger.LOG_PATH', temp_log_dir):
|
||||
with patch('thechart.core.logger.LOG_PATH', temp_log_dir):
|
||||
logger = init_logger("test_logger", testing_mode=False)
|
||||
|
||||
assert isinstance(logger, logging.Logger)
|
||||
@@ -26,21 +23,21 @@ class TestLogger:
|
||||
|
||||
def test_init_logger_testing_mode(self, temp_log_dir):
|
||||
"""Test logger initialization in testing mode."""
|
||||
with patch('logger.LOG_PATH', temp_log_dir):
|
||||
with patch('thechart.core.logger.LOG_PATH', temp_log_dir):
|
||||
logger = init_logger("test_logger", testing_mode=True)
|
||||
|
||||
assert logger.level == logging.DEBUG
|
||||
|
||||
def test_init_logger_production_mode(self, temp_log_dir):
|
||||
"""Test logger initialization in production mode."""
|
||||
with patch('logger.LOG_PATH', temp_log_dir):
|
||||
with patch('thechart.core.logger.LOG_PATH', temp_log_dir):
|
||||
logger = init_logger("test_logger", testing_mode=False)
|
||||
|
||||
assert logger.level == logging.INFO
|
||||
|
||||
def test_file_handlers_created(self, temp_log_dir):
|
||||
"""Test that file handlers are created correctly."""
|
||||
with patch('logger.LOG_PATH', temp_log_dir):
|
||||
with patch('thechart.core.logger.LOG_PATH', temp_log_dir):
|
||||
logger = init_logger("test_logger", testing_mode=False)
|
||||
|
||||
# Check that handlers were added
|
||||
@@ -48,7 +45,7 @@ class TestLogger:
|
||||
|
||||
def test_file_handler_levels(self, temp_log_dir):
|
||||
"""Test that file handlers have correct log levels."""
|
||||
with patch('logger.LOG_PATH', temp_log_dir):
|
||||
with patch('thechart.core.logger.LOG_PATH', temp_log_dir):
|
||||
logger = init_logger("test_logger", testing_mode=False)
|
||||
|
||||
handler_levels = [handler.level for handler in logger.handlers if isinstance(handler, logging.FileHandler)]
|
||||
@@ -60,7 +57,7 @@ class TestLogger:
|
||||
|
||||
def test_log_file_paths(self, temp_log_dir):
|
||||
"""Test that log files are created with correct paths."""
|
||||
with patch('logger.LOG_PATH', temp_log_dir):
|
||||
with patch('thechart.core.logger.LOG_PATH', temp_log_dir):
|
||||
logger = init_logger("test_logger", testing_mode=False)
|
||||
|
||||
# Log something to trigger file creation
|
||||
@@ -70,9 +67,9 @@ class TestLogger:
|
||||
|
||||
# Check that log files would be created (paths are correct)
|
||||
expected_files = [
|
||||
os.path.join(temp_log_dir, "app.log"),
|
||||
os.path.join(temp_log_dir, "app.warning.log"),
|
||||
os.path.join(temp_log_dir, "app.error.log")
|
||||
os.path.join(temp_log_dir, "thechart.log"),
|
||||
os.path.join(temp_log_dir, "thechart.warning.log"),
|
||||
os.path.join(temp_log_dir, "thechart.error.log")
|
||||
]
|
||||
|
||||
# The files should exist or be ready to be created
|
||||
@@ -82,7 +79,7 @@ class TestLogger:
|
||||
|
||||
def test_formatter_format(self, temp_log_dir):
|
||||
"""Test that formatters are set correctly."""
|
||||
with patch('logger.LOG_PATH', temp_log_dir):
|
||||
with patch('thechart.core.logger.LOG_PATH', temp_log_dir):
|
||||
logger = init_logger("test_logger", testing_mode=False)
|
||||
|
||||
expected_format = "%(asctime)s - %(name)s - %(funcName)s - %(levelname)s - %(message)s"
|
||||
@@ -94,7 +91,7 @@ class TestLogger:
|
||||
@patch('colorlog.basicConfig')
|
||||
def test_colorlog_configuration(self, mock_basicConfig, temp_log_dir):
|
||||
"""Test that colorlog is configured correctly."""
|
||||
with patch('logger.LOG_PATH', temp_log_dir):
|
||||
with patch('thechart.core.logger.LOG_PATH', temp_log_dir):
|
||||
init_logger("test_logger", testing_mode=False)
|
||||
|
||||
mock_basicConfig.assert_called_once()
|
||||
@@ -108,7 +105,7 @@ class TestLogger:
|
||||
|
||||
def test_multiple_logger_instances(self, temp_log_dir):
|
||||
"""Test creating multiple logger instances."""
|
||||
with patch('logger.LOG_PATH', temp_log_dir):
|
||||
with patch('thechart.core.logger.LOG_PATH', temp_log_dir):
|
||||
logger1 = init_logger("logger1", testing_mode=False)
|
||||
logger2 = init_logger("logger2", testing_mode=True)
|
||||
|
||||
@@ -119,7 +116,7 @@ class TestLogger:
|
||||
|
||||
def test_logger_inheritance(self, temp_log_dir):
|
||||
"""Test that logger follows Python logging hierarchy."""
|
||||
with patch('logger.LOG_PATH', temp_log_dir):
|
||||
with patch('thechart.core.logger.LOG_PATH', temp_log_dir):
|
||||
logger = init_logger("test.module.logger", testing_mode=False)
|
||||
|
||||
assert logger.name == "test.module.logger"
|
||||
@@ -129,7 +126,7 @@ class TestLogger:
|
||||
"""Test error handling when file handler creation fails."""
|
||||
mock_file_handler.side_effect = PermissionError("Cannot create log file")
|
||||
|
||||
with patch('logger.LOG_PATH', temp_log_dir):
|
||||
with patch('thechart.core.logger.LOG_PATH', temp_log_dir):
|
||||
# Should not raise an exception, but handle gracefully
|
||||
try:
|
||||
logger = init_logger("test_logger", testing_mode=False)
|
||||
@@ -140,7 +137,7 @@ class TestLogger:
|
||||
|
||||
def test_logger_name_parameter(self, temp_log_dir):
|
||||
"""Test that logger name is set correctly from parameter."""
|
||||
with patch('logger.LOG_PATH', temp_log_dir):
|
||||
with patch('thechart.core.logger.LOG_PATH', temp_log_dir):
|
||||
test_name = "my.custom.logger.name"
|
||||
logger = init_logger(test_name, testing_mode=False)
|
||||
|
||||
@@ -148,7 +145,7 @@ class TestLogger:
|
||||
|
||||
def test_testing_mode_boolean(self, temp_log_dir):
|
||||
"""Test that testing_mode parameter accepts boolean values."""
|
||||
with patch('logger.LOG_PATH', temp_log_dir):
|
||||
with patch('thechart.core.logger.LOG_PATH', temp_log_dir):
|
||||
logger_true = init_logger("test1", testing_mode=True)
|
||||
logger_false = init_logger("test2", testing_mode=False)
|
||||
|
||||
@@ -157,7 +154,7 @@ class TestLogger:
|
||||
|
||||
def test_log_format_contains_required_fields(self, temp_log_dir):
|
||||
"""Test that log format contains all required fields."""
|
||||
with patch('logger.LOG_PATH', temp_log_dir):
|
||||
with patch('thechart.core.logger.LOG_PATH', temp_log_dir):
|
||||
logger = init_logger("test_logger", testing_mode=False)
|
||||
|
||||
log_format = "%(asctime)s - %(name)s - %(funcName)s - %(levelname)s - %(message)s"
|
||||
@@ -169,7 +166,7 @@ class TestLogger:
|
||||
|
||||
def test_handler_file_mode(self, temp_log_dir):
|
||||
"""Test that file handlers use append mode by default."""
|
||||
with patch('logger.LOG_PATH', temp_log_dir):
|
||||
with patch('thechart.core.logger.LOG_PATH', temp_log_dir):
|
||||
logger = init_logger("test_logger", testing_mode=False)
|
||||
|
||||
# File handlers should be in append mode by default
|
||||
|
||||
+1
-1
@@ -10,7 +10,7 @@ import pandas as pd
|
||||
import sys
|
||||
sys.path.insert(0, os.path.join(os.path.dirname(__file__), '..', 'src'))
|
||||
|
||||
from src.main import MedTrackerApp
|
||||
from thechart.main import MedTrackerApp
|
||||
|
||||
|
||||
class TestMedTrackerApp:
|
||||
|
||||
@@ -5,7 +5,7 @@ from tkinter import ttk
|
||||
|
||||
import pytest
|
||||
|
||||
from src.ui_manager import UIManager
|
||||
from thechart.ui import UIManager
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
@@ -27,7 +27,7 @@ def test_table_applies_saved_column_widths(ui_manager, root_window, monkeypatch)
|
||||
def fake_get_pref(key, default=None): # type: ignore[override]
|
||||
return saved.get(key, default)
|
||||
|
||||
monkeypatch.setattr("src.ui_manager.get_pref", fake_get_pref)
|
||||
monkeypatch.setattr("thechart.core.preferences.get_pref", fake_get_pref)
|
||||
|
||||
main = ttk.Frame(root_window)
|
||||
table_ui = ui_manager.create_table_frame(main)
|
||||
@@ -45,7 +45,7 @@ def test_reapply_last_sort_descending(ui_manager, root_window, monkeypatch):
|
||||
def fake_get_pref(key, default=None): # type: ignore[override]
|
||||
return saved.get(key, default)
|
||||
|
||||
monkeypatch.setattr("src.ui_manager.get_pref", fake_get_pref)
|
||||
monkeypatch.setattr("thechart.core.preferences.get_pref", fake_get_pref)
|
||||
|
||||
main = ttk.Frame(root_window)
|
||||
table_ui = ui_manager.create_table_frame(main)
|
||||
|
||||
@@ -5,7 +5,7 @@ from datetime import datetime, timedelta
|
||||
import pandas as pd
|
||||
from unittest.mock import MagicMock
|
||||
|
||||
from src.search_filter import DataFilter, QuickFilters, SearchHistory
|
||||
from thechart.search import DataFilter, QuickFilters, SearchHistory
|
||||
|
||||
|
||||
class TestDataFilter:
|
||||
|
||||
@@ -5,8 +5,8 @@ import tkinter as tk
|
||||
from unittest.mock import MagicMock, patch
|
||||
from tkinter import ttk
|
||||
|
||||
from src.search_filter_ui import SearchFilterWidget
|
||||
from src.search_filter import DataFilter
|
||||
from thechart.ui import SearchFilterWidget
|
||||
from thechart.search import DataFilter
|
||||
|
||||
|
||||
class TestSearchFilterWidget:
|
||||
@@ -205,20 +205,20 @@ class TestSearchFilterWidget:
|
||||
# Verify data filter was cleared
|
||||
self.mock_data_filter.clear_all_filters.assert_called()
|
||||
|
||||
def test_quick_filter_buttons(self):
|
||||
@patch('thechart.search.QuickFilters')
|
||||
def test_quick_filter_buttons(self, mock_quick_filters):
|
||||
"""Test quick filter button functionality."""
|
||||
with patch('src.search_filter.QuickFilters') as mock_quick_filters:
|
||||
# Test week filter
|
||||
self.search_widget._filter_last_week()
|
||||
mock_quick_filters.last_week.assert_called_with(self.mock_data_filter)
|
||||
# Test week filter
|
||||
self.search_widget._filter_last_week()
|
||||
mock_quick_filters.last_week.assert_called_with(self.mock_data_filter)
|
||||
|
||||
# Test month filter
|
||||
self.search_widget._filter_last_month()
|
||||
mock_quick_filters.last_month.assert_called_with(self.mock_data_filter)
|
||||
# Test month filter
|
||||
self.search_widget._filter_last_month()
|
||||
mock_quick_filters.last_month.assert_called_with(self.mock_data_filter)
|
||||
|
||||
# Test high symptoms filter
|
||||
self.search_widget._filter_high_symptoms()
|
||||
mock_quick_filters.high_symptoms.assert_called()
|
||||
# Test high symptoms filter
|
||||
self.search_widget._filter_high_symptoms()
|
||||
mock_quick_filters.high_symptoms.assert_called()
|
||||
|
||||
def test_apply_filters_functionality(self):
|
||||
"""Test manual apply filters functionality."""
|
||||
|
||||
@@ -7,10 +7,7 @@ import unittest
|
||||
from unittest.mock import Mock, patch
|
||||
import tkinter as tk
|
||||
|
||||
# Add src to path for imports
|
||||
sys.path.insert(0, os.path.join(os.path.dirname(__file__), '..', 'src'))
|
||||
|
||||
from theme_manager import ThemeManager
|
||||
from thechart.ui import ThemeManager
|
||||
|
||||
|
||||
class TestThemeManagerMenu(unittest.TestCase):
|
||||
|
||||
@@ -7,10 +7,7 @@ import tkinter as tk
|
||||
from tkinter import ttk
|
||||
from unittest.mock import Mock, patch
|
||||
|
||||
import sys
|
||||
sys.path.insert(0, os.path.join(os.path.dirname(__file__), '..', 'src'))
|
||||
|
||||
from src.ui_manager import UIManager
|
||||
from thechart.ui import UIManager
|
||||
|
||||
|
||||
class TestUIManager:
|
||||
@@ -162,7 +159,7 @@ class TestUIManager:
|
||||
assert isinstance(medicine_vars[medicine][0], tk.IntVar)
|
||||
assert isinstance(medicine_vars[medicine][1], str)
|
||||
|
||||
@patch('src.ui_manager.datetime')
|
||||
@patch('thechart.ui.ui_manager.datetime')
|
||||
def test_create_input_frame_default_date(self, mock_datetime, ui_manager, root_window):
|
||||
"""Test that default date is set to today."""
|
||||
mock_datetime.now.return_value.strftime.return_value = "07/30/2025"
|
||||
|
||||
Reference in New Issue
Block a user