aboutsummaryrefslogtreecommitdiffstats
path: root/packages/meshbay-hub/tests/harness
diff options
context:
space:
mode:
Diffstat (limited to 'packages/meshbay-hub/tests/harness')
-rw-r--r--packages/meshbay-hub/tests/harness/cast_session_probe.py1
-rw-r--r--packages/meshbay-hub/tests/harness/group_tab_probe.py1
-rw-r--r--packages/meshbay-hub/tests/harness/music_grid_probe.py1
-rwxr-xr-xpackages/meshbay-hub/tests/harness/music_queue_probe.py1
-rw-r--r--packages/meshbay-hub/tests/harness/music_untagged_probe.py1
-rw-r--r--packages/meshbay-hub/tests/harness/network_change_harness.mjs70
-rw-r--r--packages/meshbay-hub/tests/harness/playlist_ui_probe.py1
-rwxr-xr-xpackages/meshbay-hub/tests/harness/sticky_header_probe.py1
-rw-r--r--packages/meshbay-hub/tests/harness/video_series_probe.py1
9 files changed, 74 insertions, 4 deletions
diff --git a/packages/meshbay-hub/tests/harness/cast_session_probe.py b/packages/meshbay-hub/tests/harness/cast_session_probe.py
index 573dd950..901d27cd 100644
--- a/packages/meshbay-hub/tests/harness/cast_session_probe.py
+++ b/packages/meshbay-hub/tests/harness/cast_session_probe.py
@@ -99,6 +99,7 @@ window.MeshBayTransport = function () {
// One chunk per photo: every photo is the same picture.
async fetchChunk() { return { nonce: 'n', ct: await photoBytes() }; },
addReconnectListener() { return () => {}; },
+ addReconnectFailureListener() { return () => {}; },
close() {},
};
return new Proxy(self, {
diff --git a/packages/meshbay-hub/tests/harness/group_tab_probe.py b/packages/meshbay-hub/tests/harness/group_tab_probe.py
index e23a9462..a430c625 100644
--- a/packages/meshbay-hub/tests/harness/group_tab_probe.py
+++ b/packages/meshbay-hub/tests/harness/group_tab_probe.py
@@ -73,6 +73,7 @@ window.MeshBayTransport = class {
// unmount; a stub without this throws inside connect() and the page
// renders its error state instead of a tab bar.
addReconnectListener() { return () => {}; }
+ addReconnectFailureListener() { return () => {}; }
// What the node should hold of our identity, settled after connecting.
async settleNodeBundle() {}
close() {}
diff --git a/packages/meshbay-hub/tests/harness/music_grid_probe.py b/packages/meshbay-hub/tests/harness/music_grid_probe.py
index 4f5b0ce7..1995b2a1 100644
--- a/packages/meshbay-hub/tests/harness/music_grid_probe.py
+++ b/packages/meshbay-hub/tests/harness/music_grid_probe.py
@@ -81,6 +81,7 @@ window.MeshBayTransport = function () {
// Real, not left to the Proxy below: that would hand back a promise
// where an unsubscribe belongs, and the page calls it on unmount.
addReconnectListener() { return () => {}; },
+ addReconnectFailureListener() { return () => {}; },
close() {},
};
return new Proxy(self, {
diff --git a/packages/meshbay-hub/tests/harness/music_queue_probe.py b/packages/meshbay-hub/tests/harness/music_queue_probe.py
index 9be440b3..83c2972e 100755
--- a/packages/meshbay-hub/tests/harness/music_queue_probe.py
+++ b/packages/meshbay-hub/tests/harness/music_queue_probe.py
@@ -80,6 +80,7 @@ window.MeshBayTransport = function () {
// Real, not left to the Proxy below: that would hand back a promise
// where an unsubscribe belongs, and the page calls it on unmount.
addReconnectListener() { return () => {}; },
+ addReconnectFailureListener() { return () => {}; },
close() {},
};
return new Proxy(self, {
diff --git a/packages/meshbay-hub/tests/harness/music_untagged_probe.py b/packages/meshbay-hub/tests/harness/music_untagged_probe.py
index f4c484c3..7af1efc7 100644
--- a/packages/meshbay-hub/tests/harness/music_untagged_probe.py
+++ b/packages/meshbay-hub/tests/harness/music_untagged_probe.py
@@ -110,6 +110,7 @@ window.MeshBayTransport = function () {
async fetchChatHistory() { return { messages: [], hasMore: false }; },
async fetchLinkPreview() { return { ok: false }; },
addReconnectListener() { return () => {}; },
+ addReconnectFailureListener() { return () => {}; },
close() {},
};
return new Proxy(self, {
diff --git a/packages/meshbay-hub/tests/harness/network_change_harness.mjs b/packages/meshbay-hub/tests/harness/network_change_harness.mjs
index e0201125..a8bd0809 100644
--- a/packages/meshbay-hub/tests/harness/network_change_harness.mjs
+++ b/packages/meshbay-hub/tests/harness/network_change_harness.mjs
@@ -12,7 +12,8 @@
//
// Usage: node network_change_harness.mjs <transport sources> <json>
// {"scenario": "change-dead" | "change-alive" | "backoff" | "ice-dead" | "ice-alive"
-// | "events" | "closed"}
+// | "events" | "closed" | "refused"
+// | "refused-during"}
import { readFileSync } from 'fs';
import { delimiter } from 'path';
@@ -98,12 +99,21 @@ window.MeshBayCrypto = {
};
const SDP = 'v=0\r\na=fingerprint:sha-256 AA:BB\r\n';
+const candidate = (address, kind) => `a=candidate:1 1 udp 2130706431 ${address} 50000 typ ${kind}\r\n`;
+// On the node's Wi-Fi without the permission (test_local_network_refused.py):
+// the phone's offer and the node's answer as measured, and the node's address
+// on the phone's subnet missing from what the browser paired.
+const WIFI_OFFER = SDP + candidate('192.168.1.13', 'host') + candidate('90.112.206.172', 'srflx');
+const NODE_ANSWER = SDP + candidate('192.168.1.22', 'host') + candidate('90.112.206.172', 'srflx');
+const KEPT_WHEN_REFUSED = ['90.112.206.172'];
let offline = false;
+let lanRefused = false;
globalThis.fetch = async (url) => {
if (offline) throw new TypeError('Failed to fetch');
return {
ok: true, status: 200, headers: { get: () => null },
- json: async () => (url.endsWith('/mnp-token') ? { mnp_token: 't' } : { sdp: SDP }),
+ json: async () => (url.endsWith('/mnp-token') ? { mnp_token: 't' }
+ : { sdp: lanRefused ? NODE_ANSWER : SDP }),
};
};
@@ -167,10 +177,30 @@ class FakePC {
pcs.push(this);
}
createDataChannel() { this.channel = new FakeChannel(); return this.channel; }
- async createOffer() { return { type: 'offer', sdp: SDP }; }
+ async createOffer() { return { type: 'offer', sdp: lanRefused ? WIFI_OFFER : SDP }; }
async setLocalDescription(d) { this.localDescription = d; }
- async getStats() { return new Map(); }
+ async getStats() {
+ const reports = new Map();
+ if (this.refused) {
+ KEPT_WHEN_REFUSED.forEach((address, i) => {
+ reports.set(`R${i}`, { id: `R${i}`, type: 'remote-candidate', address });
+ });
+ }
+ return reports;
+ }
async setRemoteDescription() {
+ if (lanRefused) {
+ // ICE gives up, and the channel goes with it, as Chrome reports it:
+ // there after thirty seconds, here after fifteen.
+ this.refused = true;
+ setTimeout(() => {
+ if (this.connectionState === 'closed') return;
+ this.iceConnectionState = 'failed';
+ this.connectionState = 'failed';
+ this.channel.close();
+ }, 15000);
+ return;
+ }
setTimeout(() => {
if (this.connectionState === 'closed') return;
this.channel.session = new NodeSession(this.channel);
@@ -218,6 +248,8 @@ if (scenario === 'events') {
const main = new Transport('https://hub.invalid', 'token');
let reconnectedAt = null;
main.addReconnectListener(() => { if (reconnectedAt === null) reconnectedAt = now; });
+ const failures = [];
+ main.addReconnectFailureListener((err) => { failures.push(err.reason || null); });
let connected = false;
const identity = { pkEdB64: 'ed', signAs: async () => 'sig', native: false };
main.connect('node', 'token', 'group', new Uint8Array(32), identity, null, 'u', 'uid',
@@ -252,6 +284,36 @@ if (scenario === 'events') {
connection.dispatch('change');
out.backAt = now;
await run(80000);
+ } else if (scenario === 'refused-during') {
+ // The same, with the permission granted while an attempt set up before it
+ // is still waiting on ICE: that attempt is given up, the next goes at once.
+ lanRefused = true;
+ first.channel.session.cut = true;
+ connection.type = 'wifi';
+ connection.dispatch('change');
+ await run(40000);
+ const attempts = pcs.length;
+ while (pcs.length === attempts) await run(now + 100);
+ await run(now + 2000);
+ lanRefused = false;
+ out.backAt = now;
+ main.retryNow();
+ await run(now + 40000);
+ } else if (scenario === 'refused') {
+ // Connected on mobile data, then on the node's Wi-Fi without the local
+ // network permission: every attempt is refused, and says so. The
+ // permission is granted after a while, and the page asks to try now.
+ lanRefused = true;
+ first.channel.session.cut = true;
+ connection.type = 'wifi';
+ connection.dispatch('change');
+ await run(60000);
+ out.failures = [...failures];
+ out.stillReconnecting = main.reconnecting;
+ lanRefused = false;
+ out.backAt = now;
+ main.retryNow();
+ await run(80000);
} else if (scenario === 'closed') {
main.close();
connection.type = 'wifi';
diff --git a/packages/meshbay-hub/tests/harness/playlist_ui_probe.py b/packages/meshbay-hub/tests/harness/playlist_ui_probe.py
index 6e3be1e2..0d6e4134 100644
--- a/packages/meshbay-hub/tests/harness/playlist_ui_probe.py
+++ b/packages/meshbay-hub/tests/harness/playlist_ui_probe.py
@@ -76,6 +76,7 @@ window.MeshBayTransport = function () {
// Real, not left to the Proxy below: that would hand back a promise
// where an unsubscribe belongs, and the page calls it on unmount.
addReconnectListener() { return () => {}; },
+ addReconnectFailureListener() { return () => {}; },
close() {},
};
return new Proxy(self, {
diff --git a/packages/meshbay-hub/tests/harness/sticky_header_probe.py b/packages/meshbay-hub/tests/harness/sticky_header_probe.py
index 3e75494b..89fb2827 100755
--- a/packages/meshbay-hub/tests/harness/sticky_header_probe.py
+++ b/packages/meshbay-hub/tests/harness/sticky_header_probe.py
@@ -240,6 +240,7 @@ window.MeshBayTransport = function () {
// Real, not left to the Proxy below: that would hand back a promise
// where an unsubscribe belongs, and the page calls it on unmount.
addReconnectListener() { return () => {}; },
+ addReconnectFailureListener() { return () => {}; },
close() {},
};
return new Proxy(self, {
diff --git a/packages/meshbay-hub/tests/harness/video_series_probe.py b/packages/meshbay-hub/tests/harness/video_series_probe.py
index f161c305..2b9ade30 100644
--- a/packages/meshbay-hub/tests/harness/video_series_probe.py
+++ b/packages/meshbay-hub/tests/harness/video_series_probe.py
@@ -75,6 +75,7 @@ window.MeshBayTransport = function () {
// what TMDB would have said.
async fetchMediaMeta() { return { confidence: 0 }; },
addReconnectListener() { return () => {}; },
+ addReconnectFailureListener() { return () => {}; },
close() {},
};
// Everything else the player asks for never answers: it sits on its