summaryrefslogtreecommitdiffstats
path: root/packages/meshbay-node/tests/test_bundle_store_recovery.py
diff options
context:
space:
mode:
Diffstat (limited to 'packages/meshbay-node/tests/test_bundle_store_recovery.py')
-rw-r--r--packages/meshbay-node/tests/test_bundle_store_recovery.py76
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()