aboutsummaryrefslogtreecommitdiffstats
path: root/packages/meshbay-node/tests
diff options
context:
space:
mode:
authorChristophe Besson <cbesson@gmail.com>2026-09-16 16:07:18 +0200
committerChristophe Besson <cbesson@gmail.com>2026-09-16 16:07:18 +0200
commit2916aa376009283305a7acec4aafa3c96544499e (patch)
treebc19d010e8bebf39c175271564b3be01d1c06aa0 /packages/meshbay-node/tests
parent25f62e169e049f0757ef611d5b409e7523958dee (diff)
downloadmeshbay-2916aa376009283305a7acec4aafa3c96544499e.tar.gz
playlists: make the writes actually leave the browser
Reported from a phone: signing in with the same account showed no playlists. syncWith was called from exactly one place in the interface, so creating a playlist, deleting one, removing a track and saving the queue all wrote to IndexedDB and stopped there. The store pushes itself now, coalesced, so a new mutation cannot forget to. Silence was the real defect. The node audited only successes, so a refusal left no trace and user_blob_list none at all; the background push swallowed its reason; the interface said nothing. All three report now, and "Sync now" says what happened either way. An unreadable blob on a node was treated as a fetch failure and returned before the push — permanent, once the node held anything. It is an absence: the client is the authority, and it gets overwritten. A sign-in reconciles whatever this browser already holds, a pending push is flushed when the page goes away, and a push that did not land is retried once. no_key is spelled out: a client that signs in with its remembered device key only ever has a bundle key persisted before the playlist subkey existed, and an AES handle is non-extractable. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Diffstat (limited to 'packages/meshbay-node/tests')
-rw-r--r--packages/meshbay-node/tests/test_user_blob_mnp.py31
1 files changed, 31 insertions, 0 deletions
diff --git a/packages/meshbay-node/tests/test_user_blob_mnp.py b/packages/meshbay-node/tests/test_user_blob_mnp.py
index 296239f..85893af 100644
--- a/packages/meshbay-node/tests/test_user_blob_mnp.py
+++ b/packages/meshbay-node/tests/test_user_blob_mnp.py
@@ -242,6 +242,37 @@ async def test_an_unauthenticated_session_reaches_nothing(store):
@pytest.mark.asyncio
+@pytest.mark.parametrize("msg, why", [
+ ({"kind": "nonsense", "rev": 1, "blob_enc": b"x"}, "Unknown blob kind"),
+ ({"kind": "playlists", "rev": 1}, "Missing blob_enc"),
+ ({"kind": "playlists", "blob_enc": b"x"}, "Missing rev"),
+ ({"kind": "playlists", "rev": 1,
+ "blob_enc": b"x" * (USER_BLOB_MANIFEST_MAX + 1)}, "too large"),
+])
+async def test_a_refusal_is_audited_and_not_merely_sent(store, msg, why):
+ """A refusal used to leave no trace at all: the audit line was written only
+ after a store *succeeded*. So a client whose every write was being turned
+ away looked exactly like a client that never wrote — which is how a wedged
+ sync went unnoticed, with the operator's own log saying nothing."""
+ s = _session(store)
+ await s._do_user_blob_store(msg)
+ assert _last(s)["type"] == "error"
+ events = [(e, d) for e, d in s.audited if e == "user_blob_refused"]
+ assert events, "the refusal is invisible in the audit log"
+ assert why in events[0][1]
+
+
+@pytest.mark.asyncio
+async def test_a_listing_is_audited(store):
+ """Half the traffic was invisible: `user_blob_list` is what every sync does
+ first, and it wrote no audit line, so the log could not distinguish a client
+ that was syncing from one that was not."""
+ s = _session(store)
+ await s._do_user_blob_list()
+ assert ("user_blob_list", "0 blobs") in s.audited
+
+
+@pytest.mark.asyncio
async def test_reads_and_writes_are_audited(store):
"""The node logs that a blob moved, never what was in it — the same line
`keypair_bundle_store` already writes."""