aboutsummaryrefslogtreecommitdiffstats
path: root/packages/meshbay-hub/tests/harness
diff options
context:
space:
mode:
Diffstat (limited to 'packages/meshbay-hub/tests/harness')
-rwxr-xr-xpackages/meshbay-hub/tests/harness/playlist_store_probe.py61
1 files changed, 61 insertions, 0 deletions
diff --git a/packages/meshbay-hub/tests/harness/playlist_store_probe.py b/packages/meshbay-hub/tests/harness/playlist_store_probe.py
index f7573b7..83c00ad 100755
--- a/packages/meshbay-hub/tests/harness/playlist_store_probe.py
+++ b/packages/meshbay-hub/tests/harness/playlist_store_probe.py
@@ -362,6 +362,67 @@ function fakeNode() {
names: readBack ? Object.values(readBack.playlists).map((p) => p.name).sort() : [],
});
+ // ── what a playlist costs, sealed ─────────────────────────────────────
+ //
+ // The cap below is in bytes, but the only number a reader can act on is a
+ // number of tracks — so it is measured here rather than quoted from the
+ // design doc. (Quoting it is how "225 tracks" got written down: §4's
+ // figure is ~270 bytes a track *raw* and ~60 *sealed*, and the raw one was
+ // read for the sealed one.)
+ //
+ // Varied metadata on purpose. A fixture where every track shares an artist
+ // and an album measures deflate's opinion of its own regularity: the first
+ // attempt at this, 400 tracks differing only by id, sealed to 41 bytes a
+ // track and would have let the case below pass while testing nothing.
+ const WORDS = ['aube', 'ciel', 'verre', 'nord', 'ombre', 'pluie', 'fer',
+ 'sel', 'onze', 'rive', 'brume', 'cendre', 'axe', 'lune'];
+ const hex = (n) => [...crypto.getRandomValues(new Uint8Array(n))]
+ .map((b) => b.toString(16).padStart(2, '0')).join('');
+ const word = () => WORDS[Math.floor(Math.random() * WORDS.length)];
+ const storedTrack = (i) => {
+ const artist = `${word()} ${word()}`;
+ const album = `${word()} ${word()} ${1970 + (i % 50)}`;
+ const title = `${word()} ${word()} ${word()}`;
+ return { id: hex(16), g: 'g1', hv: 1, n: `${i % 20} - ${title}.flac`,
+ s: 30000000 + i, p: `${artist}/${album}`, t: title, a: artist,
+ b: album, d: 180 + (i % 300), tn: (i % 20) + 1 };
+ };
+ const sizes = {};
+ for (const n of [100, 500, 1000]) {
+ const tracks = [];
+ for (let i = 0; i < n; i++) tracks.push(storedTrack(i));
+ const sealed = await seal({ v: 1, id: 'measure', rev: 1, device: 'aaa', tracks },
+ bodyKind('measure'), USER, key);
+ sizes[n] = sealed.length;
+ }
+ steps.push({ step: 'what a playlist costs sealed', sizes });
+
+ // ── a playlist too large for one frame is named, not swallowed ────────
+ //
+ // A DataChannel `send()` throws above the max-message-size the far end
+ // advertised, which for the node's aiortc is 65536 — so the node's 1 MB
+ // body cap is unreachable and the real limit is a count of tracks
+ // (docs/playlists.md §15.3). The push used to swallow every per-body
+ // failure in one bare `catch {}`: the playlist stopped leaving the browser
+ // and nothing anywhere said so, which is the silent loss this design
+ // exists to prevent.
+ const bigNode = fakeNode();
+ const longId = await P.createPlaylist(USER, 'Trop longue');
+ const many = [];
+ for (let i = 0; i < 1500; i++) {
+ const st = storedTrack(i);
+ many.push({ ...track(i), id: st.id, name: st.n, display_title: st.t,
+ path: st.p, artist: st.a, album: st.b, duration: st.d });
+ }
+ await P.addTracks(USER, longId, many, 'g1');
+ const bigResult = await P.syncWith(bigNode, USER);
+ steps.push({ step: 'a playlist too large for one frame',
+ result: bigResult,
+ bigKind: bodyKind(longId),
+ // Everything else must still have gone: one oversized
+ // playlist is not a broken sync.
+ stored: bigNode.stored.map((e) => ({ kind: e.kind, bytes: e.bytes })) });
+
// ── a session with no HKDF handle degrades rather than failing ─────────
P.setPlaylistTransport(null);
P.forgetPlaylistKey();