diff options
Diffstat (limited to 'packages/meshbay-node/tests/test_bundle_store_recovery.py')
| -rw-r--r-- | packages/meshbay-node/tests/test_bundle_store_recovery.py | 76 |
1 files changed, 76 insertions, 0 deletions
diff --git a/packages/meshbay-node/tests/test_bundle_store_recovery.py b/packages/meshbay-node/tests/test_bundle_store_recovery.py new file mode 100644 index 0000000..10a3400 --- /dev/null +++ b/packages/meshbay-node/tests/test_bundle_store_recovery.py @@ -0,0 +1,76 @@ +""" +The recovery-wrapped keypair copy (docs/auth-confirm.md §4.3, MNP 0.14). + +`bundle_enc_recovery` is a second copy of the identity bundle wrapped under the +account's recovery key. The store has to add the column to a database that +predates it, and a plain re-backup that omits the recovery copy must not erase +one already there. +""" + +import aiosqlite +import pytest +from meshbay_node.bundle_store import BundleStore + + +@pytest.mark.asyncio +async def test_round_trip_with_and_without_recovery(tmp_path): + store = BundleStore(db_path=tmp_path / "bundles.db") + await store.open() + + await store.store_keypair("u1", "pass-wrapped-1") + row = await store.fetch_keypair("u1") + assert row == {"bundle_enc": "pass-wrapped-1", "bundle_enc_recovery": None} + + await store.store_keypair("u2", "pass-wrapped-2", "recovery-wrapped-2") + row = await store.fetch_keypair("u2") + assert row["bundle_enc"] == "pass-wrapped-2" + assert row["bundle_enc_recovery"] == "recovery-wrapped-2" + + assert await store.fetch_keypair("nobody") is None + await store.close() + + +@pytest.mark.asyncio +async def test_re_backup_without_recovery_keeps_the_existing_copy(tmp_path): + """A passphrase-change re-wrap sends only bundle_enc; the recovery copy stays.""" + store = BundleStore(db_path=tmp_path / "bundles.db") + await store.open() + + await store.store_keypair("u1", "v1", "recovery-v1") + await store.store_keypair("u1", "v2") # no recovery arg + row = await store.fetch_keypair("u1") + assert row["bundle_enc"] == "v2" + assert row["bundle_enc_recovery"] == "recovery-v1" + + # An explicit new recovery copy does replace it. + await store.store_keypair("u1", "v3", "recovery-v3") + row = await store.fetch_keypair("u1") + assert row == {"bundle_enc": "v3", "bundle_enc_recovery": "recovery-v3"} + await store.close() + + +@pytest.mark.asyncio +async def test_migration_adds_the_column_to_an_old_database(tmp_path): + db_path = tmp_path / "bundles.db" + + # A keypair_bundles table as it looked before MNP 0.14. + async with aiosqlite.connect(str(db_path)) as db: + await db.execute( + "CREATE TABLE keypair_bundles (" + " user_id TEXT PRIMARY KEY," + " bundle_enc TEXT NOT NULL," + " stored_at TEXT NOT NULL DEFAULT (datetime('now')))") + await db.execute( + "INSERT INTO keypair_bundles (user_id, bundle_enc) VALUES ('old', 'legacy')") + await db.commit() + + store = BundleStore(db_path=db_path) + await store.open() # runs the migration + + row = await store.fetch_keypair("old") + assert row == {"bundle_enc": "legacy", "bundle_enc_recovery": None} + + await store.store_keypair("old", "legacy", "recovery-now") + row = await store.fetch_keypair("old") + assert row["bundle_enc_recovery"] == "recovery-now" + await store.close() |