"""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 src.auto_save 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)