Some checks are pending
NordaBiz Tests / Unit & Integration Tests (push) Waiting to run
NordaBiz Tests / E2E Tests (Playwright) (push) Blocked by required conditions
NordaBiz Tests / Smoke Tests (Production) (push) Blocked by required conditions
NordaBiz Tests / Send Failure Notification (push) Blocked by required conditions
- pytest framework with fixtures for auth (auth_client, admin_client) - Unit tests for SearchService - Integration tests for auth flow - Security tests (OWASP Top 10: SQL injection, XSS, CSRF) - Smoke tests for production health and backup monitoring - E2E tests with Playwright (basic structure) - DR tests for backup/restore procedures - GitHub Actions CI/CD workflow (.github/workflows/test.yml) - Coverage configuration (.coveragerc) with 80% minimum - DR documentation and restore script Staging environment: VM 248, staging.nordabiznes.pl Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
133 lines
4.1 KiB
Python
133 lines
4.1 KiB
Python
"""
|
|
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"
|