aboutsummaryrefslogtreecommitdiffstats
path: root/packages/meshbay-hub/tests/test_network_change.py
diff options
context:
space:
mode:
Diffstat (limited to 'packages/meshbay-hub/tests/test_network_change.py')
-rw-r--r--packages/meshbay-hub/tests/test_network_change.py90
1 files changed, 90 insertions, 0 deletions
diff --git a/packages/meshbay-hub/tests/test_network_change.py b/packages/meshbay-hub/tests/test_network_change.py
new file mode 100644
index 00000000..2298e8e6
--- /dev/null
+++ b/packages/meshbay-hub/tests/test_network_change.py
@@ -0,0 +1,90 @@
+"""
+A connection follows the device to another network.
+
+A phone moving between mobile data and the Wi-Fi the node is on keeps a
+connection whose candidate pair runs over the network it left. ICE says so only
+once its consent checks lapse, about thirty seconds, and on Android not before
+the old network has lingered. Reported on the Android application: the group
+stopped answering and the application had to be killed; in Chrome, a group that
+had failed on Wi-Fi opened on mobile data only after a pull-to-refresh.
+
+Now a change of network asks the open connection for a ping, and a connection
+that does not answer is reconnected; a reconnect that is backing off tries again
+at once; and a group page left on an error tries again (group-page.js).
+
+These run the shipped transport: harness/network_change_harness.mjs.
+"""
+
+import json
+import re
+import shutil
+import subprocess
+from pathlib import Path
+
+import pytest
+from spa_source import transport_argv
+
+STATIC = Path(__file__).resolve().parents[1] / "src" / "meshbay_hub" / "static"
+HARNESS = Path(__file__).parent / "harness" / "network_change_harness.mjs"
+
+pytestmark = pytest.mark.skipif(shutil.which("node") is None, reason="node is not available")
+
+# Connected at 5 s, the network changes at 5 s: settled 1.5 s later, a ping
+# unanswered for 5 s, the first reconnect attempt 1 s after that.
+CHANGED_AT_MS = 5000
+
+
+def _run(scenario: str) -> dict:
+ proc = subprocess.run(["node", str(HARNESS), transport_argv(),
+ json.dumps({"scenario": scenario})],
+ capture_output=True, text=True, encoding="utf-8", timeout=120)
+ assert proc.returncode == 0, proc.stderr
+ return json.loads(proc.stdout)
+
+
+def test_a_connection_left_on_the_old_network_is_reconnected():
+ out = _run("change-dead")
+ assert out["connected"]
+ assert out["connections"] == 2, out
+ # Long before ICE's own thirty seconds would have declared it failed.
+ assert out["reconnectedAt"] - CHANGED_AT_MS < 10000, out
+
+
+def test_a_connection_that_still_answers_is_kept():
+ out = _run("change-alive")
+ assert out["connections"] == 1 and out["reconnectedAt"] is None, out
+ assert out["pings"] == 1
+
+
+def test_an_ice_disconnection_is_checked_rather_than_waited_out():
+ dead = _run("ice-dead")
+ assert dead["connections"] == 2 and dead["reconnectedAt"] - CHANGED_AT_MS < 10000, dead
+ alive = _run("ice-alive")
+ assert alive["connections"] == 1 and alive["reconnectedAt"] is None, alive
+
+
+def test_a_reconnect_backing_off_tries_again_on_the_new_network():
+ """Without the wake-up, the attempt after 30 s of failures waits 16 s more."""
+ out = _run("backoff")
+ assert out["reconnectedAt"] is not None, out
+ assert out["reconnectedAt"] - out["backAt"] < 3000, out
+
+
+def test_only_a_change_of_network_counts_and_once():
+ """A bandwidth estimate is not news; Wi-Fi against mobile data, `online` and
+ the application's own event are, and a burst of them is one change."""
+ out = _run("events")
+ assert out["calls"] == [0, 1, 2, 3, 4, 4], out
+ assert out["leftListening"] == 0, out
+
+
+def test_a_closed_transport_stops_listening():
+ out = _run("closed")
+ assert out["connections"] == 1 and out["leftListening"] == 0, out
+
+
+def test_a_group_left_on_an_error_tries_again_when_the_network_changes():
+ source = (STATIC / "group-page.js").read_text(encoding="utf-8")
+ block = re.search(r"onNetworkChange\(.*?\)", source, re.S)
+ assert block, "the group page does not listen for another network"
+ assert "setRetryKey" in source[block.start():block.start() + 200]