78 lines
3.8 KiB
Python
78 lines
3.8 KiB
Python
"""
|
||
وضعیتِ کپیِ زندهی دیتابیس در /api/health (config/health.py: _slot_state، _offsite_warnings).
|
||
پرسشِ واقعی از pg_replication_slots اینجا اجرا نمیشود (تستها روی SQLiteاند)؛ روی سرور با اسلاتِ
|
||
واقعی دیده شد (docs/operations.md «بکاپ»).
|
||
"""
|
||
from datetime import datetime, timedelta, timezone
|
||
|
||
from django.test import SimpleTestCase, override_settings
|
||
|
||
from config import health
|
||
|
||
|
||
class SlotStateTests(SimpleTestCase):
|
||
def test_missing_slot(self):
|
||
self.assertEqual(health._slot_state(None)['state'], 'slot_missing')
|
||
|
||
def test_streaming_and_caught_up_is_ok(self):
|
||
self.assertEqual(health._slot_state((True, 'reserved', 0)), {'state': 'ok', 'lagBytes': 0})
|
||
|
||
def test_inactive_slot_is_disconnected(self):
|
||
self.assertEqual(health._slot_state((False, 'reserved', 0))['state'], 'disconnected')
|
||
|
||
def test_lost_slot_wins_over_everything(self):
|
||
self.assertEqual(health._slot_state((False, 'lost', None))['state'], 'lost')
|
||
|
||
@override_settings(OFFSITE_WAL_MAX_LAG_BYTES=100)
|
||
def test_lag_above_limit(self):
|
||
self.assertEqual(health._slot_state((True, 'reserved', 101))['state'], 'lagging')
|
||
self.assertEqual(health._slot_state((True, 'reserved', 100))['state'], 'ok')
|
||
|
||
|
||
@override_settings(OFFSITE_SYNC_MAX_AGE_SECONDS=600)
|
||
class PushedStateTests(SimpleTestCase):
|
||
"""بکاپِ رمزشدهی ۲۱۶ (_pushed_state). پرسشِ pg_stat_archiver و خواندنِ فایل روی سرور دیده شد، نه اینجا."""
|
||
|
||
T0 = datetime(2026, 10, 17, 16, 0, tzinfo=timezone.utc)
|
||
|
||
def test_recent_sync_is_ok(self):
|
||
r = health._pushed_state(self.T0, None, 1000, now=1060)
|
||
self.assertEqual((r['state'], r['syncAgeSeconds']), ('ok', 60))
|
||
self.assertEqual(r['lastArchivedAt'], self.T0.isoformat())
|
||
|
||
def test_old_sync_is_stale(self):
|
||
self.assertEqual(health._pushed_state(self.T0, None, 1000, now=1601)['state'], 'sync_stale')
|
||
self.assertEqual(health._pushed_state(self.T0, None, 1000, now=1600)['state'], 'ok')
|
||
|
||
def test_no_status_file_is_never_synced(self):
|
||
self.assertEqual(health._pushed_state(self.T0, None, None, now=1000)['state'], 'never_synced')
|
||
|
||
def test_failure_after_last_success_wins(self):
|
||
later = self.T0 + timedelta(minutes=1)
|
||
self.assertEqual(health._pushed_state(self.T0, later, 1000, now=1001)['state'], 'archive_failing')
|
||
self.assertEqual(health._pushed_state(None, later, 1000, now=1001)['state'], 'archive_failing')
|
||
|
||
def test_old_failure_then_success_is_ok(self):
|
||
earlier = self.T0 - timedelta(minutes=1)
|
||
self.assertEqual(health._pushed_state(self.T0, earlier, 1000, now=1001)['state'], 'ok')
|
||
|
||
def test_idle_database_with_no_archive_yet_is_ok(self):
|
||
self.assertEqual(health._pushed_state(None, None, 1000, now=1001)['state'], 'ok')
|
||
|
||
|
||
class OffsiteWarningTests(SimpleTestCase):
|
||
def test_ok_and_dev_are_silent(self):
|
||
self.assertEqual(health._offsite_warnings({'state': 'ok', 'lagBytes': 0}), [])
|
||
self.assertEqual(health._offsite_warnings({'state': 'not_postgres'}), [])
|
||
|
||
def test_every_bad_state_warns(self):
|
||
for state in ('not_configured', 'slot_missing', 'disconnected', 'lagging', 'lost', 'error',
|
||
'archive_failing', 'never_synced', 'sync_stale'):
|
||
self.assertEqual(len(health._offsite_warnings({'state': state})), 1, state)
|
||
|
||
def test_sqlite_reports_not_postgres(self):
|
||
# سوئیت روی Postgres هم اجرا میشود (پیش از هر تغییرِ دیتابیسی)؛ آنجا این حالت معنی ندارد.
|
||
if health.connection.vendor != 'sqlite':
|
||
self.skipTest('فقط روی SQLite')
|
||
self.assertEqual(health._offsite_backup(), {'state': 'not_postgres'})
|