Some checks failed
Build and Push Docker Image / build-and-push (push) Has been cancelled
467 lines
18 KiB
Python
467 lines
18 KiB
Python
"""
|
|
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 src.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)
|
|
|
|
# Check symptom toggles
|
|
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 medicine toggles
|
|
assert "bupropion" in gm.toggle_vars
|
|
assert "hydroxyzine" in gm.toggle_vars
|
|
assert "gabapentin" in gm.toggle_vars
|
|
assert "propranolol" in gm.toggle_vars
|
|
assert "quetiapine" in gm.toggle_vars
|
|
|
|
# Check that symptom toggles are initially True
|
|
for symptom in ["depression", "anxiety", "sleep", "appetite"]:
|
|
assert gm.toggle_vars[symptom].get() is True
|
|
|
|
# Check that some medicine toggles are True by default
|
|
assert gm.toggle_vars["bupropion"].get() is True
|
|
assert gm.toggle_vars["propranolol"].get() is True
|
|
|
|
# Check that some medicine toggles are False by default
|
|
assert gm.toggle_vars["hydroxyzine"].get() is False
|
|
assert gm.toggle_vars["gabapentin"].get() is False
|
|
assert gm.toggle_vars["quetiapine"].get() is False
|
|
|
|
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 all toggle variables exist
|
|
expected_toggles = ["depression", "anxiety", "sleep", "appetite",
|
|
"bupropion", "hydroxyzine", "gabapentin", "propranolol", "quetiapine"]
|
|
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
|
|
|
|
def test_calculate_daily_dose_empty_input(self, parent_frame):
|
|
"""Test dose calculation with empty/invalid input."""
|
|
gm = GraphManager(parent_frame)
|
|
|
|
# Test empty string
|
|
assert gm._calculate_daily_dose("") == 0.0
|
|
|
|
# Test NaN values
|
|
assert gm._calculate_daily_dose("nan") == 0.0
|
|
assert gm._calculate_daily_dose("NaN") == 0.0
|
|
|
|
# Test None (will be converted to string)
|
|
assert gm._calculate_daily_dose(None) == 0.0
|
|
|
|
def test_calculate_daily_dose_standard_format(self, parent_frame):
|
|
"""Test dose calculation with standard timestamp:dose format."""
|
|
gm = GraphManager(parent_frame)
|
|
|
|
# Single dose
|
|
dose_str = "2025-07-28 18:59:45:150mg"
|
|
assert gm._calculate_daily_dose(dose_str) == 150.0
|
|
|
|
# Multiple doses
|
|
dose_str = "2025-07-28 18:59:45:150mg|2025-07-28 19:34:19:75mg"
|
|
assert gm._calculate_daily_dose(dose_str) == 225.0
|
|
|
|
# Doses without units
|
|
dose_str = "2025-07-28 18:59:45:10|2025-07-28 19:34:19:5"
|
|
assert gm._calculate_daily_dose(dose_str) == 15.0
|
|
|
|
def test_calculate_daily_dose_with_symbols(self, parent_frame):
|
|
"""Test dose calculation with bullet symbols."""
|
|
gm = GraphManager(parent_frame)
|
|
|
|
# With bullet symbols
|
|
dose_str = "• • • • 2025-07-30 07:50:00:300"
|
|
assert gm._calculate_daily_dose(dose_str) == 300.0
|
|
|
|
# Multiple bullets
|
|
dose_str = "• 2025-07-30 22:50:00:10|• 2025-07-30 23:50:00:5"
|
|
assert gm._calculate_daily_dose(dose_str) == 15.0
|
|
|
|
def test_calculate_daily_dose_no_timestamp(self, parent_frame):
|
|
"""Test dose calculation without timestamp."""
|
|
gm = GraphManager(parent_frame)
|
|
|
|
# Just dose value
|
|
dose_str = "150mg"
|
|
assert gm._calculate_daily_dose(dose_str) == 150.0
|
|
|
|
# Multiple values without timestamp
|
|
dose_str = "100|50"
|
|
assert gm._calculate_daily_dose(dose_str) == 150.0
|
|
|
|
def test_calculate_daily_dose_decimal_values(self, parent_frame):
|
|
"""Test dose calculation with decimal values."""
|
|
gm = GraphManager(parent_frame)
|
|
|
|
# Decimal dose
|
|
dose_str = "2025-07-28 18:59:45:12.5mg"
|
|
assert gm._calculate_daily_dose(dose_str) == 12.5
|
|
|
|
# Multiple decimal doses
|
|
dose_str = "2025-07-28 18:59:45:12.5mg|2025-07-28 19:34:19:7.5mg"
|
|
assert gm._calculate_daily_dose(dose_str) == 20.0
|
|
|
|
def test_medicine_dose_plotting(self, parent_frame):
|
|
"""Test that medicine doses are plotted correctly."""
|
|
# Create a DataFrame with dose data
|
|
df_with_doses = pd.DataFrame({
|
|
'date': ['2024-01-01', '2024-01-02', '2024-01-03'],
|
|
'depression': [3, 2, 4],
|
|
'anxiety': [2, 3, 1],
|
|
'sleep': [4, 3, 5],
|
|
'appetite': [3, 4, 2],
|
|
'bupropion': [1, 1, 0],
|
|
'bupropion_doses': ['2024-01-01 08:00:00:150mg', '2024-01-02 08:00:00:300mg', ''],
|
|
'hydroxyzine': [0, 1, 0],
|
|
'hydroxyzine_doses': ['', '2024-01-02 20:00:00:25mg', ''],
|
|
'gabapentin': [0, 0, 0],
|
|
'gabapentin_doses': ['', '', ''],
|
|
'propranolol': [1, 0, 1],
|
|
'propranolol_doses': ['2024-01-01 12:00:00:10mg', '', '2024-01-03 12:00:00:20mg'],
|
|
'quetiapine': [0, 0, 0],
|
|
'quetiapine_doses': ['', '', ''],
|
|
})
|
|
|
|
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(df_with_doses)
|
|
|
|
# Verify that bar plots were called (for medicines with doses)
|
|
mock_ax.bar.assert_called()
|
|
|
|
# Verify canvas was redrawn
|
|
mock_canvas.draw.assert_called()
|
|
|
|
def test_medicine_toggle_functionality(self, parent_frame):
|
|
"""Test that medicine toggles affect dose display."""
|
|
df_with_doses = pd.DataFrame({
|
|
'date': ['2024-01-01'],
|
|
'depression': [3],
|
|
'anxiety': [2],
|
|
'sleep': [4],
|
|
'appetite': [3],
|
|
'bupropion': [1],
|
|
'bupropion_doses': ['2024-01-01 08:00:00:150mg'],
|
|
'hydroxyzine': [0],
|
|
'hydroxyzine_doses': [''],
|
|
'gabapentin': [0],
|
|
'gabapentin_doses': [''],
|
|
'propranolol': [1],
|
|
'propranolol_doses': ['2024-01-01 12:00:00:10mg'],
|
|
'quetiapine': [0],
|
|
'quetiapine_doses': [''],
|
|
})
|
|
|
|
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 bupropion toggle
|
|
gm.toggle_vars["bupropion"].set(False)
|
|
gm.update_graph(df_with_doses)
|
|
|
|
# Turn on hydroxyzine toggle (though it has no doses)
|
|
gm.toggle_vars["hydroxyzine"].set(True)
|
|
gm.update_graph(df_with_doses)
|
|
|
|
# Verify the graph was updated
|
|
assert mock_ax.clear.call_count >= 2
|
|
assert mock_canvas.draw.call_count >= 2
|
|
|
|
def test_dose_calculation_comprehensive(self, parent_frame, sample_dose_data):
|
|
"""Test dose calculation with comprehensive test cases."""
|
|
gm = GraphManager(parent_frame)
|
|
|
|
# Test all sample dose data cases
|
|
assert gm._calculate_daily_dose(sample_dose_data['standard_format']) == 225.0
|
|
assert gm._calculate_daily_dose(sample_dose_data['with_bullets']) == 300.0
|
|
assert gm._calculate_daily_dose(sample_dose_data['decimal_doses']) == 20.0
|
|
assert gm._calculate_daily_dose(sample_dose_data['no_timestamp']) == 150.0
|
|
assert gm._calculate_daily_dose(sample_dose_data['mixed_format']) == 85.0
|
|
assert gm._calculate_daily_dose(sample_dose_data['empty_string']) == 0.0
|
|
assert gm._calculate_daily_dose(sample_dose_data['nan_value']) == 0.0
|
|
assert gm._calculate_daily_dose(sample_dose_data['no_units']) == 15.0
|
|
|
|
def test_dose_calculation_edge_cases(self, parent_frame):
|
|
"""Test dose calculation with edge cases."""
|
|
gm = GraphManager(parent_frame)
|
|
|
|
# Test with malformed data
|
|
assert gm._calculate_daily_dose("malformed:data") == 0.0
|
|
assert gm._calculate_daily_dose("::::") == 0.0
|
|
assert gm._calculate_daily_dose("2025-07-28:") == 0.0
|
|
assert gm._calculate_daily_dose("2025-07-28::mg") == 0.0
|
|
|
|
# Test with partial data
|
|
assert gm._calculate_daily_dose("2025-07-28 18:59:45:150") == 150.0 # no units
|
|
assert gm._calculate_daily_dose("150mg") == 150.0 # no timestamp
|
|
|
|
# Test with spaces and special characters
|
|
assert gm._calculate_daily_dose(" 2025-07-28 18:59:45:150mg ") == 150.0
|
|
assert gm._calculate_daily_dose("••• 2025-07-28 18:59:45:150mg •••") == 150.0
|