import json import os import sqlite3 import subprocess import sys from pathlib import Path from src.unfiled_draft_store import UnfiledDraftStore SCRIPT = Path(__file__).parents[1] / "scripts" / "rotate_unfiled_drafts.py" OLD_KEY = "b29vb29vb29vb29vb29vb29vb29vb29vb29vb29vb28=" NEW_KEY = "bm5ubm5ubm5ubm5ubm5ubm5ubm5ubm5ubm5ubm5ubm4=" def _draft(title): return {"id": "draft", "title": title, "body": "private", "saved_at": 1} def test_rotation_cli_migrates_readable_rows_and_fails_closed_on_unreadable_rows(tmp_path): path = tmp_path / "drafts.sqlite3" store = UnfiledDraftStore(path, encryption_key=b"o" * 32) store.replace("timmy", 0, [_draft("Secret launch")]) with sqlite3.connect(path) as connection: connection.execute( "INSERT INTO unfiled_drafts(login, revision, drafts) VALUES (?, ?, ?)", ("broken", 1, "v2:missing:not-ciphertext"), ) env = { **os.environ, "STACKCHAIN_UNFILED_DRAFT_DB": str(path), "STACKCHAIN_UNFILED_DRAFT_ENCRYPTION_KEYS": json.dumps( {"legacy": OLD_KEY, "new": NEW_KEY} ), "STACKCHAIN_UNFILED_DRAFT_ACTIVE_KEY_ID": "new", } completed = subprocess.run( [sys.executable, str(SCRIPT)], cwd=SCRIPT.parents[1], env=env, text=True, capture_output=True, check=False, ) assert completed.returncode == 1 assert json.loads(completed.stdout) == { "current": 0, "failed": 1, "migrated": 1, "total": 2, } assert "timmy" not in completed.stdout assert "Secret launch" not in completed.stdout assert completed.stderr == ""