Run ruff format changes and finalize indentation and lint fixes.

This commit is contained in:
William Valentin
2025-08-09 12:10:16 -07:00
parent 9cec07e9f6
commit 9a5a2f0022
68 changed files with 1272 additions and 4301 deletions
+65 -4
View File
@@ -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
+1 -1
View File
@@ -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
View File
@@ -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 != ""
+1 -1
View File
@@ -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 -1
View File
@@ -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():
+1 -1
View File
@@ -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:
+14 -19
View File
@@ -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
+7 -7
View File
@@ -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()
+1 -1
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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:
+3 -3
View File
@@ -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)
+1 -1
View File
@@ -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:
+13 -13
View File
@@ -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."""
+1 -4
View File
@@ -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):
+2 -5
View File
@@ -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"