diff --git a/tests/test_integration_backup_file_restore.py b/tests/test_integration_backup_file_restore.py index 9b268ba5..ad4100b8 100644 --- a/tests/test_integration_backup_file_restore.py +++ b/tests/test_integration_backup_file_restore.py @@ -5,7 +5,8 @@ the real `restore_full_backup`. The unit lane already shows the tar hands back the dict it was given. What only this lane shows is that the dict it hands back is one the restore accepts — every id remapped to the restored rows, nothing dropped — and that -the command-line restore refuses a database that already has accounts. +the command-line restore refuses, on a live instance, a backup that doesn't +record this instance as its source. """ import pytest import pytest_asyncio @@ -97,10 +98,11 @@ async def test_the_tar_restores_with_every_id_remapped(written): assert note.status == "todo" -async def test_the_command_refuses_a_database_with_accounts(written): - """The source user exists, so this database is not empty: the command - must stop before restoring anything.""" - assert await run(written) == 3 +async def test_the_command_refuses_a_backup_from_no_known_instance(written): + """This database is live and the backup names no instance it came from, + so it can't be rolled back to: the command stops before restoring + anything, even with --yes.""" + assert await run(written, yes=True) == 3 async with async_session() as s: restored = (await s.execute(select(User).where(User.username == RESTORED))).scalars().all() assert restored == [] diff --git a/tests/test_integration_backup_rollback.py b/tests/test_integration_backup_rollback.py index c6c80924..e807f838 100644 --- a/tests/test_integration_backup_rollback.py +++ b/tests/test_integration_backup_rollback.py @@ -4,6 +4,7 @@ The lane shares one database, so every test here takes its backup of the whole store, changes its own rows, and rolls back to that backup: what it puts back is exactly what was there when it started. """ +from datetime import datetime, timezone from unittest.mock import AsyncMock, patch import pytest @@ -14,8 +15,10 @@ from grimoire.models import async_session from grimoire.models.note import Note from grimoire.models.project import Project from grimoire.models.user import User -from grimoire.services import backup +from grimoire.restore import run +from grimoire.services import backup, backup_job from grimoire.services.api_keys import create_api_key, lookup_key +from grimoire.services.backup_file import write_archive from grimoire.services.instance import adopt_instance_id, get_instance_id from tests.helpers import ensure_user @@ -176,3 +179,36 @@ async def test_a_fresh_instance_adopts_the_backups_instance_id(seeded): async with async_session() as s: await adopt_instance_id(s, own) await s.commit() + + +async def test_the_command_only_previews_without_yes(seeded, tmp_path): + ids, payload, _ = seeded + path = write_archive(payload, tmp_path, datetime.now(timezone.utc), app_version="test") + new_id = await _change_everything(ids) + + assert await run(path) == 4 + + async with async_session() as s: + assert (await s.get(Note, ids["kept"])).title == "after" + assert await s.get(Note, new_id) is not None + await backup.replace_from_backup(payload) + + +async def test_the_command_rolls_back_from_the_tar_with_yes(seeded, tmp_path): + """The scheduled backup's own file, read back through the command: the + whole store returns to it, after the safety backup is taken.""" + ids, payload, _ = seeded + path = write_archive(payload, tmp_path, datetime.now(timezone.utc), app_version="test") + new_id = await _change_everything(ids) + saved = {"file": "grimoire-safety.tar", "size_bytes": 1, "where": str(tmp_path)} + + with patch.object(backup_job, "save_state_before_restore", + AsyncMock(return_value=saved)) as save: + assert await run(path, yes=True) == 0 + + save.assert_awaited_once() + async with async_session() as s: + kept = await s.get(Note, ids["kept"]) + assert (kept.title, kept.body) == ("before", "as backed up") + assert await s.get(Note, ids["doomed"]) is not None + assert await s.get(Note, new_id) is None