"""Tests for db_backup.py""" import json import sys from datetime import datetime from pathlib import Path from unittest.mock import Mock, patch, MagicMock, call import pytest # Add parent directory to path sys.path.insert(0, str(Path(__file__).parent.parent)) from db_backup import BackupInfo, BackupManager @pytest.fixture def temp_backup_dir(tmp_path): """Create temporary backup directory.""" backup_dir = tmp_path / "backups" backup_dir.mkdir() return str(backup_dir) @pytest.fixture def sample_backup_info(): """Create sample backup info.""" return BackupInfo( filename="test_backup_20250101_120000.dump", database_type="mongodb", database_name="testdb", timestamp=datetime.now(), size_bytes=1024000, compressed=True, verified=True ) class TestBackupInfo: """Test BackupInfo dataclass.""" def test_backup_info_creation(self): """Test creating backup info object.""" info = BackupInfo( filename="backup.dump", database_type="mongodb", database_name="mydb", timestamp=datetime.now(), size_bytes=1024, compressed=False ) assert info.filename == "backup.dump" assert info.database_type == "mongodb" assert info.database_name == "mydb" assert info.size_bytes == 1024 assert not info.compressed assert not info.verified class TestBackupManager: """Test BackupManager class.""" def test_init(self, temp_backup_dir): """Test manager initialization.""" manager = BackupManager("mongodb", temp_backup_dir) assert manager.db_type == "mongodb" assert Path(temp_backup_dir).exists() @patch('subprocess.run') def test_backup_mongodb(self, mock_run, temp_backup_dir): """Test MongoDB backup creation.""" mock_run.return_value = Mock(returncode=0, stderr="") manager = BackupManager("mongodb", temp_backup_dir) backup_info = manager.create_backup( "mongodb://localhost", "testdb", compress=False, verify=False ) assert backup_info is not None assert backup_info.database_type == "mongodb" assert backup_info.database_name == "testdb" mock_run.assert_called_once() @patch('subprocess.run') def test_backup_postgres(self, mock_run, temp_backup_dir): """Test PostgreSQL backup creation.""" mock_run.return_value = Mock(returncode=0, stderr="") manager = BackupManager("postgres", temp_backup_dir) with patch('builtins.open', create=True) as mock_open: mock_open.return_value.__enter__.return_value = MagicMock() backup_info = manager.create_backup( "postgresql://localhost/testdb", "testdb", compress=False, verify=False ) assert backup_info is not None assert backup_info.database_type == "postgres" assert backup_info.database_name == "testdb" def test_backup_postgres_no_database(self, temp_backup_dir): """Test PostgreSQL backup without database name.""" manager = BackupManager("postgres", temp_backup_dir) backup_info = manager.create_backup( "postgresql://localhost", database=None, compress=False, verify=False ) assert backup_info is None @patch('subprocess.run') def test_backup_with_compression(self, mock_run, temp_backup_dir): """Test backup with compression.""" mock_run.return_value = Mock(returncode=0, stderr="") manager = BackupManager("mongodb", temp_backup_dir) with patch('shutil.make_archive') as mock_archive, \ patch('shutil.rmtree') as mock_rmtree: backup_info = manager.create_backup( "mongodb://localhost", "testdb", compress=True, verify=False ) assert backup_info is not None assert backup_info.compressed mock_archive.assert_called_once() def test_save_and_load_metadata(self, temp_backup_dir, sample_backup_info): """Test saving and loading backup metadata.""" manager = BackupManager("mongodb", temp_backup_dir) # Save metadata manager._save_metadata(sample_backup_info) # Check file was created metadata_file = Path(temp_backup_dir) / f"{sample_backup_info.filename}.json" assert metadata_file.exists() # Load metadata with open(metadata_file) as f: data = json.load(f) assert data["filename"] == sample_backup_info.filename assert data["database_type"] == "mongodb" assert data["database_name"] == "testdb" def test_list_backups(self, temp_backup_dir, sample_backup_info): """Test listing backups.""" manager = BackupManager("mongodb", temp_backup_dir) # Create test backup metadata manager._save_metadata(sample_backup_info) # List backups backups = manager.list_backups() assert len(backups) == 1 assert backups[0].filename == sample_backup_info.filename assert backups[0].database_name == "testdb" @patch('subprocess.run') def test_restore_mongodb(self, mock_run, temp_backup_dir): """Test MongoDB restore.""" mock_run.return_value = Mock(returncode=0, stderr="") manager = BackupManager("mongodb", temp_backup_dir) # Create dummy backup file backup_file = Path(temp_backup_dir) / "test_backup.dump" backup_file.touch() result = manager.restore_backup( "test_backup.dump", "mongodb://localhost" ) assert result is True mock_run.assert_called_once() @patch('subprocess.run') def test_restore_postgres(self, mock_run, temp_backup_dir): """Test PostgreSQL restore.""" mock_run.return_value = Mock(returncode=0, stderr="") manager = BackupManager("postgres", temp_backup_dir) # Create dummy backup file backup_file = Path(temp_backup_dir) / "test_backup.sql" backup_file.write_text("SELECT 1;") with patch('builtins.open', create=True) as mock_open: mock_open.return_value.__enter__.return_value = MagicMock() result = manager.restore_backup( "test_backup.sql", "postgresql://localhost/testdb" ) assert result is True def test_restore_nonexistent_backup(self, temp_backup_dir): """Test restore with non-existent backup file.""" manager = BackupManager("mongodb", temp_backup_dir) result = manager.restore_backup( "nonexistent.dump", "mongodb://localhost" ) assert result is False def test_restore_dry_run(self, temp_backup_dir): """Test restore in dry-run mode.""" manager = BackupManager("mongodb", temp_backup_dir) # Create dummy backup file backup_file = Path(temp_backup_dir) / "test_backup.dump" backup_file.touch() result = manager.restore_backup( "test_backup.dump", "mongodb://localhost", dry_run=True ) assert result is True def test_cleanup_old_backups(self, temp_backup_dir): """Test cleaning up old backups.""" manager = BackupManager("mongodb", temp_backup_dir) # Create old backup file (simulate by setting mtime) old_backup = Path(temp_backup_dir) / "old_backup.dump" old_backup.touch() # Set mtime to 10 days ago old_time = datetime.now().timestamp() - (10 * 24 * 3600) os.utime(old_backup, (old_time, old_time)) # Cleanup with 7-day retention removed = manager.cleanup_old_backups(retention_days=7) assert removed == 1 assert not old_backup.exists() def test_cleanup_dry_run(self, temp_backup_dir): """Test cleanup in dry-run mode.""" manager = BackupManager("mongodb", temp_backup_dir) # Create old backup file old_backup = Path(temp_backup_dir) / "old_backup.dump" old_backup.touch() old_time = datetime.now().timestamp() - (10 * 24 * 3600) os.utime(old_backup, (old_time, old_time)) # Cleanup with dry-run removed = manager.cleanup_old_backups(retention_days=7, dry_run=True) assert removed == 1 assert old_backup.exists() # File should still exist def test_verify_backup(self, temp_backup_dir, sample_backup_info): """Test backup verification.""" manager = BackupManager("mongodb", temp_backup_dir) # Create dummy backup file backup_file = Path(temp_backup_dir) / sample_backup_info.filename backup_file.write_text("backup data") result = manager._verify_backup(sample_backup_info) assert result is True def test_verify_empty_backup(self, temp_backup_dir, sample_backup_info): """Test verification of empty backup file.""" manager = BackupManager("mongodb", temp_backup_dir) # Create empty backup file backup_file = Path(temp_backup_dir) / sample_backup_info.filename backup_file.touch() result = manager._verify_backup(sample_backup_info) assert result is False def test_format_size(self, temp_backup_dir): """Test size formatting.""" manager = BackupManager("mongodb", temp_backup_dir) assert manager._format_size(500) == "500.00 B" assert manager._format_size(1024) == "1.00 KB" assert manager._format_size(1024 * 1024) == "1.00 MB" assert manager._format_size(1024 * 1024 * 1024) == "1.00 GB" def test_get_size_file(self, temp_backup_dir): """Test getting size of file.""" manager = BackupManager("mongodb", temp_backup_dir) test_file = Path(temp_backup_dir) / "test.txt" test_file.write_text("test data") size = manager._get_size(test_file) assert size > 0 def test_get_size_directory(self, temp_backup_dir): """Test getting size of directory.""" manager = BackupManager("mongodb", temp_backup_dir) test_dir = Path(temp_backup_dir) / "test_dir" test_dir.mkdir() (test_dir / "file1.txt").write_text("data1") (test_dir / "file2.txt").write_text("data2") size = manager._get_size(test_dir) assert size > 0 # Import os for cleanup test import os if __name__ == "__main__": pytest.main([__file__, "-v"])