Add three-scenario disaster recovery
This commit is contained in:
@@ -30,6 +30,7 @@ class DashboardModeTests(unittest.TestCase):
|
||||
cls.tempdir = tempfile.TemporaryDirectory()
|
||||
with patch.dict(os.environ, {
|
||||
"DASHBOARD_HISTORY_DB": str(Path(cls.tempdir.name) / "history.sqlite3"),
|
||||
"DASHBOARD_BACKUP_DIR": str(Path(cls.tempdir.name) / "backups"),
|
||||
"ROUTER_URL": "http://router.test:8081",
|
||||
"ROUTER_API_KEY": "test-key",
|
||||
}):
|
||||
@@ -37,6 +38,8 @@ class DashboardModeTests(unittest.TestCase):
|
||||
cls.dashboard = importlib.util.module_from_spec(spec)
|
||||
assert spec.loader is not None
|
||||
spec.loader.exec_module(cls.dashboard)
|
||||
cls.backup_dir = Path(cls.tempdir.name) / "backups"
|
||||
cls.backup_dir.mkdir()
|
||||
|
||||
@classmethod
|
||||
def tearDownClass(cls):
|
||||
@@ -101,6 +104,20 @@ class DashboardModeTests(unittest.TestCase):
|
||||
self.assertIn("http://192.168.1.212:8011/", html)
|
||||
self.assertIn("http://192.168.1.212:8012/", html)
|
||||
|
||||
def test_dashboard_lists_only_portable_encrypted_backups(self):
|
||||
valid = self.backup_dir / "athena-portable-2026-09-10T10-00-00Z.tar.zst.age"
|
||||
valid.write_bytes(b"encrypted")
|
||||
valid.with_name(valid.name + ".sha256").write_text(
|
||||
"a" * 64 + " " + valid.name + "\n", encoding="utf-8"
|
||||
)
|
||||
(self.backup_dir / "unrelated.txt").write_text("ignore", encoding="utf-8")
|
||||
|
||||
backups = self.dashboard.backup_inventory()
|
||||
|
||||
self.assertEqual([item["name"] for item in backups], [valid.name])
|
||||
self.assertEqual(backups[0]["sha256"], "a" * 64)
|
||||
self.assertTrue(backups[0]["download_url"].startswith("/api/backups/download/"))
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
unittest.main()
|
||||
|
||||
@@ -101,6 +101,26 @@ class RecoveryScriptTests(unittest.TestCase):
|
||||
self.assertIn('start_proxy 9443 portainer:9443', gateway)
|
||||
self.assertIn('portainer-data:/backup/volumes/portainer-data:ro', compose)
|
||||
|
||||
def test_disaster_recovery_covers_all_three_scenarios_without_formatting(self) -> None:
|
||||
script = (ROOT / "disaster-recovery.sh").read_text(encoding="utf-8")
|
||||
for scenario in ("system", "data", "all"):
|
||||
self.assertIn(scenario, script)
|
||||
self.assertIn("mountpoint -q /data", script)
|
||||
self.assertIn("--portable", script)
|
||||
for destructive in ("mkfs", "fdisk", "parted", "reboot", "shutdown"):
|
||||
self.assertNotIn(f"{destructive} ", script)
|
||||
|
||||
def test_backup_layers_include_code_and_irreplaceable_data(self) -> None:
|
||||
compose = (ROOT / "compose.yaml").read_text(encoding="utf-8")
|
||||
export = (ROOT / "platform/backup/athena-export-backup").read_text(
|
||||
encoding="utf-8"
|
||||
)
|
||||
self.assertIn("/opt/mike-ai:/backup/opt-mike-ai:ro", compose)
|
||||
self.assertIn("/data/voice/applio/logs", export)
|
||||
self.assertIn("/data/voice/applio/datasets", export)
|
||||
self.assertIn("ATHENA_EXPORT_KEEP:-5", export)
|
||||
self.assertNotIn("add_path /data/models", export)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
unittest.main()
|
||||
|
||||
Reference in New Issue
Block a user