Some checks failed
Build and Push Docker Image / build-and-push (push) Has been cancelled
- Implemented unit tests for the ErrorHandler class, covering error handling, frequency tracking, and performance warnings. - Created integration tests for input validation, error handling, auto-save functionality, and search/filter systems. - Developed unit tests for the DataFilter, QuickFilters, and SearchHistory classes to ensure filtering logic works as expected. - Added tests for the SearchFilterWidget UI component, verifying initialization, filter functionality, and responsiveness. - Included edge case tests for error handling without UI manager and handling of None values.
254 lines
9.2 KiB
Python
254 lines
9.2 KiB
Python
"""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)
|