""" Tests for the ExportManager class. """ import os import tempfile import pytest from pathlib import Path from unittest.mock import Mock, patch, MagicMock import pandas as pd import sys sys.path.insert(0, os.path.join(os.path.dirname(__file__), '..', 'src')) from src.export_manager import ExportManager class TestExportManager: """Test cases for the ExportManager class.""" @pytest.fixture def mock_data_manager(self): """Create a mock data manager with sample data.""" mock_dm = Mock() sample_data = pd.DataFrame({ 'date': ['2025-01-01', '2025-01-02'], 'depression': [5, 6], 'anxiety': [3, 4], 'bupropion': [1, 0], 'bupropion_doses': ['09:00:150mg', ''], 'note': ['feeling better', 'neutral day'] }) mock_dm.load_data.return_value = sample_data return mock_dm @pytest.fixture def mock_graph_manager(self): """Create a mock graph manager.""" mock_gm = Mock() mock_fig = Mock() mock_gm.fig = mock_fig mock_gm.update_graph = Mock() return mock_gm @pytest.fixture def mock_medicine_manager(self): """Create a mock medicine manager.""" mock_mm = Mock() mock_mm.get_medicine_keys.return_value = ['bupropion', 'hydroxyzine'] return mock_mm @pytest.fixture def mock_pathology_manager(self): """Create a mock pathology manager.""" mock_pm = Mock() mock_pm.get_pathology_keys.return_value = ['depression', 'anxiety'] return mock_pm @pytest.fixture def export_manager(self, mock_data_manager, mock_graph_manager, mock_medicine_manager, mock_pathology_manager, mock_logger): """Create an ExportManager instance with mocked dependencies.""" return ExportManager( mock_data_manager, mock_graph_manager, mock_medicine_manager, mock_pathology_manager, mock_logger ) def test_init(self, export_manager, mock_data_manager, mock_graph_manager, mock_medicine_manager, mock_pathology_manager, mock_logger): """Test ExportManager initialization.""" assert export_manager.data_manager == mock_data_manager assert export_manager.graph_manager == mock_graph_manager assert export_manager.medicine_manager == mock_medicine_manager assert export_manager.pathology_manager == mock_pathology_manager assert export_manager.logger == mock_logger def test_export_data_to_json_success(self, export_manager): """Test successful JSON export.""" with tempfile.NamedTemporaryFile(mode='w', suffix='.json', delete=False) as f: temp_path = f.name try: result = export_manager.export_data_to_json(temp_path) assert result is True assert os.path.exists(temp_path) # Verify file content import json with open(temp_path, 'r') as f: data = json.load(f) assert 'metadata' in data assert 'entries' in data assert data['metadata']['total_entries'] == 2 assert len(data['entries']) == 2 finally: if os.path.exists(temp_path): os.unlink(temp_path) def test_export_data_to_json_empty_data(self, export_manager): """Test JSON export with empty data.""" export_manager.data_manager.load_data.return_value = pd.DataFrame() with tempfile.NamedTemporaryFile(mode='w', suffix='.json', delete=False) as f: temp_path = f.name try: result = export_manager.export_data_to_json(temp_path) assert result is False export_manager.logger.warning.assert_called_with("No data to export") finally: if os.path.exists(temp_path): os.unlink(temp_path) def test_export_data_to_xml_success(self, export_manager): """Test successful XML export.""" with tempfile.NamedTemporaryFile(mode='w', suffix='.xml', delete=False) as f: temp_path = f.name try: result = export_manager.export_data_to_xml(temp_path) assert result is True assert os.path.exists(temp_path) # Verify file content with open(temp_path, 'r') as f: content = f.read() assert 'thechart_data' in content assert 'metadata' in content assert 'entries' in content finally: if os.path.exists(temp_path): os.unlink(temp_path) def test_export_data_to_xml_empty_data(self, export_manager): """Test XML export with empty data.""" export_manager.data_manager.load_data.return_value = pd.DataFrame() with tempfile.NamedTemporaryFile(mode='w', suffix='.xml', delete=False) as f: temp_path = f.name try: result = export_manager.export_data_to_xml(temp_path) assert result is False export_manager.logger.warning.assert_called_with("No data to export") finally: if os.path.exists(temp_path): os.unlink(temp_path) @patch('matplotlib.pyplot.draw') @patch('matplotlib.pyplot.pause') def test_save_graph_as_image_success(self, mock_pause, mock_draw, export_manager): """Test successful graph image saving.""" with tempfile.TemporaryDirectory() as temp_dir: temp_path = Path(temp_dir) # Mock the savefig method export_manager.graph_manager.fig.savefig = Mock() # Create a dummy image file to simulate successful save image_path = temp_path / "graph.png" image_path.write_bytes(b"fake image data") # Mock the savefig to create the file def mock_savefig(path, **kwargs): Path(path).write_bytes(b"fake image data") export_manager.graph_manager.fig.savefig.side_effect = mock_savefig result = export_manager._save_graph_as_image(temp_path) assert result is not None assert str(temp_path / "graph.png") in result export_manager.graph_manager.update_graph.assert_called_once() def test_save_graph_as_image_no_graph_manager(self, export_manager): """Test graph image saving with no graph manager.""" export_manager.graph_manager = None with tempfile.TemporaryDirectory() as temp_dir: result = export_manager._save_graph_as_image(Path(temp_dir)) assert result is None export_manager.logger.warning.assert_called_with( "No graph manager available for export" ) def test_save_graph_as_image_no_figure(self, export_manager): """Test graph image saving with no figure.""" export_manager.graph_manager.fig = None with tempfile.TemporaryDirectory() as temp_dir: result = export_manager._save_graph_as_image(Path(temp_dir)) assert result is None export_manager.logger.warning.assert_called_with( "No graph figure available for export" ) def test_save_graph_as_image_empty_data(self, export_manager): """Test graph image saving with empty data.""" export_manager.data_manager.load_data.return_value = pd.DataFrame() with tempfile.TemporaryDirectory() as temp_dir: result = export_manager._save_graph_as_image(Path(temp_dir)) assert result is None export_manager.logger.warning.assert_called_with( "No data available to update graph for export" ) @patch('src.export_manager.ExportManager._save_graph_as_image') @patch('src.export_manager.SimpleDocTemplate') def test_export_to_pdf_success(self, mock_doc, mock_save_graph, export_manager): """Test successful PDF export.""" # Mock graph image saving mock_save_graph.return_value = "/tmp/test_graph.png" # Mock document building mock_doc_instance = Mock() mock_doc.return_value = mock_doc_instance # Mock os.path.exists to return True for the image with patch('os.path.exists', return_value=True): with patch('os.remove'): # Mock cleanup with tempfile.NamedTemporaryFile(suffix='.pdf', delete=False) as f: temp_path = f.name try: result = export_manager.export_to_pdf(temp_path, include_graph=True) assert result is True mock_doc_instance.build.assert_called_once() export_manager.logger.info.assert_called_with( f"Data exported to PDF: {temp_path}" ) finally: if os.path.exists(temp_path): os.unlink(temp_path) @patch('src.export_manager.ExportManager._save_graph_as_image') @patch('src.export_manager.SimpleDocTemplate') def test_export_to_pdf_no_graph(self, mock_doc, mock_save_graph, export_manager): """Test PDF export without graph.""" # Mock document building mock_doc_instance = Mock() mock_doc.return_value = mock_doc_instance with tempfile.NamedTemporaryFile(suffix='.pdf', delete=False) as f: temp_path = f.name try: result = export_manager.export_to_pdf(temp_path, include_graph=False) assert result is True mock_doc_instance.build.assert_called_once() mock_save_graph.assert_not_called() finally: if os.path.exists(temp_path): os.unlink(temp_path) @patch('src.export_manager.SimpleDocTemplate') def test_export_to_pdf_empty_data(self, mock_doc, export_manager): """Test PDF export with empty data.""" export_manager.data_manager.load_data.return_value = pd.DataFrame() # Mock document building mock_doc_instance = Mock() mock_doc.return_value = mock_doc_instance with tempfile.NamedTemporaryFile(suffix='.pdf', delete=False) as f: temp_path = f.name try: result = export_manager.export_to_pdf(temp_path, include_graph=False) assert result is True mock_doc_instance.build.assert_called_once() finally: if os.path.exists(temp_path): os.unlink(temp_path) @patch('src.export_manager.SimpleDocTemplate') def test_export_to_pdf_exception(self, mock_doc, export_manager): """Test PDF export with exception.""" # Mock document building to raise exception mock_doc.side_effect = Exception("PDF generation failed") with tempfile.NamedTemporaryFile(suffix='.pdf', delete=False) as f: temp_path = f.name try: result = export_manager.export_to_pdf(temp_path) assert result is False export_manager.logger.error.assert_called() finally: if os.path.exists(temp_path): os.unlink(temp_path) def test_get_export_info_with_data(self, export_manager): """Test getting export info with data.""" info = export_manager.get_export_info() assert info['total_entries'] == 2 assert info['has_data'] is True assert info['date_range']['start'] == '2025-01-01' assert info['date_range']['end'] == '2025-01-02' assert info['pathologies'] == ['depression', 'anxiety'] assert info['medicines'] == ['bupropion', 'hydroxyzine'] def test_get_export_info_empty_data(self, export_manager): """Test getting export info with empty data.""" export_manager.data_manager.load_data.return_value = pd.DataFrame() info = export_manager.get_export_info() assert info['total_entries'] == 0 assert info['has_data'] is False assert info['date_range']['start'] is None assert info['date_range']['end'] is None class TestExportManagerIntegration: """Integration tests for ExportManager with real-like scenarios.""" @pytest.fixture def real_data_manager(self, temp_csv_file, mock_logger): """Create a data manager with real test data.""" from src.medicine_manager import MedicineManager from src.pathology_manager import PathologyManager from src.data_manager import DataManager # Create managers with real data medicine_manager = MedicineManager(logger=mock_logger) pathology_manager = PathologyManager(logger=mock_logger) # Initialize data manager data_manager = DataManager(temp_csv_file, mock_logger, medicine_manager, pathology_manager) # Add some test data test_entries = [ ['2025-01-01', 5, 3, 6, 7, 1, '09:00:150mg', 0, '', 0, '', 0, '', 0, '', 'feeling better today'], ['2025-01-02', 6, 4, 5, 6, 0, '', 1, '22:00:25mg', 0, '', 0, '', 0, '', 'neutral day'], ['2025-01-03', 4, 2, 7, 8, 1, '09:00:150mg|21:00:150mg', 0, '', 0, '', 0, '', 0, '', 'good sleep, multiple doses'], ] for entry in test_entries: data_manager.add_entry(entry) return data_manager, medicine_manager, pathology_manager @pytest.fixture def real_graph_manager(self, mock_logger): """Create a real graph manager for testing.""" import tkinter as tk import tkinter.ttk as ttk from src.graph_manager import GraphManager from src.medicine_manager import MedicineManager from src.pathology_manager import PathologyManager # Create minimal tkinter setup root = tk.Tk() root.withdraw() # Hide window frame = ttk.Frame(root) medicine_manager = MedicineManager(logger=mock_logger) pathology_manager = PathologyManager(logger=mock_logger) graph_manager = GraphManager(frame, medicine_manager, pathology_manager) # Store root for cleanup graph_manager._test_root = root return graph_manager def test_full_pdf_export_integration(self, real_data_manager, real_graph_manager, mock_logger): """Test complete PDF export with real managers and improved formatting.""" data_manager, medicine_manager, pathology_manager = real_data_manager # Create export manager export_manager = ExportManager( data_manager, real_graph_manager, medicine_manager, pathology_manager, mock_logger ) # Update graph with data df = data_manager.load_data() assert not df.empty, "Test data should be loaded" real_graph_manager.update_graph(df) # Test PDF export with tempfile.NamedTemporaryFile(suffix='.pdf', delete=False) as f: temp_pdf_path = f.name try: success = export_manager.export_to_pdf(temp_pdf_path, include_graph=True) # Verify export success assert success is True, "PDF export should succeed" assert os.path.exists(temp_pdf_path), "PDF file should be created" assert os.path.getsize(temp_pdf_path) > 1000, "PDF should have reasonable size" # Check that info log was called for successful export mock_logger.info.assert_any_call(f"Data exported to PDF: {temp_pdf_path}") finally: # Cleanup if hasattr(real_graph_manager, '_test_root'): real_graph_manager._test_root.destroy() if os.path.exists(temp_pdf_path): os.unlink(temp_pdf_path) def test_pdf_export_with_landscape_format(self, real_data_manager, real_graph_manager, mock_logger): """Test PDF export uses landscape format and proper dimensions.""" data_manager, medicine_manager, pathology_manager = real_data_manager export_manager = ExportManager( data_manager, real_graph_manager, medicine_manager, pathology_manager, mock_logger ) # Update graph with data df = data_manager.load_data() real_graph_manager.update_graph(df) with tempfile.NamedTemporaryFile(suffix='.pdf', delete=False) as f: temp_pdf_path = f.name try: # Mock the SimpleDocTemplate to verify landscape format with patch('src.export_manager.SimpleDocTemplate') as mock_doc: mock_doc_instance = Mock() mock_doc.return_value = mock_doc_instance success = export_manager.export_to_pdf(temp_pdf_path, include_graph=True) # Verify SimpleDocTemplate was called with landscape pagesize mock_doc.assert_called_once() call_args = mock_doc.call_args # Check that landscape format is used from reportlab.lib.pagesizes import landscape, A4 expected_pagesize = landscape(A4) assert call_args[1]['pagesize'] == expected_pagesize finally: if hasattr(real_graph_manager, '_test_root'): real_graph_manager._test_root.destroy() if os.path.exists(temp_pdf_path): os.unlink(temp_pdf_path) def test_pdf_export_table_formatting(self, real_data_manager, real_graph_manager, mock_logger): """Test PDF export uses improved table formatting.""" data_manager, medicine_manager, pathology_manager = real_data_manager export_manager = ExportManager( data_manager, real_graph_manager, medicine_manager, pathology_manager, mock_logger ) df = data_manager.load_data() real_graph_manager.update_graph(df) with tempfile.NamedTemporaryFile(suffix='.pdf', delete=False) as f: temp_pdf_path = f.name try: # Mock Table to verify column widths and styling with patch('src.export_manager.Table') as mock_table: mock_table_instance = Mock() mock_table.return_value = mock_table_instance with patch('src.export_manager.SimpleDocTemplate') as mock_doc: mock_doc_instance = Mock() mock_doc.return_value = mock_doc_instance success = export_manager.export_to_pdf(temp_pdf_path, include_graph=True) # Verify Table was called with column widths mock_table.assert_called() call_args = mock_table.call_args # Check that colWidths parameter is provided assert 'colWidths' in call_args[1] col_widths = call_args[1]['colWidths'] # Verify column widths are reasonable assert len(col_widths) > 0 from reportlab.lib.units import inch assert all(width > 0.5 * inch for width in col_widths) # All columns at least 0.5" finally: if hasattr(real_graph_manager, '_test_root'): real_graph_manager._test_root.destroy() if os.path.exists(temp_pdf_path): os.unlink(temp_pdf_path) def test_pdf_export_with_long_notes(self, real_data_manager, real_graph_manager, mock_logger): """Test PDF export handles long notes without truncation.""" data_manager, medicine_manager, pathology_manager = real_data_manager # Add entry with very long note long_note = "This is a very long note that would have been truncated in the old system but should now be displayed in full with proper word wrapping and formatting in the improved PDF export system." data_manager.add_entry(['2025-01-04', 3, 2, 5, 6, 0, '', 0, '', 0, '', 0, '', 0, '', long_note]) export_manager = ExportManager( data_manager, real_graph_manager, medicine_manager, pathology_manager, mock_logger ) df = data_manager.load_data() real_graph_manager.update_graph(df) with tempfile.NamedTemporaryFile(suffix='.pdf', delete=False) as f: temp_pdf_path = f.name try: success = export_manager.export_to_pdf(temp_pdf_path, include_graph=True) assert success is True # Verify that the long note was not truncated by checking data processing df_processed = data_manager.load_data() note_entry = df_processed[df_processed['date'] == '2025-01-04']['note'].iloc[0] assert long_note in note_entry # Full note should be preserved finally: if hasattr(real_graph_manager, '_test_root'): real_graph_manager._test_root.destroy() if os.path.exists(temp_pdf_path): os.unlink(temp_pdf_path)