412 lines
16 KiB
Python
412 lines
16 KiB
Python
"""
|
|
Tests for the main application and MedTrackerApp class.
|
|
"""
|
|
import os
|
|
import pytest
|
|
import tkinter as tk
|
|
from unittest.mock import Mock, patch
|
|
import pandas as pd
|
|
|
|
import sys
|
|
sys.path.insert(0, os.path.join(os.path.dirname(__file__), '..', 'src'))
|
|
|
|
from src.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_application_icon was called on UI manager
|
|
app.ui_manager.setup_application_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_application_icon was called with fallback path
|
|
app.ui_manager.setup_application_icon.assert_called_with(img_path="./chart-671.png")
|
|
|
|
def test_add_new_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, 'refresh_data_display') as mock_load:
|
|
|
|
app.add_new_entry()
|
|
|
|
mock_info.assert_called_once()
|
|
mock_clear.assert_called_once()
|
|
mock_load.assert_called_once()
|
|
|
|
def test_add_new_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_new_entry()
|
|
|
|
mock_error.assert_called_once_with(
|
|
"Error", "Please enter a date.", parent=app.root
|
|
)
|
|
|
|
def test_add_new_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_new_entry()
|
|
|
|
mock_error.assert_called_once()
|
|
assert "already exists" in mock_error.call_args[0][1]
|
|
|
|
def test_handle_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.handle_double_click(mock_event)
|
|
|
|
mock_create_edit.assert_called_once()
|
|
|
|
def test_handle_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.handle_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, 'refresh_data_display') 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, 'refresh_data_display') 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_refresh_data_display(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.refresh_data_display()
|
|
|
|
# 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_refresh_data_display_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.refresh_data_display()
|
|
|
|
# Graph should still be updated even with empty data
|
|
app.graph_manager.update_graph.assert_called_with(empty_df)
|
|
|
|
def test_handle_window_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.handle_window_closing()
|
|
|
|
mock_confirm.assert_called_once()
|
|
app.graph_manager.close.assert_called_once()
|
|
|
|
def test_handle_window_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.handle_window_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
|