"""Tests for auto-save and backup system.""" import pytest import tempfile import os import shutil from unittest.mock import MagicMock, patch from datetime import datetime import pandas as pd from thechart.core import AutoSaveManager class TestAutoSaveManager: """Test cases for AutoSaveManager class.""" def setup_method(self): """Set up test fixtures.""" # Create temporary directories for testing self.test_dir = tempfile.mkdtemp() self.backup_dir = os.path.join(self.test_dir, "backups") self.test_data_file = os.path.join(self.test_dir, "test_data.csv") # Create test data file test_data = pd.DataFrame({ 'Date': ['2024-01-01', '2024-01-02'], 'Notes': ['Test note 1', 'Test note 2'] }) test_data.to_csv(self.test_data_file, index=False) # Mock callbacks self.mock_status_callback = MagicMock() self.mock_error_callback = MagicMock() # Create AutoSaveManager instance self.auto_save = AutoSaveManager( data_file_path=self.test_data_file, backup_dir=self.backup_dir, status_callback=self.mock_status_callback, error_callback=self.mock_error_callback, interval_minutes=0.1, # Very short interval for testing max_backups=3 ) def teardown_method(self): """Clean up test fixtures.""" if hasattr(self, 'auto_save'): self.auto_save.stop() if os.path.exists(self.test_dir): shutil.rmtree(self.test_dir) def test_initialization(self): """Test AutoSaveManager initialization.""" assert self.auto_save.data_file_path == self.test_data_file assert self.auto_save.backup_dir == self.backup_dir assert self.auto_save.interval_minutes == 0.1 assert self.auto_save.max_backups == 3 assert not self.auto_save.is_running def test_backup_directory_creation(self): """Test that backup directory is created.""" # Directory should be created during initialization assert os.path.exists(self.backup_dir) assert os.path.isdir(self.backup_dir) def test_create_backup(self): """Test backup creation.""" backup_file = self.auto_save.create_backup("test_backup") # Verify backup file exists assert os.path.exists(backup_file) assert backup_file.startswith(self.backup_dir) assert "test_backup" in backup_file # Verify backup content matches original original_data = pd.read_csv(self.test_data_file) backup_data = pd.read_csv(backup_file) pd.testing.assert_frame_equal(original_data, backup_data) def test_create_backup_nonexistent_file(self): """Test backup creation when source file doesn't exist.""" auto_save = AutoSaveManager( data_file_path="/nonexistent/file.csv", backup_dir=self.backup_dir, status_callback=self.mock_status_callback, error_callback=self.mock_error_callback ) backup_file = auto_save.create_backup("test") assert backup_file is None # Error callback should have been called self.mock_error_callback.assert_called() def test_cleanup_old_backups(self): """Test cleanup of old backups.""" # Create more backups than max_backups backup_files = [] for i in range(5): backup_file = self.auto_save.create_backup(f"test_{i}") backup_files.append(backup_file) # Perform cleanup self.auto_save._cleanup_old_backups() # Should only have max_backups files remaining remaining_files = [f for f in backup_files if os.path.exists(f)] assert len(remaining_files) <= self.auto_save.max_backups def test_start_and_stop(self): """Test starting and stopping auto-save.""" # Start auto-save self.auto_save.start() assert self.auto_save.is_running # Stop auto-save self.auto_save.stop() assert not self.auto_save.is_running def test_get_backup_files(self): """Test getting list of backup files.""" # Create some backups self.auto_save.create_backup("backup1") self.auto_save.create_backup("backup2") backup_files = self.auto_save.get_backup_files() assert len(backup_files) >= 2 assert all(os.path.exists(f) for f in backup_files) assert all(f.endswith('.csv') for f in backup_files) def test_restore_from_backup(self): """Test restoring from backup.""" # Create a backup backup_file = self.auto_save.create_backup("test_restore") # Modify original file modified_data = pd.DataFrame({ 'Date': ['2024-01-03'], 'Notes': ['Modified note'] }) modified_data.to_csv(self.test_data_file, index=False) # Restore from backup success = self.auto_save.restore_from_backup(backup_file) assert success # Verify restoration restored_data = pd.read_csv(self.test_data_file) assert len(restored_data) == 2 # Original had 2 rows assert 'Test note 1' in restored_data['Notes'].values def test_restore_from_nonexistent_backup(self): """Test restoring from nonexistent backup.""" success = self.auto_save.restore_from_backup("/nonexistent/backup.csv") assert not success self.mock_error_callback.assert_called() def test_backup_filename_format(self): """Test backup filename format.""" backup_file = self.auto_save.create_backup("test_format") filename = os.path.basename(backup_file) # Should contain source filename, suffix, and timestamp assert "test_data" in filename assert "test_format" in filename assert filename.endswith('.csv') # Should have timestamp in format assert len(filename.split('_')) >= 4 # name_suffix_date_time.csv def test_backup_with_special_characters(self): """Test backup creation with special characters in suffix.""" backup_file = self.auto_save.create_backup("test with spaces & symbols!") assert os.path.exists(backup_file) # Special characters should be handled appropriately assert os.path.isfile(backup_file) def test_concurrent_backup_operations(self): """Test that concurrent backup operations don't interfere.""" # This tests thread safety (basic test) backup1 = self.auto_save.create_backup("concurrent1") backup2 = self.auto_save.create_backup("concurrent2") assert backup1 != backup2 assert os.path.exists(backup1) assert os.path.exists(backup2) def test_error_handling_during_backup(self): """Test error handling during backup operations.""" # Test with permission error with patch('shutil.copy2', side_effect=PermissionError("Permission denied")): backup_file = self.auto_save.create_backup("permission_test") assert backup_file is None self.mock_error_callback.assert_called() def test_auto_save_integration(self): """Test integration of auto-save functionality.""" # Start auto-save self.auto_save.start() # Wait a short time for at least one auto-save cycle import time time.sleep(0.2) # Wait longer than interval # Should have created startup backup backup_files = self.auto_save.get_backup_files() assert len(backup_files) > 0 # Stop auto-save self.auto_save.stop() def test_status_callback_integration(self): """Test status callback integration.""" self.auto_save.create_backup("status_test") # Status callback should have been called self.mock_status_callback.assert_called() call_args = self.mock_status_callback.call_args[0] assert "backup" in call_args[0].lower() def test_backup_size_validation(self): """Test that backups have reasonable size.""" backup_file = self.auto_save.create_backup("size_test") original_size = os.path.getsize(self.test_data_file) backup_size = os.path.getsize(backup_file) # Backup should be similar size to original (allowing for minor differences) assert abs(backup_size - original_size) < 100 # Within 100 bytes def test_backup_file_sorting(self): """Test that backup files are sorted by creation time.""" # Create backups with small delays import time backup1 = self.auto_save.create_backup("first") time.sleep(0.01) backup2 = self.auto_save.create_backup("second") time.sleep(0.01) backup3 = self.auto_save.create_backup("third") backup_files = self.auto_save.get_backup_files() # Files should be sorted with newest first assert len(backup_files) >= 3 # Check that the files are in the list (order might vary based on filesystem) backup_names = [os.path.basename(f) for f in backup_files] assert any("first" in name for name in backup_names) assert any("second" in name for name in backup_names) assert any("third" in name for name in backup_names)