""" Smoke tests for backup health ============================== Verify that backups are being created and are valid. These tests require SSH access to production server. Usage: pytest tests/smoke/test_backup_health.py -v """ import os import subprocess from datetime import datetime, timedelta import pytest pytestmark = [pytest.mark.smoke, pytest.mark.dr] # Production server PROD_HOST = os.environ.get('PROD_HOST', 'maciejpi@10.22.68.249') BACKUP_DIR = '/var/backups/nordabiz' def ssh_command(cmd: str) -> tuple[int, str, str]: """Execute SSH command on production server.""" result = subprocess.run( ['ssh', PROD_HOST, cmd], capture_output=True, text=True, timeout=30 ) return result.returncode, result.stdout, result.stderr class TestBackupFreshness: """Tests for backup freshness.""" @pytest.mark.slow def test_hourly_backup_exists(self): """Hourly backup from last 2 hours should exist.""" returncode, stdout, stderr = ssh_command( f'ls -t {BACKUP_DIR}/hourly/ | head -1' ) assert returncode == 0, f"SSH failed: {stderr}" assert stdout.strip(), "No hourly backup found" # Check file is recent returncode, stdout, _ = ssh_command( f'stat -c %Y {BACKUP_DIR}/hourly/$(ls -t {BACKUP_DIR}/hourly/ | head -1)' ) if returncode == 0 and stdout.strip(): file_time = int(stdout.strip()) now = int(datetime.now().timestamp()) age_hours = (now - file_time) / 3600 assert age_hours < 2, f"Hourly backup is {age_hours:.1f} hours old (should be < 2)" @pytest.mark.slow def test_daily_backup_exists(self): """Daily backup from last 25 hours should exist.""" returncode, stdout, stderr = ssh_command( f'ls -t {BACKUP_DIR}/daily/ | head -1' ) assert returncode == 0, f"SSH failed: {stderr}" assert stdout.strip(), "No daily backup found" class TestBackupSize: """Tests for backup file sizes.""" @pytest.mark.slow def test_hourly_backup_size_reasonable(self): """Hourly backup should be at least 1MB (not empty/corrupt).""" returncode, stdout, stderr = ssh_command( f'du -b {BACKUP_DIR}/hourly/$(ls -t {BACKUP_DIR}/hourly/ | head -1) | cut -f1' ) assert returncode == 0, f"SSH failed: {stderr}" size_bytes = int(stdout.strip()) size_mb = size_bytes / (1024 * 1024) assert size_mb >= 1, f"Backup too small: {size_mb:.2f} MB (should be >= 1 MB)" @pytest.mark.slow def test_daily_backup_size_consistent(self): """Daily backup should not vary wildly from previous.""" returncode, stdout, stderr = ssh_command( f'du -b {BACKUP_DIR}/daily/* | sort -k2 -r | head -2 | cut -f1' ) if returncode == 0 and stdout.strip(): sizes = [int(s) for s in stdout.strip().split('\n') if s] if len(sizes) >= 2: latest, previous = sizes[0], sizes[1] ratio = latest / previous if previous > 0 else 0 # Size should be within 50% of previous assert 0.5 < ratio < 2.0, f"Backup size changed significantly: {ratio:.2f}x" class TestDRScriptReady: """Tests for DR restore script availability.""" @pytest.mark.slow def test_dr_restore_script_exists(self): """DR restore script should exist and be executable.""" returncode, stdout, stderr = ssh_command( 'test -x /var/www/nordabiznes/scripts/dr-restore.sh && echo "OK"' ) assert returncode == 0, f"DR restore script not found or not executable: {stderr}" assert 'OK' in stdout class TestBackupCronActive: """Tests for backup cron jobs.""" @pytest.mark.slow def test_backup_cron_exists(self): """Backup cron job should be configured.""" returncode, stdout, stderr = ssh_command( 'cat /etc/cron.d/nordabiz-backup 2>/dev/null || crontab -l | grep nordabiz' ) # Either cron.d file exists or user crontab has entry assert returncode == 0 or 'nordabiz' in stdout.lower(), "No backup cron found"