1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
|
"""
An offer the hub refuses for load is sent again; one refused for cause is not.
A phone's Search reported groups as unreachable whose node was answering every
offer that reached it: the hub had refused those offers with 429, one of its
per-account ceilings, and the browser took that for the node. A 429 — or a 502
or 503 while the hub restarts behind its proxy — says the hub is busy, so
`postOffer` waits (the hub's `Retry-After` when it gives one) and sends the same
offer again. A 404 for a node that is not connected, or a 504 for one that did
not answer, fails at once: retrying those would make a dead node cost time.
These run the shipped `postOffer`, lifted out of transport.js as text, against
a fake hub and a fake clock — see harness/offer_retry_harness.mjs.
"""
import json
import shutil
import subprocess
from pathlib import Path
import pytest
STATIC = Path(__file__).resolve().parents[1] / "src" / "meshbay_hub" / "static"
TRANSPORT = STATIC / "transport.js"
HARNESS = Path(__file__).parent / "harness" / "offer_retry_harness.mjs"
pytestmark = pytest.mark.skipif(
shutil.which("node") is None or not TRANSPORT.exists(),
reason="node or the SPA sources are not available")
def _post(**cfg) -> dict:
proc = subprocess.run(
["node", str(HARNESS), str(TRANSPORT), json.dumps(cfg)],
capture_output=True, text=True)
assert proc.returncode == 0, proc.stderr
return json.loads(proc.stdout)
def test_a_busy_hub_is_asked_again_when_it_says():
out = _post(answers=[[429, 1], 200])
assert out["result"] == "answered"
assert out["posts"] == [0, 1000], "Retry-After was not honoured"
def test_without_retry_after_the_waits_grow():
out = _post(answers=[429, 429, 503, 200])
assert out["result"] == "answered"
assert out["posts"] == [0, 500, 1500, 3500]
def test_a_node_that_is_not_there_fails_at_once():
for status in (404, 403, 504):
out = _post(answers=[status])
assert out["result"] == "failed"
assert out["status"] == status
assert out["posts"] == [0], f"{status} was retried"
def test_a_hub_that_stays_busy_is_given_up_on():
out = _post(answers=[429])
assert out["result"] == "failed"
assert out["status"] == 429
assert len(out["posts"]) == 6
assert out["at"] <= 20000, "a busy hub cost more than the worst case stated"
def test_a_retry_after_that_asks_too_much_is_capped():
out = _post(answers=[[429, 3600], 200])
assert out["posts"] == [0, 10000]
def test_a_caller_that_gave_up_sends_nothing_more():
"""Search's deadline, or a page that went away, closes the transport while
it waits; the offer must not go out on its behalf afterwards."""
out = _post(answers=[429], closeAt=100)
assert out["result"] == "failed"
assert out["posts"] == [0]
|