feat: Add coverage, iniconfig, pluggy, pygments, pytest, pytest-cov, and pytest-mock as dependencies
- Added coverage version 7.10.1 with multiple wheel distributions. - Added iniconfig version 2.1.0 with its wheel distribution. - Added pluggy version 1.6.0 with its wheel distribution. - Added pygments version 2.19.2 with its wheel distribution. - Added pytest version 8.4.1 with its wheel distribution and dependencies. - Added pytest-cov version 6.2.1 with its wheel distribution and dependencies. - Added pytest-mock version 3.14.1 with its wheel distribution and dependencies. - Updated dev-dependencies to include coverage, pytest, pytest-cov, and pytest-mock. - Updated requires-dist to specify minimum versions for coverage, pytest, pytest-cov, and pytest-mock.
This commit is contained in:
@@ -0,0 +1 @@
|
||||
# Tests for TheChart application
|
||||
@@ -0,0 +1,68 @@
|
||||
"""
|
||||
Fixtures and configuration for pytest tests.
|
||||
"""
|
||||
import os
|
||||
import tempfile
|
||||
import pytest
|
||||
import pandas as pd
|
||||
from unittest.mock import Mock
|
||||
import logging
|
||||
|
||||
|
||||
@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 sample_data():
|
||||
"""Sample data for testing."""
|
||||
return [
|
||||
["2024-01-01", 3, 2, 4, 3, 1, 0, 2, 1, "Test note 1"],
|
||||
["2024-01-02", 2, 3, 3, 4, 1, 1, 2, 0, "Test note 2"],
|
||||
["2024-01-03", 4, 1, 5, 2, 0, 0, 1, 1, ""],
|
||||
]
|
||||
|
||||
|
||||
@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],
|
||||
'hydroxyzine': [0, 1, 0],
|
||||
'gabapentin': [2, 2, 1],
|
||||
'propranolol': [1, 0, 1],
|
||||
'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")
|
||||
@@ -0,0 +1,130 @@
|
||||
"""
|
||||
Tests for constants module.
|
||||
"""
|
||||
import os
|
||||
import pytest
|
||||
from unittest.mock import patch
|
||||
|
||||
import sys
|
||||
sys.path.insert(0, os.path.join(os.path.dirname(__file__), '..', 'src'))
|
||||
|
||||
|
||||
class TestConstants:
|
||||
"""Test cases for the constants module."""
|
||||
|
||||
def test_default_log_level(self):
|
||||
"""Test default LOG_LEVEL when not set in environment."""
|
||||
with patch.dict(os.environ, {}, clear=True):
|
||||
# Re-import to get fresh values
|
||||
import importlib
|
||||
if 'constants' in sys.modules:
|
||||
importlib.reload(sys.modules['constants'])
|
||||
else:
|
||||
import 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):
|
||||
import importlib
|
||||
if 'constants' in sys.modules:
|
||||
importlib.reload(sys.modules['constants'])
|
||||
else:
|
||||
import 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):
|
||||
import importlib
|
||||
if 'constants' in sys.modules:
|
||||
importlib.reload(sys.modules['constants'])
|
||||
else:
|
||||
import 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):
|
||||
import importlib
|
||||
if 'constants' in sys.modules:
|
||||
importlib.reload(sys.modules['constants'])
|
||||
else:
|
||||
import 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):
|
||||
import importlib
|
||||
if 'constants' in sys.modules:
|
||||
importlib.reload(sys.modules['constants'])
|
||||
else:
|
||||
import 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):
|
||||
import importlib
|
||||
if 'constants' in sys.modules:
|
||||
importlib.reload(sys.modules['constants'])
|
||||
else:
|
||||
import 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):
|
||||
import importlib
|
||||
if 'constants' in sys.modules:
|
||||
importlib.reload(sys.modules['constants'])
|
||||
else:
|
||||
import 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):
|
||||
import importlib
|
||||
if 'constants' in sys.modules:
|
||||
importlib.reload(sys.modules['constants'])
|
||||
else:
|
||||
import 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:
|
||||
import importlib
|
||||
if 'constants' in sys.modules:
|
||||
importlib.reload(sys.modules['constants'])
|
||||
else:
|
||||
import constants
|
||||
|
||||
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
|
||||
|
||||
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
|
||||
|
||||
assert constants.LOG_LEVEL != ""
|
||||
assert constants.LOG_PATH != ""
|
||||
assert constants.LOG_CLEAR != ""
|
||||
@@ -0,0 +1,285 @@
|
||||
"""
|
||||
Tests for the DataManager class.
|
||||
"""
|
||||
import os
|
||||
import csv
|
||||
import pytest
|
||||
import pandas as pd
|
||||
from unittest.mock import Mock, patch
|
||||
import tempfile
|
||||
|
||||
import sys
|
||||
sys.path.insert(0, os.path.join(os.path.dirname(__file__), '..', 'src'))
|
||||
|
||||
from data_manager import DataManager
|
||||
|
||||
|
||||
class TestDataManager:
|
||||
"""Test cases for the DataManager class."""
|
||||
|
||||
def test_init(self, temp_csv_file, mock_logger):
|
||||
"""Test DataManager initialization."""
|
||||
dm = DataManager(temp_csv_file, mock_logger)
|
||||
assert dm.filename == temp_csv_file
|
||||
assert dm.logger == mock_logger
|
||||
assert os.path.exists(temp_csv_file)
|
||||
|
||||
def test_initialize_csv_creates_file_with_headers(self, temp_csv_file, mock_logger):
|
||||
"""Test that initialize_csv creates a file with proper headers."""
|
||||
# Remove the file if it exists
|
||||
if os.path.exists(temp_csv_file):
|
||||
os.unlink(temp_csv_file)
|
||||
|
||||
dm = DataManager(temp_csv_file, mock_logger)
|
||||
|
||||
# Check file exists and has correct headers
|
||||
assert os.path.exists(temp_csv_file)
|
||||
with open(temp_csv_file, 'r') as f:
|
||||
reader = csv.reader(f)
|
||||
headers = next(reader)
|
||||
expected_headers = [
|
||||
"date", "depression", "anxiety", "sleep", "appetite",
|
||||
"bupropion", "hydroxyzine", "gabapentin", "propranolol", "note"
|
||||
]
|
||||
assert headers == expected_headers
|
||||
|
||||
def test_initialize_csv_does_not_overwrite_existing_file(self, temp_csv_file, mock_logger):
|
||||
"""Test that initialize_csv does not overwrite existing file."""
|
||||
# Write some data to the file first
|
||||
with open(temp_csv_file, 'w') as f:
|
||||
f.write("existing,data\n1,2\n")
|
||||
|
||||
dm = DataManager(temp_csv_file, mock_logger)
|
||||
|
||||
# Check that existing data is preserved
|
||||
with open(temp_csv_file, 'r') as f:
|
||||
content = f.read()
|
||||
assert "existing,data" in content
|
||||
|
||||
def test_load_data_empty_file(self, temp_csv_file, mock_logger):
|
||||
"""Test loading data from an empty file."""
|
||||
dm = DataManager(temp_csv_file, mock_logger)
|
||||
df = dm.load_data()
|
||||
assert df.empty
|
||||
|
||||
def test_load_data_nonexistent_file(self, mock_logger):
|
||||
"""Test loading data from a nonexistent file."""
|
||||
dm = DataManager("nonexistent.csv", mock_logger)
|
||||
df = dm.load_data()
|
||||
assert df.empty
|
||||
mock_logger.warning.assert_called()
|
||||
|
||||
def test_load_data_with_valid_data(self, temp_csv_file, mock_logger, sample_data):
|
||||
"""Test loading valid data from CSV file."""
|
||||
# Write sample data to file
|
||||
with open(temp_csv_file, 'w', newline='') as f:
|
||||
writer = csv.writer(f)
|
||||
# Write headers first
|
||||
writer.writerow([
|
||||
"date", "depression", "anxiety", "sleep", "appetite",
|
||||
"bupropion", "hydroxyzine", "gabapentin", "propranolol", "note"
|
||||
])
|
||||
# Write sample data
|
||||
writer.writerows(sample_data)
|
||||
|
||||
dm = DataManager(temp_csv_file, mock_logger)
|
||||
df = dm.load_data()
|
||||
|
||||
assert not df.empty
|
||||
assert len(df) == 3
|
||||
assert list(df.columns) == [
|
||||
"date", "depression", "anxiety", "sleep", "appetite",
|
||||
"bupropion", "hydroxyzine", "gabapentin", "propranolol", "note"
|
||||
]
|
||||
# Check data types
|
||||
assert df["depression"].dtype == int
|
||||
assert df["anxiety"].dtype == int
|
||||
assert df["note"].dtype == object
|
||||
|
||||
def test_load_data_sorted_by_date(self, temp_csv_file, mock_logger):
|
||||
"""Test that loaded data is sorted by date."""
|
||||
# Write data in random order
|
||||
unsorted_data = [
|
||||
["2024-01-03", 1, 1, 1, 1, 1, 1, 1, 1, "third"],
|
||||
["2024-01-01", 2, 2, 2, 2, 2, 2, 2, 2, "first"],
|
||||
["2024-01-02", 3, 3, 3, 3, 3, 3, 3, 3, "second"],
|
||||
]
|
||||
|
||||
with open(temp_csv_file, 'w', newline='') as f:
|
||||
writer = csv.writer(f)
|
||||
writer.writerow([
|
||||
"date", "depression", "anxiety", "sleep", "appetite",
|
||||
"bupropion", "hydroxyzine", "gabapentin", "propranolol", "note"
|
||||
])
|
||||
writer.writerows(unsorted_data)
|
||||
|
||||
dm = DataManager(temp_csv_file, mock_logger)
|
||||
df = dm.load_data()
|
||||
|
||||
# Check that data is sorted by date
|
||||
assert df.iloc[0]["note"] == "first"
|
||||
assert df.iloc[1]["note"] == "second"
|
||||
assert df.iloc[2]["note"] == "third"
|
||||
|
||||
def test_add_entry_success(self, temp_csv_file, mock_logger):
|
||||
"""Test successfully adding an entry."""
|
||||
dm = DataManager(temp_csv_file, mock_logger)
|
||||
entry = ["2024-01-01", 3, 2, 4, 3, 1, 0, 2, 1, "Test note"]
|
||||
|
||||
result = dm.add_entry(entry)
|
||||
assert result is True
|
||||
|
||||
# Verify entry was added
|
||||
df = dm.load_data()
|
||||
assert len(df) == 1
|
||||
assert df.iloc[0]["date"] == "2024-01-01"
|
||||
assert df.iloc[0]["note"] == "Test note"
|
||||
|
||||
def test_add_entry_duplicate_date(self, temp_csv_file, mock_logger, sample_data):
|
||||
"""Test adding entry with duplicate date."""
|
||||
# Add initial data
|
||||
with open(temp_csv_file, 'w', newline='') as f:
|
||||
writer = csv.writer(f)
|
||||
writer.writerow([
|
||||
"date", "depression", "anxiety", "sleep", "appetite",
|
||||
"bupropion", "hydroxyzine", "gabapentin", "propranolol", "note"
|
||||
])
|
||||
writer.writerows(sample_data)
|
||||
|
||||
dm = DataManager(temp_csv_file, mock_logger)
|
||||
# Try to add entry with existing date
|
||||
duplicate_entry = ["2024-01-01", 5, 5, 5, 5, 1, 1, 1, 1, "Duplicate"]
|
||||
|
||||
result = dm.add_entry(duplicate_entry)
|
||||
assert result is False
|
||||
mock_logger.warning.assert_called_with("Entry with date 2024-01-01 already exists.")
|
||||
|
||||
def test_update_entry_success(self, temp_csv_file, mock_logger, sample_data):
|
||||
"""Test successfully updating an entry."""
|
||||
# Add initial data
|
||||
with open(temp_csv_file, 'w', newline='') as f:
|
||||
writer = csv.writer(f)
|
||||
writer.writerow([
|
||||
"date", "depression", "anxiety", "sleep", "appetite",
|
||||
"bupropion", "hydroxyzine", "gabapentin", "propranolol", "note"
|
||||
])
|
||||
writer.writerows(sample_data)
|
||||
|
||||
dm = DataManager(temp_csv_file, mock_logger)
|
||||
updated_values = ["2024-01-01", 5, 5, 5, 5, 2, 2, 2, 2, "Updated note"]
|
||||
|
||||
result = dm.update_entry("2024-01-01", updated_values)
|
||||
assert result is True
|
||||
|
||||
# Verify entry was updated
|
||||
df = dm.load_data()
|
||||
updated_row = df[df["date"] == "2024-01-01"].iloc[0]
|
||||
assert updated_row["depression"] == 5
|
||||
assert updated_row["note"] == "Updated note"
|
||||
|
||||
def test_update_entry_change_date(self, temp_csv_file, mock_logger, sample_data):
|
||||
"""Test updating an entry with a date change."""
|
||||
# Add initial data
|
||||
with open(temp_csv_file, 'w', newline='') as f:
|
||||
writer = csv.writer(f)
|
||||
writer.writerow([
|
||||
"date", "depression", "anxiety", "sleep", "appetite",
|
||||
"bupropion", "hydroxyzine", "gabapentin", "propranolol", "note"
|
||||
])
|
||||
writer.writerows(sample_data)
|
||||
|
||||
dm = DataManager(temp_csv_file, mock_logger)
|
||||
updated_values = ["2024-01-05", 5, 5, 5, 5, 2, 2, 2, 2, "Updated note"]
|
||||
|
||||
result = dm.update_entry("2024-01-01", updated_values)
|
||||
assert result is True
|
||||
|
||||
# Verify old date is gone and new date exists
|
||||
df = dm.load_data()
|
||||
assert not any(df["date"] == "2024-01-01")
|
||||
assert any(df["date"] == "2024-01-05")
|
||||
|
||||
def test_update_entry_duplicate_date(self, temp_csv_file, mock_logger, sample_data):
|
||||
"""Test updating entry to a date that already exists."""
|
||||
# Add initial data
|
||||
with open(temp_csv_file, 'w', newline='') as f:
|
||||
writer = csv.writer(f)
|
||||
writer.writerow([
|
||||
"date", "depression", "anxiety", "sleep", "appetite",
|
||||
"bupropion", "hydroxyzine", "gabapentin", "propranolol", "note"
|
||||
])
|
||||
writer.writerows(sample_data)
|
||||
|
||||
dm = DataManager(temp_csv_file, mock_logger)
|
||||
# Try to change date to one that already exists
|
||||
updated_values = ["2024-01-02", 5, 5, 5, 5, 2, 2, 2, 2, "Updated note"]
|
||||
|
||||
result = dm.update_entry("2024-01-01", updated_values)
|
||||
assert result is False
|
||||
mock_logger.warning.assert_called_with(
|
||||
"Cannot update: entry with date 2024-01-02 already exists."
|
||||
)
|
||||
|
||||
def test_delete_entry_success(self, temp_csv_file, mock_logger, sample_data):
|
||||
"""Test successfully deleting an entry."""
|
||||
# Add initial data
|
||||
with open(temp_csv_file, 'w', newline='') as f:
|
||||
writer = csv.writer(f)
|
||||
writer.writerow([
|
||||
"date", "depression", "anxiety", "sleep", "appetite",
|
||||
"bupropion", "hydroxyzine", "gabapentin", "propranolol", "note"
|
||||
])
|
||||
writer.writerows(sample_data)
|
||||
|
||||
dm = DataManager(temp_csv_file, mock_logger)
|
||||
|
||||
result = dm.delete_entry("2024-01-02")
|
||||
assert result is True
|
||||
|
||||
# Verify entry was deleted
|
||||
df = dm.load_data()
|
||||
assert len(df) == 2
|
||||
assert not any(df["date"] == "2024-01-02")
|
||||
|
||||
def test_delete_entry_nonexistent(self, temp_csv_file, mock_logger, sample_data):
|
||||
"""Test deleting a nonexistent entry."""
|
||||
# Add initial data
|
||||
with open(temp_csv_file, 'w', newline='') as f:
|
||||
writer = csv.writer(f)
|
||||
writer.writerow([
|
||||
"date", "depression", "anxiety", "sleep", "appetite",
|
||||
"bupropion", "hydroxyzine", "gabapentin", "propranolol", "note"
|
||||
])
|
||||
writer.writerows(sample_data)
|
||||
|
||||
dm = DataManager(temp_csv_file, mock_logger)
|
||||
|
||||
result = dm.delete_entry("2024-01-10")
|
||||
assert result is True # Should return True even if no matching entry
|
||||
|
||||
# Verify no data was lost
|
||||
df = dm.load_data()
|
||||
assert len(df) == 3
|
||||
|
||||
@patch('pandas.read_csv')
|
||||
def test_load_data_exception_handling(self, mock_read_csv, temp_csv_file, mock_logger):
|
||||
"""Test exception handling in load_data."""
|
||||
mock_read_csv.side_effect = Exception("Test error")
|
||||
|
||||
dm = DataManager(temp_csv_file, mock_logger)
|
||||
df = dm.load_data()
|
||||
|
||||
assert df.empty
|
||||
mock_logger.error.assert_called_with("Error loading data: Test error")
|
||||
|
||||
@patch('builtins.open')
|
||||
def test_add_entry_exception_handling(self, mock_open, temp_csv_file, mock_logger):
|
||||
"""Test exception handling in add_entry."""
|
||||
mock_open.side_effect = Exception("Test error")
|
||||
|
||||
dm = DataManager(temp_csv_file, mock_logger)
|
||||
entry = ["2024-01-01", 3, 2, 4, 3, 1, 0, 2, 1, "Test note"]
|
||||
|
||||
result = dm.add_entry(entry)
|
||||
assert result is False
|
||||
mock_logger.error.assert_called_with("Error adding entry: Test error")
|
||||
@@ -0,0 +1,267 @@
|
||||
"""
|
||||
Tests for the GraphManager class.
|
||||
"""
|
||||
import os
|
||||
import pytest
|
||||
import pandas as pd
|
||||
import tkinter as tk
|
||||
from tkinter import ttk
|
||||
from unittest.mock import Mock, patch, MagicMock
|
||||
import matplotlib.pyplot as plt
|
||||
|
||||
import sys
|
||||
sys.path.insert(0, os.path.join(os.path.dirname(__file__), '..', 'src'))
|
||||
|
||||
from graph_manager import GraphManager
|
||||
|
||||
|
||||
class TestGraphManager:
|
||||
"""Test cases for the GraphManager class."""
|
||||
|
||||
@pytest.fixture
|
||||
def root_window(self):
|
||||
"""Create a root window for testing."""
|
||||
root = tk.Tk()
|
||||
yield root
|
||||
root.destroy()
|
||||
|
||||
@pytest.fixture
|
||||
def parent_frame(self, root_window):
|
||||
"""Create a parent frame for testing."""
|
||||
frame = ttk.LabelFrame(root_window, text="Test Frame")
|
||||
frame.pack()
|
||||
return frame
|
||||
|
||||
def test_init(self, parent_frame):
|
||||
"""Test GraphManager initialization."""
|
||||
gm = GraphManager(parent_frame)
|
||||
|
||||
assert gm.parent_frame == parent_frame
|
||||
assert isinstance(gm.toggle_vars, dict)
|
||||
assert "depression" in gm.toggle_vars
|
||||
assert "anxiety" in gm.toggle_vars
|
||||
assert "sleep" in gm.toggle_vars
|
||||
assert "appetite" in gm.toggle_vars
|
||||
|
||||
# Check that all toggles are initially True
|
||||
for var in gm.toggle_vars.values():
|
||||
assert var.get() is True
|
||||
|
||||
def test_toggle_controls_creation(self, parent_frame):
|
||||
"""Test that toggle controls are created properly."""
|
||||
gm = GraphManager(parent_frame)
|
||||
|
||||
# Check that control frame exists
|
||||
assert hasattr(gm, 'control_frame')
|
||||
assert isinstance(gm.control_frame, ttk.Frame)
|
||||
|
||||
# Check that toggle variables exist
|
||||
expected_toggles = ["depression", "anxiety", "sleep", "appetite"]
|
||||
for toggle in expected_toggles:
|
||||
assert toggle in gm.toggle_vars
|
||||
assert isinstance(gm.toggle_vars[toggle], tk.BooleanVar)
|
||||
|
||||
def test_graph_frame_creation(self, parent_frame):
|
||||
"""Test that graph frame is created properly."""
|
||||
gm = GraphManager(parent_frame)
|
||||
|
||||
assert hasattr(gm, 'graph_frame')
|
||||
assert isinstance(gm.graph_frame, ttk.Frame)
|
||||
|
||||
@patch('matplotlib.pyplot.subplots')
|
||||
def test_matplotlib_initialization(self, mock_subplots, parent_frame):
|
||||
"""Test matplotlib figure and canvas initialization."""
|
||||
mock_fig = Mock()
|
||||
mock_ax = Mock()
|
||||
mock_subplots.return_value = (mock_fig, mock_ax)
|
||||
|
||||
with patch('graph_manager.FigureCanvasTkAgg') as mock_canvas_class:
|
||||
mock_canvas = Mock()
|
||||
mock_canvas_class.return_value = mock_canvas
|
||||
|
||||
gm = GraphManager(parent_frame)
|
||||
|
||||
assert gm.fig == mock_fig
|
||||
assert gm.ax == mock_ax
|
||||
assert gm.canvas == mock_canvas
|
||||
mock_canvas_class.assert_called_once_with(figure=mock_fig, master=gm.graph_frame)
|
||||
|
||||
def test_update_graph_empty_dataframe(self, parent_frame):
|
||||
"""Test updating graph with empty DataFrame."""
|
||||
with patch('matplotlib.pyplot.subplots') as mock_subplots:
|
||||
mock_fig = Mock()
|
||||
mock_ax = Mock()
|
||||
mock_subplots.return_value = (mock_fig, mock_ax)
|
||||
|
||||
with patch('graph_manager.FigureCanvasTkAgg'):
|
||||
gm = GraphManager(parent_frame)
|
||||
|
||||
# Test with empty DataFrame
|
||||
empty_df = pd.DataFrame()
|
||||
gm.update_graph(empty_df)
|
||||
|
||||
# Verify ax.clear() was called
|
||||
mock_ax.clear.assert_called()
|
||||
|
||||
def test_update_graph_with_data(self, parent_frame, sample_dataframe):
|
||||
"""Test updating graph with valid data."""
|
||||
with patch('matplotlib.pyplot.subplots') as mock_subplots:
|
||||
mock_fig = Mock()
|
||||
mock_ax = Mock()
|
||||
mock_subplots.return_value = (mock_fig, mock_ax)
|
||||
|
||||
with patch('graph_manager.FigureCanvasTkAgg') as mock_canvas_class:
|
||||
mock_canvas = Mock()
|
||||
mock_canvas_class.return_value = mock_canvas
|
||||
|
||||
gm = GraphManager(parent_frame)
|
||||
gm.update_graph(sample_dataframe)
|
||||
|
||||
# Verify methods were called
|
||||
mock_ax.clear.assert_called()
|
||||
mock_canvas.draw.assert_called()
|
||||
|
||||
def test_toggle_functionality(self, parent_frame, sample_dataframe):
|
||||
"""Test that toggle variables affect graph display."""
|
||||
with patch('matplotlib.pyplot.subplots') as mock_subplots:
|
||||
mock_fig = Mock()
|
||||
mock_ax = Mock()
|
||||
mock_subplots.return_value = (mock_fig, mock_ax)
|
||||
|
||||
with patch('graph_manager.FigureCanvasTkAgg') as mock_canvas_class:
|
||||
mock_canvas = Mock()
|
||||
mock_canvas_class.return_value = mock_canvas
|
||||
|
||||
gm = GraphManager(parent_frame)
|
||||
|
||||
# Turn off depression toggle
|
||||
gm.toggle_vars["depression"].set(False)
|
||||
gm.update_graph(sample_dataframe)
|
||||
|
||||
# The graph should still update (specific plotting logic would need more detailed testing)
|
||||
mock_ax.clear.assert_called()
|
||||
mock_canvas.draw.assert_called()
|
||||
|
||||
def test_close_method(self, parent_frame):
|
||||
"""Test the close method."""
|
||||
with patch('matplotlib.pyplot.subplots') as mock_subplots:
|
||||
mock_fig = Mock()
|
||||
mock_ax = Mock()
|
||||
mock_subplots.return_value = (mock_fig, mock_ax)
|
||||
|
||||
with patch('graph_manager.FigureCanvasTkAgg') as mock_canvas_class:
|
||||
mock_canvas = Mock()
|
||||
mock_canvas_class.return_value = mock_canvas
|
||||
|
||||
with patch('matplotlib.pyplot.close') as mock_plt_close:
|
||||
gm = GraphManager(parent_frame)
|
||||
gm.close()
|
||||
|
||||
mock_plt_close.assert_called_once_with(mock_fig)
|
||||
|
||||
def test_date_parsing_in_update_graph(self, parent_frame):
|
||||
"""Test that date parsing works correctly in update_graph."""
|
||||
# Create a DataFrame with date strings
|
||||
df_with_dates = 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],
|
||||
'hydroxyzine': [0, 1, 0],
|
||||
'gabapentin': [2, 2, 1],
|
||||
'propranolol': [1, 0, 1],
|
||||
'note': ['Test note 1', 'Test note 2', '']
|
||||
})
|
||||
|
||||
with patch('matplotlib.pyplot.subplots') as mock_subplots:
|
||||
mock_fig = Mock()
|
||||
mock_ax = Mock()
|
||||
mock_subplots.return_value = (mock_fig, mock_ax)
|
||||
|
||||
with patch('graph_manager.FigureCanvasTkAgg') as mock_canvas_class:
|
||||
mock_canvas = Mock()
|
||||
mock_canvas_class.return_value = mock_canvas
|
||||
|
||||
with patch('pandas.to_datetime') as mock_to_datetime:
|
||||
gm = GraphManager(parent_frame)
|
||||
gm.update_graph(df_with_dates)
|
||||
|
||||
# Verify pandas.to_datetime was called
|
||||
mock_to_datetime.assert_called()
|
||||
|
||||
@patch('matplotlib.pyplot.subplots')
|
||||
def test_exception_handling_in_update_graph(self, mock_subplots, parent_frame, sample_dataframe):
|
||||
"""Test exception handling in update_graph method."""
|
||||
mock_fig = Mock()
|
||||
mock_ax = Mock()
|
||||
mock_ax.plot.side_effect = Exception("Plot error")
|
||||
mock_subplots.return_value = (mock_fig, mock_ax)
|
||||
|
||||
with patch('graph_manager.FigureCanvasTkAgg') as mock_canvas_class:
|
||||
mock_canvas = Mock()
|
||||
mock_canvas_class.return_value = mock_canvas
|
||||
|
||||
gm = GraphManager(parent_frame)
|
||||
|
||||
# This should not raise an exception, but handle it gracefully
|
||||
try:
|
||||
gm.update_graph(sample_dataframe)
|
||||
except Exception as e:
|
||||
pytest.fail(f"update_graph should handle exceptions gracefully, but raised: {e}")
|
||||
|
||||
def test_grid_configuration(self, parent_frame):
|
||||
"""Test that grid configuration is set up correctly."""
|
||||
gm = GraphManager(parent_frame)
|
||||
|
||||
# The parent frame should have grid configuration
|
||||
# Note: In a real test, you might need to check grid_info() or similar
|
||||
# This is a basic structure test
|
||||
assert hasattr(gm, 'parent_frame')
|
||||
assert hasattr(gm, 'control_frame')
|
||||
assert hasattr(gm, 'graph_frame')
|
||||
|
||||
def test_canvas_widget_packing(self, parent_frame):
|
||||
"""Test that canvas widget is properly packed."""
|
||||
with patch('matplotlib.pyplot.subplots') as mock_subplots:
|
||||
mock_fig = Mock()
|
||||
mock_ax = Mock()
|
||||
mock_subplots.return_value = (mock_fig, mock_ax)
|
||||
|
||||
with patch('graph_manager.FigureCanvasTkAgg') as mock_canvas_class:
|
||||
mock_canvas = Mock()
|
||||
mock_canvas.get_tk_widget.return_value = Mock()
|
||||
mock_canvas_class.return_value = mock_canvas
|
||||
|
||||
gm = GraphManager(parent_frame)
|
||||
|
||||
# Verify get_tk_widget was called (for packing)
|
||||
mock_canvas.get_tk_widget.assert_called()
|
||||
|
||||
def test_multiple_toggle_combinations(self, parent_frame, sample_dataframe):
|
||||
"""Test various combinations of toggle states."""
|
||||
with patch('matplotlib.pyplot.subplots') as mock_subplots:
|
||||
mock_fig = Mock()
|
||||
mock_ax = Mock()
|
||||
mock_subplots.return_value = (mock_fig, mock_ax)
|
||||
|
||||
with patch('graph_manager.FigureCanvasTkAgg') as mock_canvas_class:
|
||||
mock_canvas = Mock()
|
||||
mock_canvas_class.return_value = mock_canvas
|
||||
|
||||
gm = GraphManager(parent_frame)
|
||||
|
||||
# Test all toggles off
|
||||
for toggle in gm.toggle_vars.values():
|
||||
toggle.set(False)
|
||||
gm.update_graph(sample_dataframe)
|
||||
|
||||
# Test mixed toggles
|
||||
gm.toggle_vars["depression"].set(True)
|
||||
gm.toggle_vars["anxiety"].set(False)
|
||||
gm.update_graph(sample_dataframe)
|
||||
|
||||
# Verify the graph was updated in each case
|
||||
assert mock_ax.clear.call_count >= 2
|
||||
assert mock_canvas.draw.call_count >= 2
|
||||
@@ -0,0 +1,258 @@
|
||||
"""
|
||||
Tests for init module.
|
||||
"""
|
||||
import os
|
||||
import tempfile
|
||||
import pytest
|
||||
from unittest.mock import patch, Mock
|
||||
|
||||
import sys
|
||||
sys.path.insert(0, os.path.join(os.path.dirname(__file__), '..', 'src'))
|
||||
|
||||
|
||||
class TestInit:
|
||||
"""Test cases for the init module."""
|
||||
|
||||
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:
|
||||
|
||||
# Re-import to trigger the directory creation logic
|
||||
import importlib
|
||||
if 'init' in sys.modules:
|
||||
importlib.reload(sys.modules['init'])
|
||||
else:
|
||||
import 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 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 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 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 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 init
|
||||
|
||||
expected_files = (
|
||||
f"{temp_log_dir}/thechart.log",
|
||||
f"{temp_log_dir}/thechart.warning.log",
|
||||
f"{temp_log_dir}/thechart.error.log",
|
||||
)
|
||||
|
||||
assert 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 init
|
||||
|
||||
assert init.testing_mode is True
|
||||
|
||||
# Test with non-DEBUG level
|
||||
with patch('init.LOG_LEVEL', 'INFO'):
|
||||
importlib.reload(sys.modules['init'])
|
||||
assert 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 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 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 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 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 init
|
||||
|
||||
# Check that expected objects are available
|
||||
assert hasattr(init, 'logger')
|
||||
assert hasattr(init, 'log_files')
|
||||
assert hasattr(init, '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 init
|
||||
|
||||
mock_print.assert_called_with(temp_log_dir + '/new_dir')
|
||||
@@ -0,0 +1,180 @@
|
||||
"""
|
||||
Tests for logger module.
|
||||
"""
|
||||
import os
|
||||
import logging
|
||||
import tempfile
|
||||
import pytest
|
||||
from unittest.mock import patch, Mock
|
||||
|
||||
import sys
|
||||
sys.path.insert(0, os.path.join(os.path.dirname(__file__), '..', 'src'))
|
||||
|
||||
from logger import init_logger
|
||||
|
||||
|
||||
class TestLogger:
|
||||
"""Test cases for the logger module."""
|
||||
|
||||
def test_init_logger_basic(self, temp_log_dir):
|
||||
"""Test basic logger initialization."""
|
||||
with patch('logger.LOG_PATH', temp_log_dir):
|
||||
logger = init_logger("test_logger", testing_mode=False)
|
||||
|
||||
assert isinstance(logger, logging.Logger)
|
||||
assert logger.name == "test_logger"
|
||||
assert logger.level == logging.INFO
|
||||
|
||||
def test_init_logger_testing_mode(self, temp_log_dir):
|
||||
"""Test logger initialization in testing mode."""
|
||||
with patch('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):
|
||||
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):
|
||||
logger = init_logger("test_logger", testing_mode=False)
|
||||
|
||||
# Check that handlers were added
|
||||
assert len(logger.handlers) >= 3 # At least 3 file handlers
|
||||
|
||||
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):
|
||||
logger = init_logger("test_logger", testing_mode=False)
|
||||
|
||||
handler_levels = [handler.level for handler in logger.handlers if isinstance(handler, logging.FileHandler)]
|
||||
|
||||
# Should have handlers for DEBUG, WARNING, and ERROR levels
|
||||
assert logging.DEBUG in handler_levels
|
||||
assert logging.WARNING in handler_levels
|
||||
assert logging.ERROR in handler_levels
|
||||
|
||||
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):
|
||||
logger = init_logger("test_logger", testing_mode=False)
|
||||
|
||||
# Log something to trigger file creation
|
||||
logger.debug("Test debug message")
|
||||
logger.warning("Test warning message")
|
||||
logger.error("Test error message")
|
||||
|
||||
# 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")
|
||||
]
|
||||
|
||||
# The files should exist or be ready to be created
|
||||
for handler in logger.handlers:
|
||||
if isinstance(handler, logging.FileHandler):
|
||||
assert handler.baseFilename in expected_files
|
||||
|
||||
def test_formatter_format(self, temp_log_dir):
|
||||
"""Test that formatters are set correctly."""
|
||||
with patch('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"
|
||||
|
||||
for handler in logger.handlers:
|
||||
if isinstance(handler, logging.FileHandler):
|
||||
assert handler.formatter._fmt == expected_format
|
||||
|
||||
@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):
|
||||
init_logger("test_logger", testing_mode=False)
|
||||
|
||||
mock_basicConfig.assert_called_once()
|
||||
|
||||
# Check that format includes color and bold formatting
|
||||
call_args = mock_basicConfig.call_args
|
||||
assert 'format' in call_args[1]
|
||||
format_string = call_args[1]['format']
|
||||
assert '%(log_color)s' in format_string
|
||||
assert '\033[1m' in format_string # Bold sequence
|
||||
|
||||
def test_multiple_logger_instances(self, temp_log_dir):
|
||||
"""Test creating multiple logger instances."""
|
||||
with patch('logger.LOG_PATH', temp_log_dir):
|
||||
logger1 = init_logger("logger1", testing_mode=False)
|
||||
logger2 = init_logger("logger2", testing_mode=True)
|
||||
|
||||
assert logger1.name == "logger1"
|
||||
assert logger2.name == "logger2"
|
||||
assert logger1.level == logging.INFO
|
||||
assert logger2.level == logging.DEBUG
|
||||
|
||||
def test_logger_inheritance(self, temp_log_dir):
|
||||
"""Test that logger follows Python logging hierarchy."""
|
||||
with patch('logger.LOG_PATH', temp_log_dir):
|
||||
logger = init_logger("test.module.logger", testing_mode=False)
|
||||
|
||||
assert logger.name == "test.module.logger"
|
||||
|
||||
@patch('logging.FileHandler')
|
||||
def test_file_handler_error_handling(self, mock_file_handler, temp_log_dir):
|
||||
"""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):
|
||||
# Should not raise an exception, but handle gracefully
|
||||
try:
|
||||
logger = init_logger("test_logger", testing_mode=False)
|
||||
# Logger should still be created, just without file handlers
|
||||
assert isinstance(logger, logging.Logger)
|
||||
except PermissionError:
|
||||
pytest.fail("init_logger should handle file creation errors gracefully")
|
||||
|
||||
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):
|
||||
test_name = "my.custom.logger.name"
|
||||
logger = init_logger(test_name, testing_mode=False)
|
||||
|
||||
assert logger.name == test_name
|
||||
|
||||
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):
|
||||
logger_true = init_logger("test1", testing_mode=True)
|
||||
logger_false = init_logger("test2", testing_mode=False)
|
||||
|
||||
assert logger_true.level == logging.DEBUG
|
||||
assert logger_false.level == logging.INFO
|
||||
|
||||
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):
|
||||
logger = init_logger("test_logger", testing_mode=False)
|
||||
|
||||
log_format = "%(asctime)s - %(name)s - %(funcName)s - %(levelname)s - %(message)s"
|
||||
|
||||
# Check that format contains all expected fields
|
||||
expected_fields = ['%(asctime)s', '%(name)s', '%(funcName)s', '%(levelname)s', '%(message)s']
|
||||
for field in expected_fields:
|
||||
assert field in log_format
|
||||
|
||||
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):
|
||||
logger = init_logger("test_logger", testing_mode=False)
|
||||
|
||||
# File handlers should be in append mode by default
|
||||
for handler in logger.handlers:
|
||||
if isinstance(handler, logging.FileHandler):
|
||||
# FileHandler uses 'a' mode by default
|
||||
assert hasattr(handler, 'mode') # Basic check that it's a file handler
|
||||
@@ -0,0 +1,411 @@
|
||||
"""
|
||||
Tests for the main application and MedTrackerApp class.
|
||||
"""
|
||||
import os
|
||||
import pytest
|
||||
import tkinter as tk
|
||||
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 main import MedTrackerApp
|
||||
|
||||
|
||||
class TestMedTrackerApp:
|
||||
"""Test cases for the MedTrackerApp class."""
|
||||
|
||||
@pytest.fixture
|
||||
def root_window(self):
|
||||
"""Create a root window for testing."""
|
||||
root = tk.Tk()
|
||||
yield root
|
||||
root.destroy()
|
||||
|
||||
@pytest.fixture
|
||||
def mock_managers(self):
|
||||
"""Mock the manager classes."""
|
||||
with patch('main.UIManager') as mock_ui, \
|
||||
patch('main.DataManager') as mock_data, \
|
||||
patch('main.GraphManager') as mock_graph:
|
||||
yield {
|
||||
'ui': mock_ui,
|
||||
'data': mock_data,
|
||||
'graph': mock_graph
|
||||
}
|
||||
|
||||
def test_init_default_filename(self, root_window, mock_managers):
|
||||
"""Test initialization with default filename."""
|
||||
with patch('sys.argv', ['main.py']):
|
||||
app = MedTrackerApp(root_window)
|
||||
|
||||
assert app.filename == "thechart_data.csv"
|
||||
assert app.root == root_window
|
||||
assert root_window.title() == "Thechart - medication tracker"
|
||||
|
||||
def test_init_custom_filename_exists(self, root_window, mock_managers):
|
||||
"""Test initialization with custom filename that exists."""
|
||||
with patch('sys.argv', ['main.py', 'custom_data.csv']), \
|
||||
patch('os.path.exists', return_value=True):
|
||||
|
||||
app = MedTrackerApp(root_window)
|
||||
|
||||
assert app.filename == "custom_data.csv"
|
||||
|
||||
def test_init_custom_filename_not_exists(self, root_window, mock_managers):
|
||||
"""Test initialization with custom filename that doesn't exist."""
|
||||
with patch('sys.argv', ['main.py', 'nonexistent.csv']), \
|
||||
patch('os.path.exists', return_value=False):
|
||||
|
||||
app = MedTrackerApp(root_window)
|
||||
|
||||
assert app.filename == "thechart_data.csv"
|
||||
|
||||
@patch('main.LOG_LEVEL', 'DEBUG')
|
||||
def test_debug_logging(self, root_window, mock_managers):
|
||||
"""Test debug logging when LOG_LEVEL is DEBUG."""
|
||||
with patch('sys.argv', ['main.py', 'test.csv']), \
|
||||
patch('os.path.exists', return_value=True), \
|
||||
patch('main.logger') as mock_logger:
|
||||
|
||||
app = MedTrackerApp(root_window)
|
||||
|
||||
# Check that debug messages were logged
|
||||
mock_logger.debug.assert_called()
|
||||
|
||||
def test_setup_main_ui_components(self, root_window, mock_managers):
|
||||
"""Test that main UI components are set up correctly."""
|
||||
with patch('sys.argv', ['main.py']):
|
||||
app = MedTrackerApp(root_window)
|
||||
|
||||
# Check that managers were instantiated
|
||||
mock_managers['ui'].assert_called()
|
||||
mock_managers['data'].assert_called()
|
||||
|
||||
def test_icon_setup(self, root_window, mock_managers):
|
||||
"""Test icon setup functionality."""
|
||||
with patch('sys.argv', ['main.py']), \
|
||||
patch('os.path.exists', return_value=True):
|
||||
|
||||
app = MedTrackerApp(root_window)
|
||||
|
||||
# Check that setup_icon was called on UI manager
|
||||
app.ui_manager.setup_icon.assert_called()
|
||||
|
||||
def test_icon_setup_fallback_path(self, root_window, mock_managers):
|
||||
"""Test icon setup with fallback path."""
|
||||
def mock_exists(path):
|
||||
return path == "./chart-671.png"
|
||||
|
||||
with patch('sys.argv', ['main.py']), \
|
||||
patch('os.path.exists', side_effect=mock_exists):
|
||||
|
||||
app = MedTrackerApp(root_window)
|
||||
|
||||
# Check that setup_icon was called with fallback path
|
||||
app.ui_manager.setup_icon.assert_called_with(img_path="./chart-671.png")
|
||||
|
||||
def test_add_entry_success(self, root_window, mock_managers):
|
||||
"""Test successful entry addition."""
|
||||
with patch('sys.argv', ['main.py']):
|
||||
app = MedTrackerApp(root_window)
|
||||
|
||||
# Mock the UI variables
|
||||
app.date_var = Mock()
|
||||
app.date_var.get.return_value = "2024-01-01"
|
||||
app.symptom_vars = {
|
||||
"depression": Mock(), "anxiety": Mock(),
|
||||
"sleep": Mock(), "appetite": Mock()
|
||||
}
|
||||
for var in app.symptom_vars.values():
|
||||
var.get.return_value = 3
|
||||
|
||||
app.medicine_vars = {
|
||||
"bupropion": [Mock()], "hydroxyzine": [Mock()],
|
||||
"gabapentin": [Mock()], "propranolol": [Mock()]
|
||||
}
|
||||
for med_var in app.medicine_vars.values():
|
||||
med_var[0].get.return_value = 1
|
||||
|
||||
app.note_var = Mock()
|
||||
app.note_var.get.return_value = "Test note"
|
||||
|
||||
# Mock data manager to return success
|
||||
app.data_manager.add_entry.return_value = True
|
||||
|
||||
with patch('tkinter.messagebox.showinfo') as mock_info, \
|
||||
patch.object(app, '_clear_entries') as mock_clear, \
|
||||
patch.object(app, 'load_data') as mock_load:
|
||||
|
||||
app.add_entry()
|
||||
|
||||
mock_info.assert_called_once()
|
||||
mock_clear.assert_called_once()
|
||||
mock_load.assert_called_once()
|
||||
|
||||
def test_add_entry_empty_date(self, root_window, mock_managers):
|
||||
"""Test adding entry with empty date."""
|
||||
with patch('sys.argv', ['main.py']):
|
||||
app = MedTrackerApp(root_window)
|
||||
|
||||
app.date_var = Mock()
|
||||
app.date_var.get.return_value = " " # Empty/whitespace date
|
||||
|
||||
with patch('tkinter.messagebox.showerror') as mock_error:
|
||||
app.add_entry()
|
||||
|
||||
mock_error.assert_called_once_with(
|
||||
"Error", "Please enter a date.", parent=app.root
|
||||
)
|
||||
|
||||
def test_add_entry_duplicate_date(self, root_window, mock_managers):
|
||||
"""Test adding entry with duplicate date."""
|
||||
with patch('sys.argv', ['main.py']):
|
||||
app = MedTrackerApp(root_window)
|
||||
|
||||
# Set up UI variables
|
||||
app.date_var = Mock()
|
||||
app.date_var.get.return_value = "2024-01-01"
|
||||
app.symptom_vars = {"depression": Mock(), "anxiety": Mock(),
|
||||
"sleep": Mock(), "appetite": Mock()}
|
||||
for var in app.symptom_vars.values():
|
||||
var.get.return_value = 3
|
||||
app.medicine_vars = {"bupropion": [Mock()], "hydroxyzine": [Mock()],
|
||||
"gabapentin": [Mock()], "propranolol": [Mock()]}
|
||||
for med_var in app.medicine_vars.values():
|
||||
med_var[0].get.return_value = 1
|
||||
app.note_var = Mock()
|
||||
app.note_var.get.return_value = "Test"
|
||||
|
||||
# Mock data manager to return failure (duplicate)
|
||||
app.data_manager.add_entry.return_value = False
|
||||
|
||||
# Mock load_data to return DataFrame with existing date
|
||||
mock_df = pd.DataFrame({'date': ['2024-01-01']})
|
||||
app.data_manager.load_data.return_value = mock_df
|
||||
|
||||
with patch('tkinter.messagebox.showerror') as mock_error:
|
||||
app.add_entry()
|
||||
|
||||
mock_error.assert_called_once()
|
||||
assert "already exists" in mock_error.call_args[0][1]
|
||||
|
||||
def test_on_double_click(self, root_window, mock_managers):
|
||||
"""Test double-click event handling."""
|
||||
with patch('sys.argv', ['main.py']):
|
||||
app = MedTrackerApp(root_window)
|
||||
|
||||
# Mock tree with selection
|
||||
app.tree = Mock()
|
||||
app.tree.get_children.return_value = ['item1']
|
||||
app.tree.selection.return_value = ['item1']
|
||||
app.tree.item.return_value = {'values': ('2024-01-01', '3', '2', '4', '3', '1', '0', '2', '1', 'Note')}
|
||||
|
||||
mock_event = Mock()
|
||||
|
||||
with patch.object(app, '_create_edit_window') as mock_create_edit:
|
||||
app.on_double_click(mock_event)
|
||||
|
||||
mock_create_edit.assert_called_once()
|
||||
|
||||
def test_on_double_click_empty_tree(self, root_window, mock_managers):
|
||||
"""Test double-click when tree is empty."""
|
||||
with patch('sys.argv', ['main.py']):
|
||||
app = MedTrackerApp(root_window)
|
||||
|
||||
app.tree = Mock()
|
||||
app.tree.get_children.return_value = []
|
||||
|
||||
mock_event = Mock()
|
||||
|
||||
with patch.object(app, '_create_edit_window') as mock_create_edit:
|
||||
app.on_double_click(mock_event)
|
||||
|
||||
mock_create_edit.assert_not_called()
|
||||
|
||||
def test_save_edit_success(self, root_window, mock_managers):
|
||||
"""Test successful save edit operation."""
|
||||
with patch('sys.argv', ['main.py']):
|
||||
app = MedTrackerApp(root_window)
|
||||
|
||||
# Mock edit window
|
||||
mock_edit_win = Mock()
|
||||
|
||||
# Mock data manager to return success
|
||||
app.data_manager.update_entry.return_value = True
|
||||
|
||||
with patch('tkinter.messagebox.showinfo') as mock_info, \
|
||||
patch.object(app, '_clear_entries') as mock_clear, \
|
||||
patch.object(app, 'load_data') as mock_load:
|
||||
|
||||
app._save_edit(
|
||||
mock_edit_win, "2024-01-01", "2024-01-01",
|
||||
3, 2, 4, 3, 1, 0, 2, 1, "Updated note"
|
||||
)
|
||||
|
||||
mock_edit_win.destroy.assert_called_once()
|
||||
mock_info.assert_called_once()
|
||||
mock_clear.assert_called_once()
|
||||
mock_load.assert_called_once()
|
||||
|
||||
def test_save_edit_duplicate_date(self, root_window, mock_managers):
|
||||
"""Test save edit with duplicate date."""
|
||||
with patch('sys.argv', ['main.py']):
|
||||
app = MedTrackerApp(root_window)
|
||||
|
||||
mock_edit_win = Mock()
|
||||
|
||||
# Mock data manager to return failure
|
||||
app.data_manager.update_entry.return_value = False
|
||||
|
||||
# Mock load_data to return DataFrame with existing date
|
||||
mock_df = pd.DataFrame({'date': ['2024-01-02']})
|
||||
app.data_manager.load_data.return_value = mock_df
|
||||
|
||||
with patch('tkinter.messagebox.showerror') as mock_error:
|
||||
app._save_edit(
|
||||
mock_edit_win, "2024-01-01", "2024-01-02", # Different dates
|
||||
3, 2, 4, 3, 1, 0, 2, 1, "Updated note"
|
||||
)
|
||||
|
||||
mock_error.assert_called_once()
|
||||
assert "already exists" in mock_error.call_args[0][1]
|
||||
|
||||
def test_delete_entry_success(self, root_window, mock_managers):
|
||||
"""Test successful entry deletion."""
|
||||
with patch('sys.argv', ['main.py']):
|
||||
app = MedTrackerApp(root_window)
|
||||
|
||||
mock_edit_win = Mock()
|
||||
app.tree = Mock()
|
||||
app.tree.item.return_value = {'values': ['2024-01-01']}
|
||||
|
||||
# Mock data manager to return success
|
||||
app.data_manager.delete_entry.return_value = True
|
||||
|
||||
with patch('tkinter.messagebox.askyesno', return_value=True) as mock_confirm, \
|
||||
patch('tkinter.messagebox.showinfo') as mock_info, \
|
||||
patch.object(app, 'load_data') as mock_load:
|
||||
|
||||
app._delete_entry(mock_edit_win, 'item1')
|
||||
|
||||
mock_confirm.assert_called_once()
|
||||
mock_edit_win.destroy.assert_called_once()
|
||||
mock_info.assert_called_once()
|
||||
mock_load.assert_called_once()
|
||||
|
||||
def test_delete_entry_cancelled(self, root_window, mock_managers):
|
||||
"""Test deletion when user cancels."""
|
||||
with patch('sys.argv', ['main.py']):
|
||||
app = MedTrackerApp(root_window)
|
||||
|
||||
mock_edit_win = Mock()
|
||||
|
||||
with patch('tkinter.messagebox.askyesno', return_value=False) as mock_confirm:
|
||||
app._delete_entry(mock_edit_win, 'item1')
|
||||
|
||||
mock_confirm.assert_called_once()
|
||||
mock_edit_win.destroy.assert_not_called()
|
||||
|
||||
def test_clear_entries(self, root_window, mock_managers):
|
||||
"""Test clearing input entries."""
|
||||
with patch('sys.argv', ['main.py']):
|
||||
app = MedTrackerApp(root_window)
|
||||
|
||||
# Mock variables
|
||||
app.date_var = Mock()
|
||||
app.symptom_vars = {"depression": Mock(), "anxiety": Mock()}
|
||||
app.medicine_vars = {"bupropion": [Mock()], "hydroxyzine": [Mock()]}
|
||||
app.note_var = Mock()
|
||||
|
||||
app._clear_entries()
|
||||
|
||||
app.date_var.set.assert_called_with("")
|
||||
app.note_var.set.assert_called_with("")
|
||||
for var in app.symptom_vars.values():
|
||||
var.set.assert_called_with(0)
|
||||
for med_var in app.medicine_vars.values():
|
||||
med_var[0].set.assert_called_with(0)
|
||||
|
||||
def test_load_data(self, root_window, mock_managers):
|
||||
"""Test loading data into tree and graph."""
|
||||
with patch('sys.argv', ['main.py']):
|
||||
app = MedTrackerApp(root_window)
|
||||
|
||||
# Mock tree
|
||||
app.tree = Mock()
|
||||
app.tree.get_children.return_value = ['item1', 'item2']
|
||||
|
||||
# Mock data
|
||||
mock_df = pd.DataFrame({
|
||||
'date': ['2024-01-01', '2024-01-02'],
|
||||
'depression': [3, 2],
|
||||
'note': ['Note1', 'Note2']
|
||||
})
|
||||
app.data_manager.load_data.return_value = mock_df
|
||||
|
||||
app.load_data()
|
||||
|
||||
# Check that tree was cleared and populated
|
||||
app.tree.delete.assert_called()
|
||||
app.tree.insert.assert_called()
|
||||
|
||||
# Check that graph was updated
|
||||
app.graph_manager.update_graph.assert_called_with(mock_df)
|
||||
|
||||
def test_load_data_empty_dataframe(self, root_window, mock_managers):
|
||||
"""Test loading empty data."""
|
||||
with patch('sys.argv', ['main.py']):
|
||||
app = MedTrackerApp(root_window)
|
||||
|
||||
app.tree = Mock()
|
||||
app.tree.get_children.return_value = []
|
||||
|
||||
# Mock empty DataFrame
|
||||
empty_df = pd.DataFrame()
|
||||
app.data_manager.load_data.return_value = empty_df
|
||||
|
||||
app.load_data()
|
||||
|
||||
# Graph should still be updated even with empty data
|
||||
app.graph_manager.update_graph.assert_called_with(empty_df)
|
||||
|
||||
def test_on_closing_confirmed(self, root_window, mock_managers):
|
||||
"""Test application closing when confirmed."""
|
||||
with patch('sys.argv', ['main.py']):
|
||||
app = MedTrackerApp(root_window)
|
||||
|
||||
with patch('tkinter.messagebox.askokcancel', return_value=True) as mock_confirm:
|
||||
app.on_closing()
|
||||
|
||||
mock_confirm.assert_called_once()
|
||||
app.graph_manager.close.assert_called_once()
|
||||
|
||||
def test_on_closing_cancelled(self, root_window, mock_managers):
|
||||
"""Test application closing when cancelled."""
|
||||
with patch('sys.argv', ['main.py']):
|
||||
app = MedTrackerApp(root_window)
|
||||
|
||||
with patch('tkinter.messagebox.askokcancel', return_value=False) as mock_confirm:
|
||||
app.on_closing()
|
||||
|
||||
mock_confirm.assert_called_once()
|
||||
app.graph_manager.close.assert_not_called()
|
||||
|
||||
def test_protocol_handler_setup(self, root_window, mock_managers):
|
||||
"""Test that window close protocol is set up."""
|
||||
with patch('sys.argv', ['main.py']):
|
||||
app = MedTrackerApp(root_window)
|
||||
|
||||
# The protocol should be set during initialization
|
||||
# This is more of a structural test
|
||||
assert app.root is root_window
|
||||
|
||||
def test_window_properties(self, root_window, mock_managers):
|
||||
"""Test window properties are set correctly."""
|
||||
with patch('sys.argv', ['main.py']):
|
||||
app = MedTrackerApp(root_window)
|
||||
|
||||
assert root_window.title() == "Thechart - medication tracker"
|
||||
# Note: Testing resizable would require more complex mocking
|
||||
@@ -0,0 +1,307 @@
|
||||
"""
|
||||
Tests for the UIManager class.
|
||||
"""
|
||||
import os
|
||||
import pytest
|
||||
import tkinter as tk
|
||||
from tkinter import ttk
|
||||
from unittest.mock import Mock, patch, MagicMock
|
||||
from datetime import datetime
|
||||
|
||||
import sys
|
||||
sys.path.insert(0, os.path.join(os.path.dirname(__file__), '..', 'src'))
|
||||
|
||||
from ui_manager import UIManager
|
||||
|
||||
|
||||
class TestUIManager:
|
||||
"""Test cases for the UIManager class."""
|
||||
|
||||
@pytest.fixture
|
||||
def root_window(self):
|
||||
"""Create a root window for testing."""
|
||||
root = tk.Tk()
|
||||
yield root
|
||||
root.destroy()
|
||||
|
||||
@pytest.fixture
|
||||
def ui_manager(self, root_window, mock_logger):
|
||||
"""Create a UIManager instance for testing."""
|
||||
return UIManager(root_window, mock_logger)
|
||||
|
||||
def test_init(self, root_window, mock_logger):
|
||||
"""Test UIManager initialization."""
|
||||
ui = UIManager(root_window, mock_logger)
|
||||
assert ui.root == root_window
|
||||
assert ui.logger == mock_logger
|
||||
|
||||
@patch('os.path.exists')
|
||||
@patch('PIL.Image.open')
|
||||
def test_setup_icon_success(self, mock_image_open, mock_exists, ui_manager):
|
||||
"""Test successful icon setup."""
|
||||
mock_exists.return_value = True
|
||||
mock_image = Mock()
|
||||
mock_image.resize.return_value = mock_image
|
||||
mock_image_open.return_value = mock_image
|
||||
|
||||
with patch('PIL.ImageTk.PhotoImage') as mock_photo:
|
||||
mock_photo_instance = Mock()
|
||||
mock_photo.return_value = mock_photo_instance
|
||||
|
||||
result = ui_manager.setup_icon("test_icon.png")
|
||||
|
||||
assert result is True
|
||||
mock_image_open.assert_called_once_with("test_icon.png")
|
||||
mock_image.resize.assert_called_once_with(size=(32, 32), resample=Mock())
|
||||
ui_manager.logger.info.assert_called_with("Trying to load icon from: test_icon.png")
|
||||
|
||||
@patch('os.path.exists')
|
||||
def test_setup_icon_file_not_found(self, mock_exists, ui_manager):
|
||||
"""Test icon setup when file is not found."""
|
||||
mock_exists.return_value = False
|
||||
|
||||
result = ui_manager.setup_icon("nonexistent_icon.png")
|
||||
|
||||
assert result is False
|
||||
ui_manager.logger.warning.assert_called_with("Icon file not found at nonexistent_icon.png")
|
||||
|
||||
@patch('os.path.exists')
|
||||
@patch('PIL.Image.open')
|
||||
def test_setup_icon_exception(self, mock_image_open, mock_exists, ui_manager):
|
||||
"""Test icon setup with exception."""
|
||||
mock_exists.return_value = True
|
||||
mock_image_open.side_effect = Exception("Test error")
|
||||
|
||||
result = ui_manager.setup_icon("test_icon.png")
|
||||
|
||||
assert result is False
|
||||
ui_manager.logger.error.assert_called_with("Error setting up icon: Test error")
|
||||
|
||||
@patch('sys._MEIPASS', '/test/bundle/path', create=True)
|
||||
@patch('os.path.exists')
|
||||
@patch('PIL.Image.open')
|
||||
def test_setup_icon_pyinstaller_bundle(self, mock_image_open, mock_exists, ui_manager):
|
||||
"""Test icon setup in PyInstaller bundle."""
|
||||
# Mock exists to return False for original path, True for bundle path
|
||||
def mock_exists_side_effect(path):
|
||||
if 'test_icon.png' in path and '/test/bundle/path' in path:
|
||||
return True
|
||||
return False
|
||||
|
||||
mock_exists.side_effect = mock_exists_side_effect
|
||||
mock_image = Mock()
|
||||
mock_image.resize.return_value = mock_image
|
||||
mock_image_open.return_value = mock_image
|
||||
|
||||
with patch('PIL.ImageTk.PhotoImage') as mock_photo:
|
||||
mock_photo_instance = Mock()
|
||||
mock_photo.return_value = mock_photo_instance
|
||||
|
||||
result = ui_manager.setup_icon("test_icon.png")
|
||||
|
||||
assert result is True
|
||||
ui_manager.logger.info.assert_called_with("Found icon in PyInstaller bundle: /test/bundle/path/test_icon.png")
|
||||
|
||||
def test_create_graph_frame(self, ui_manager, root_window):
|
||||
"""Test creation of graph frame."""
|
||||
main_frame = ttk.Frame(root_window)
|
||||
|
||||
graph_frame = ui_manager.create_graph_frame(main_frame)
|
||||
|
||||
assert isinstance(graph_frame, ttk.LabelFrame)
|
||||
assert graph_frame.winfo_parent() == str(main_frame)
|
||||
|
||||
def test_create_input_frame(self, ui_manager, root_window):
|
||||
"""Test creation of input frame."""
|
||||
main_frame = ttk.Frame(root_window)
|
||||
|
||||
input_ui = ui_manager.create_input_frame(main_frame)
|
||||
|
||||
assert isinstance(input_ui, dict)
|
||||
assert "frame" in input_ui
|
||||
assert "symptom_vars" in input_ui
|
||||
assert "medicine_vars" in input_ui
|
||||
assert "note_var" in input_ui
|
||||
assert "date_var" in input_ui
|
||||
|
||||
assert isinstance(input_ui["frame"], ttk.LabelFrame)
|
||||
assert isinstance(input_ui["symptom_vars"], dict)
|
||||
assert isinstance(input_ui["medicine_vars"], dict)
|
||||
assert isinstance(input_ui["note_var"], tk.StringVar)
|
||||
assert isinstance(input_ui["date_var"], tk.StringVar)
|
||||
|
||||
def test_create_input_frame_symptom_vars(self, ui_manager, root_window):
|
||||
"""Test that symptom variables are created correctly."""
|
||||
main_frame = ttk.Frame(root_window)
|
||||
|
||||
input_ui = ui_manager.create_input_frame(main_frame)
|
||||
symptom_vars = input_ui["symptom_vars"]
|
||||
|
||||
expected_symptoms = ["depression", "anxiety", "sleep", "appetite"]
|
||||
for symptom in expected_symptoms:
|
||||
assert symptom in symptom_vars
|
||||
assert isinstance(symptom_vars[symptom], tk.IntVar)
|
||||
|
||||
def test_create_input_frame_medicine_vars(self, ui_manager, root_window):
|
||||
"""Test that medicine variables are created correctly."""
|
||||
main_frame = ttk.Frame(root_window)
|
||||
|
||||
input_ui = ui_manager.create_input_frame(main_frame)
|
||||
medicine_vars = input_ui["medicine_vars"]
|
||||
|
||||
expected_medicines = ["bupropion", "hydroxyzine", "gabapentin", "propranolol"]
|
||||
for medicine in expected_medicines:
|
||||
assert medicine in medicine_vars
|
||||
assert isinstance(medicine_vars[medicine], list)
|
||||
assert len(medicine_vars[medicine]) == 2 # IntVar and Spinbox
|
||||
assert isinstance(medicine_vars[medicine][0], tk.IntVar)
|
||||
assert isinstance(medicine_vars[medicine][1], ttk.Spinbox)
|
||||
|
||||
@patch('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 = "2024-01-15"
|
||||
|
||||
main_frame = ttk.Frame(root_window)
|
||||
input_ui = ui_manager.create_input_frame(main_frame)
|
||||
|
||||
assert input_ui["date_var"].get() == "2024-01-15"
|
||||
|
||||
def test_create_table_frame(self, ui_manager, root_window):
|
||||
"""Test creation of table frame."""
|
||||
main_frame = ttk.Frame(root_window)
|
||||
|
||||
table_ui = ui_manager.create_table_frame(main_frame)
|
||||
|
||||
assert isinstance(table_ui, dict)
|
||||
assert "tree" in table_ui
|
||||
assert isinstance(table_ui["tree"], ttk.Treeview)
|
||||
|
||||
def test_create_table_frame_columns(self, ui_manager, root_window):
|
||||
"""Test that table columns are set up correctly."""
|
||||
main_frame = ttk.Frame(root_window)
|
||||
|
||||
table_ui = ui_manager.create_table_frame(main_frame)
|
||||
tree = table_ui["tree"]
|
||||
|
||||
expected_columns = [
|
||||
"date", "depression", "anxiety", "sleep", "appetite",
|
||||
"bupropion", "hydroxyzine", "gabapentin", "propranolol", "note"
|
||||
]
|
||||
|
||||
# Check that columns are configured
|
||||
assert tree["columns"] == tuple(expected_columns)
|
||||
|
||||
def test_add_buttons(self, ui_manager, root_window):
|
||||
"""Test adding buttons to a frame."""
|
||||
frame = ttk.Frame(root_window)
|
||||
|
||||
buttons_config = [
|
||||
{"text": "Test Button 1", "command": lambda: None},
|
||||
{"text": "Test Button 2", "command": lambda: None, "fill": "x"},
|
||||
]
|
||||
|
||||
ui_manager.add_buttons(frame, buttons_config)
|
||||
|
||||
# Check that buttons were added (basic structure test)
|
||||
children = frame.winfo_children()
|
||||
assert len(children) >= 2
|
||||
|
||||
def test_create_edit_window(self, ui_manager):
|
||||
"""Test creation of edit window."""
|
||||
values = ("2024-01-01", "3", "2", "4", "3", "1", "0", "2", "1", "Test note")
|
||||
callbacks = {
|
||||
"save": lambda win, *args: None,
|
||||
"delete": lambda win: None
|
||||
}
|
||||
|
||||
edit_window = ui_manager.create_edit_window(values, callbacks)
|
||||
|
||||
assert isinstance(edit_window, tk.Toplevel)
|
||||
assert edit_window.title() == "Edit Entry"
|
||||
|
||||
def test_create_edit_window_widgets(self, ui_manager):
|
||||
"""Test that edit window contains expected widgets."""
|
||||
values = ("2024-01-01", "3", "2", "4", "3", "1", "0", "2", "1", "Test note")
|
||||
callbacks = {
|
||||
"save": lambda win, *args: None,
|
||||
"delete": lambda win: None
|
||||
}
|
||||
|
||||
edit_window = ui_manager.create_edit_window(values, callbacks)
|
||||
|
||||
# Check that window has children (widgets)
|
||||
children = edit_window.winfo_children()
|
||||
assert len(children) > 0
|
||||
|
||||
def test_create_edit_window_initial_values(self, ui_manager):
|
||||
"""Test that edit window is populated with initial values."""
|
||||
values = ("2024-01-01", "3", "2", "4", "3", "1", "0", "2", "1", "Test note")
|
||||
callbacks = {
|
||||
"save": lambda win, *args: None,
|
||||
"delete": lambda win: None
|
||||
}
|
||||
|
||||
edit_window = ui_manager.create_edit_window(values, callbacks)
|
||||
|
||||
# The window should be created successfully
|
||||
assert edit_window is not None
|
||||
# More detailed testing would require examining the internal widgets
|
||||
|
||||
def test_create_scale_with_var(self, ui_manager, root_window):
|
||||
"""Test creation of scale widget with variable."""
|
||||
frame = ttk.Frame(root_window)
|
||||
var = tk.IntVar()
|
||||
|
||||
scale = ui_manager._create_scale_with_var(frame, var, "Test Label", 0, 0)
|
||||
|
||||
assert isinstance(scale, ttk.Scale)
|
||||
|
||||
def test_create_spinbox_with_var(self, ui_manager, root_window):
|
||||
"""Test creation of spinbox widget with variable."""
|
||||
frame = ttk.Frame(root_window)
|
||||
var = tk.IntVar()
|
||||
|
||||
result = ui_manager._create_spinbox_with_var(frame, var, "Test Label", 0, 0)
|
||||
|
||||
assert isinstance(result, list)
|
||||
assert len(result) == 2
|
||||
assert isinstance(result[0], tk.IntVar)
|
||||
assert isinstance(result[1], ttk.Spinbox)
|
||||
|
||||
def test_frame_positioning(self, ui_manager, root_window):
|
||||
"""Test that frames are positioned correctly."""
|
||||
main_frame = ttk.Frame(root_window)
|
||||
|
||||
# Create multiple frames
|
||||
graph_frame = ui_manager.create_graph_frame(main_frame)
|
||||
input_ui = ui_manager.create_input_frame(main_frame)
|
||||
table_ui = ui_manager.create_table_frame(main_frame)
|
||||
|
||||
# All frames should be created successfully
|
||||
assert graph_frame is not None
|
||||
assert input_ui["frame"] is not None
|
||||
assert table_ui["tree"] is not None
|
||||
|
||||
def test_widget_configuration(self, ui_manager, root_window):
|
||||
"""Test that widgets are configured with appropriate properties."""
|
||||
main_frame = ttk.Frame(root_window)
|
||||
input_ui = ui_manager.create_input_frame(main_frame)
|
||||
|
||||
# Check that variables have default values
|
||||
for var in input_ui["symptom_vars"].values():
|
||||
assert var.get() == 0
|
||||
|
||||
for medicine_data in input_ui["medicine_vars"].values():
|
||||
assert medicine_data[0].get() == 0
|
||||
|
||||
@patch('tkinter.messagebox.showerror')
|
||||
def test_error_handling_in_setup_icon(self, mock_showerror, ui_manager):
|
||||
"""Test error handling in setup_icon method."""
|
||||
with patch('PIL.Image.open') as mock_open:
|
||||
mock_open.side_effect = Exception("Image error")
|
||||
|
||||
result = ui_manager.setup_icon("test.png")
|
||||
|
||||
assert result is False
|
||||
ui_manager.logger.error.assert_called()
|
||||
Reference in New Issue
Block a user