aboutsummaryrefslogtreecommitdiffstats
path: root/packages/meshbay-hub/tests
diff options
context:
space:
mode:
Diffstat (limited to 'packages/meshbay-hub/tests')
-rw-r--r--packages/meshbay-hub/tests/harness/reconnect_race_harness.mjs29
-rw-r--r--packages/meshbay-hub/tests/test_reconnect_race.py12
2 files changed, 36 insertions, 5 deletions
diff --git a/packages/meshbay-hub/tests/harness/reconnect_race_harness.mjs b/packages/meshbay-hub/tests/harness/reconnect_race_harness.mjs
index 0f1cd979..606598da 100644
--- a/packages/meshbay-hub/tests/harness/reconnect_race_harness.mjs
+++ b/packages/meshbay-hub/tests/harness/reconnect_race_harness.mjs
@@ -30,6 +30,7 @@ const {
rttMs = 300, // one request → its reply, during the handshake
lateCloseMs = 0, // >0: the replaced channel's close event fires this late
runUntil = 300000,
+ stream = false, // a film playing instead of a download (see below)
} = JSON.parse(process.argv[4] || '{}');
for (const level of ['log', 'warn', 'error', 'info', 'debug']) {
@@ -106,6 +107,7 @@ globalThis.fetch = async (url) => ({
const OPENING = new Set(['handshake', 'handshake_response', 'keypair_bundle_fetch',
'gek_bundle_fetch', 'join_request', 'device_add_request', 'device_hello']);
let refusedBeforeHandshake = 0;
+const refusedTypes = new Set();
let served = 0;
const pcs = [];
@@ -127,6 +129,7 @@ class NodeSession {
const req_id = msg.req_id;
if (!this.authed && !OPENING.has(msg.type)) {
refusedBeforeHandshake += 1;
+ refusedTypes.add(msg.type);
return this.reply({ type: 'error', detail: 'Handshake required', req_id }, 1);
}
switch (msg.type) {
@@ -210,10 +213,26 @@ main.addReconnectListener(() => { reconnected += 1; });
const written = [];
const writable = { write: async (bytes) => { written.push(bytes[0]); } };
-main.connect('node', 'token', 'group', new Uint8Array(32), identity, null, 'u', 'uid',
- undefined, undefined, '', 'pk')
- .then(() => pipelinedDownload(main, 'gek', 'f'.repeat(64), chunks, null, writable, null, ''))
- .then(() => { outcome = 'done'; }, (err) => { outcome = `failed: ${err.message}`; });
+// A film playing (`stream`): the player goes on granting the node room for
+// segments as it plays from its buffer, and on telling it what it sees, through
+// the connection loss and the reconnect, as video-player.js does. A refusal of
+// either is a node error the player shows in place of the film.
+const opened = main.connect('node', 'token', 'group', new Uint8Array(32), identity, null,
+ 'u', 'uid', undefined, undefined, '', 'pk');
+if (stream) {
+ opened.then(() => {
+ main.requestStream('f'.repeat(64));
+ setInterval(() => {
+ main.grantStreamCredit(1);
+ main.sendStreamDiag({ buffered: 1 });
+ }, 50);
+ outcome = 'done';
+ }, (err) => { outcome = `failed: ${err.message}`; });
+} else {
+ opened
+ .then(() => pipelinedDownload(main, 'gek', 'f'.repeat(64), chunks, null, writable, null, ''))
+ .then(() => { outcome = 'done'; }, (err) => { outcome = `failed: ${err.message}`; });
+}
await run(hangAt);
pcs[0].channel.session.hung = true;
@@ -223,7 +242,7 @@ pcs[0].onconnectionstatechange();
await run(runUntil);
process.stdout.write(JSON.stringify({
- outcome, reconnected, refusedBeforeHandshake, served,
+ outcome, reconnected, refusedBeforeHandshake, refusedTypes: [...refusedTypes], served,
written: written.length,
inOrder: written.every((b, i) => b === (i & 255)),
connections: pcs.length,
diff --git a/packages/meshbay-hub/tests/test_reconnect_race.py b/packages/meshbay-hub/tests/test_reconnect_race.py
index 01c51824..539ae268 100644
--- a/packages/meshbay-hub/tests/test_reconnect_race.py
+++ b/packages/meshbay-hub/tests/test_reconnect_race.py
@@ -75,6 +75,18 @@ def test_the_replaced_channels_close_event_is_ignored(late):
assert out["outcome"] == "done" and out["written"] == 400
+@pytest.mark.parametrize("cfg", [{}, {"rttMs": 800}, {"openMs": 2500}])
+def test_a_film_playing_says_nothing_before_the_handshake(cfg):
+ # Reported live on a phone moving from mobile data to Wi-Fi: the player
+ # went on granting stream credit through the reconnect, one grant reached
+ # the new channel before its handshake, and the node's "Handshake required"
+ # replaced the film. A grant or a diagnostic for a stream the lost session
+ # took with it has nowhere to go; the reconnect asks for the stream again.
+ out = _run(stream=True, runUntil=60000, **cfg)
+ assert out["outcome"] == "done" and out["reconnected"] == 1
+ assert out["refusedTypes"] == [], out["refusedTypes"]
+
+
def _node_opening_types() -> set[str]:
"""What the node answers before the handshake, read from its dispatch."""
from meshbay_common.protocol import MNP