"""Worker readiness and relay-proxy verification tests.""" import json import time import pytest import websocket from nostr_helpers import TEST_SECKEY, make_event class TestWorkerReadiness: def test_supervisor_launches_workers_to_ready(self, relay, browser): browser.get(relay["url"]) deadline = time.time() + 25 while time.time() < deadline: state = browser.execute_script( "return JSON.stringify({wasm: document.body.getAttribute('data-wasm-ready')||''," " rp: !!window.__rpReady, rpErr: window.__rpError||null})" ) data = json.loads(state) if data.get("rp") and data.get("wasm") == "1": break time.sleep(0.3) assert data.get("wasm") == "1", f"app worker did not reach ready: {state}" assert data.get("rp") is True, f"relay-proxy worker did not reach ready: {state}" assert not data.get("rpErr"), f"relay-proxy worker reported error: {data.get('rpErr')}" class TestRelayProxyWorker: def test_relay_proxy_handles_req_returns_eose(self, relay, browser): """Construct a fresh relay-proxy worker, send REQ with empty filter, verify the worker queries IDB and returns EOSE.""" browser.get(relay["url"]) # Wait for page boot to settle. deadline = time.time() + 25 while time.time() < deadline: ready = browser.execute_script( "return document.body.getAttribute('data-wasm-ready') || ''" ) if ready == "1": break time.sleep(0.3) time.sleep(2) result_str = browser.execute_async_script( """ const done = arguments[arguments.length-1]; const w = new Worker('/relay-proxy-wasm-host.mjs', { type: 'module' }); window.__rpTestWorker = w; const log = []; let booted = false, finished = false; const finish = (out) => { if (finished) return; finished = true; done(JSON.stringify(out)); }; w.onmessage = (e) => { if (typeof e.data !== 'string') return; log.push(e.data.slice(0, 120)); if (e.data.startsWith('["READY"]') && !booted) { booted = true; w.postMessage(JSON.stringify(['REQ', 'sub-test', {kinds: [99999], limit: 1}])); } else if (e.data.startsWith('["S_QUERY"')) { // Mock store: return empty result for the query. try { const parsed = JSON.parse(e.data); const reqID = parsed[1]; w.postMessage(JSON.stringify(['SR_QUERY', reqID, '[]'])); } catch (_) {} } else if (e.data.startsWith('["EOSE","sub-test"]')) { finish({ ok: true, log }); } else if (e.data.startsWith('["__ERROR"')) { finish({ ok: false, error: e.data, log }); } }; w.onerror = (e) => finish({ ok: false, error: e.message, log }); w.postMessage({ type: 'init', mode: 'root', wasmUrl: '/relay-proxy.wasm' }); setTimeout(() => finish({ ok: false, error: 'timeout', log }), 15000); """ ) result = json.loads(result_str) assert result.get("ok"), f"REQ -> EOSE round-trip failed: {result}" # Clean up. browser.execute_script("if (window.__rpTestWorker) { window.__rpTestWorker.terminate(); window.__rpTestWorker = null; }") def test_relay_proxy_publishes_via_publish_to(self, relay, browser): """Send a signed EVENT via PUBLISH_TO; verify the test relay received it (independent WS sub).""" unique_content = f"phase2-2-publish-to-{int(time.time() * 1000)}" ev = make_event(TEST_SECKEY, unique_content, kind=1) browser.get(relay["url"]) deadline = time.time() + 25 while time.time() < deadline: ready = browser.execute_script( "return document.body.getAttribute('data-wasm-ready') || ''" ) if ready == "1": break time.sleep(0.3) time.sleep(2) # Subscribe to the test relay BEFORE publishing so we can observe # the event arrive. ws_obs = websocket.create_connection(relay["ws"], timeout=5) try: ws_obs.send(json.dumps(["REQ", "obs", {"kinds": [1], "limit": 0}])) # Drain initial EOSE. deadline = time.time() + 3 while time.time() < deadline: try: ws_obs.settimeout(0.5) msg = ws_obs.recv() if json.loads(msg)[0] == "EOSE": break except Exception: break # Drive the worker to PUBLISH_TO. result_str = browser.execute_async_script( """ const done = arguments[arguments.length-1]; const wsURL = arguments[0]; const eventJSON = arguments[1]; const w = new Worker('/relay-proxy-wasm-host.mjs', { type: 'module' }); window.__rpTestWorker = w; const log = []; let booted = false, finished = false; const finish = (out) => { if (finished) return; finished = true; done(JSON.stringify(out)); }; w.onmessage = (e) => { if (typeof e.data !== 'string') return; log.push(e.data.slice(0, 160)); if (e.data.startsWith('["READY"]') && !booted) { booted = true; const msg = '["PUBLISH_TO",' + eventJSON + ',["' + wsURL + '"]]'; w.postMessage(msg); } else if (e.data.startsWith('["OK"')) { finish({ ok: true, msg: e.data, log }); } else if (e.data.startsWith('["__ERROR"')) { finish({ ok: false, error: e.data, log }); } }; w.onerror = (e) => finish({ ok: false, error: e.message, log }); w.postMessage({ type: 'init', mode: 'root', wasmUrl: '/relay-proxy.wasm' }); setTimeout(() => finish({ ok: false, error: 'timeout', log }), 12000); """, relay["ws"], json.dumps(ev), ) result = json.loads(result_str) assert result.get("ok"), f"PUBLISH_TO didn't get OK: {result}" # Now observe the test relay forwarding the new event on our sub. ws_obs.settimeout(8) saw_event = False deadline = time.time() + 8 while time.time() < deadline: try: msg = ws_obs.recv() parsed = json.loads(msg) if parsed[0] == "EVENT" and parsed[1] == "obs" and parsed[2].get("content") == unique_content: saw_event = True break except Exception: break assert saw_event, "test relay did not receive the published event" finally: ws_obs.close() browser.execute_script("if (window.__rpTestWorker) { window.__rpTestWorker.terminate(); window.__rpTestWorker = null; }") def test_relay_proxy_event_uses_set_write_relays(self, relay, browser): """SET_WRITE_RELAYS configures the worker's default relay list; a subsequent EVENT (no explicit list) publishes to that list.""" unique_content = f"phase2-3-default-relays-{int(time.time() * 1000)}" ev = make_event(TEST_SECKEY, unique_content, kind=1) browser.get(relay["url"]) deadline = time.time() + 25 while time.time() < deadline: ready = browser.execute_script( "return document.body.getAttribute('data-wasm-ready') || ''" ) if ready == "1": break time.sleep(0.3) time.sleep(2) ws_obs = websocket.create_connection(relay["ws"], timeout=5) try: ws_obs.send(json.dumps(["REQ", "obs2", {"kinds": [1], "limit": 0}])) deadline = time.time() + 3 while time.time() < deadline: try: ws_obs.settimeout(0.5) msg = ws_obs.recv() if json.loads(msg)[0] == "EOSE": break except Exception: break result_str = browser.execute_async_script( """ const done = arguments[arguments.length-1]; const wsURL = arguments[0]; const eventJSON = arguments[1]; const w = new Worker('/relay-proxy-wasm-host.mjs', { type: 'module' }); window.__rpTestWorker = w; const log = []; let booted = false, finished = false; const finish = (out) => { if (finished) return; finished = true; done(JSON.stringify(out)); }; w.onmessage = (e) => { if (typeof e.data !== 'string') return; log.push(e.data.slice(0, 160)); if (e.data.startsWith('["READY"]') && !booted) { booted = true; // Configure write relays, then publish (no explicit URL list). w.postMessage(JSON.stringify(['SET_WRITE_RELAYS', [wsURL]])); w.postMessage('["EVENT",' + eventJSON + ']'); } else if (e.data.startsWith('["OK"')) { finish({ ok: true, msg: e.data, log }); } else if (e.data.startsWith('["__ERROR"')) { finish({ ok: false, error: e.data, log }); } }; w.onerror = (e) => finish({ ok: false, error: e.message, log }); w.postMessage({ type: 'init', mode: 'root', wasmUrl: '/relay-proxy.wasm' }); setTimeout(() => finish({ ok: false, error: 'timeout', log }), 12000); """, relay["ws"], json.dumps(ev), ) result = json.loads(result_str) assert result.get("ok"), f"EVENT default-relays didn't OK: {result}" ws_obs.settimeout(8) saw_event = False deadline = time.time() + 8 while time.time() < deadline: try: msg = ws_obs.recv() parsed = json.loads(msg) if parsed[0] == "EVENT" and parsed[1] == "obs2" and parsed[2].get("content") == unique_content: saw_event = True break except Exception: break assert saw_event, "test relay did not receive event published via writeRelays" finally: ws_obs.close() browser.execute_script("if (window.__rpTestWorker) { window.__rpTestWorker.terminate(); window.__rpTestWorker = null; }") @pytest.mark.skip(reason="DM_LIST is handled by the DM worker, not relay-proxy; needs full worker topology") def test_relay_proxy_dm_list_returns_array(self, relay, browser): """DM_LIST queries IDB conversation list. Empty database => empty array.""" browser.get(relay["url"]) deadline = time.time() + 25 while time.time() < deadline: ready = browser.execute_script( "return document.body.getAttribute('data-wasm-ready') || ''" ) if ready == "1": break time.sleep(0.3) time.sleep(2) browser.set_script_timeout(15) result_str = browser.execute_async_script( """ const done = arguments[arguments.length-1]; const w = new Worker('/relay-proxy-wasm-host.mjs', { type: 'module' }); window.__rpTestWorker = w; const log = []; let booted = false, finished = false; const finish = (out) => { if (finished) return; finished = true; done(JSON.stringify(out)); }; w.onmessage = (e) => { if (typeof e.data !== 'string') return; log.push(e.data.slice(0, 160)); if (e.data.startsWith('["READY"]') && !booted) { booted = true; w.postMessage('["DM_LIST"]'); } else if (e.data.startsWith('["DM_LIST",')) { finish({ ok: true, msg: e.data, log }); } else if (e.data.startsWith('["__ERROR"')) { finish({ ok: false, error: e.data, log }); } }; w.onerror = (e) => finish({ ok: false, error: e.message, log }); w.postMessage({ type: 'init', mode: 'root', wasmUrl: '/relay-proxy.wasm' }); setTimeout(() => finish({ ok: false, error: 'timeout', log }), 12000); """ ) result = json.loads(result_str) assert result.get("ok"), f"DM_LIST failed: {result}" # Format is ["DM_LIST", ]; just confirm the array exists. assert "[]" in result["msg"] or "[{" in result["msg"], f"unexpected DM_LIST shape: {result['msg']}" browser.execute_script("if (window.__rpTestWorker) { window.__rpTestWorker.terminate(); window.__rpTestWorker = null; }") def test_relay_proxy_close_stops_event_flow(self, relay, browser): """PROXY → first event arrives. CLOSE the sub. Subsequent publishes to the same relay must NOT produce EVENT messages on the closed subID.""" browser.get(relay["url"]) deadline = time.time() + 25 while time.time() < deadline: ready = browser.execute_script( "return document.body.getAttribute('data-wasm-ready') || ''" ) if ready == "1": break time.sleep(0.3) time.sleep(2) # Seed initial event before subscribing. first_content = f"phase2-8-close-first-{int(time.time() * 1000)}" first_ev = make_event(TEST_SECKEY, first_content, kind=1) ws_seed = websocket.create_connection(relay["ws"], timeout=5) try: ws_seed.send(json.dumps(["EVENT", first_ev])) ws_seed.recv() # OK finally: ws_seed.close() # Subscribe via PROXY, wait for first event, CLOSE, then publish a # second event and verify the worker does not forward it. late_content = f"phase2-8-close-late-{int(time.time() * 1000)}" late_ev = make_event(TEST_SECKEY, late_content, kind=1) result_str = browser.execute_async_script( """ const done = arguments[arguments.length-1]; const wsURL = arguments[0]; const expectedFirst = arguments[1]; const expectedLate = arguments[2]; const lateEvent = arguments[3]; const w = new Worker('/relay-proxy-wasm-host.mjs', { type: 'module' }); window.__rpTestWorker = w; const log = []; const seenLate = []; let booted = false, sawFirst = false, closed = false, finished = false; const finish = (out) => { if (finished) return; finished = true; done(JSON.stringify(out)); }; w.onmessage = (e) => { if (typeof e.data !== 'string') return; log.push(e.data.slice(0, 160)); if (e.data.startsWith('["READY"]') && !booted) { booted = true; w.postMessage(JSON.stringify(['PROXY', 'sub-close', {kinds: [1], limit: 50}, [wsURL]])); } else if (!closed && e.data.startsWith('["EVENT","sub-close"') && e.data.indexOf(expectedFirst) >= 0) { sawFirst = true; closed = true; w.postMessage(JSON.stringify(['CLOSE', 'sub-close'])); // Publish a SECOND event via a separate WS that the test // controls; we use a fetch to the relay's HTTP doesn't help, so // we open a WS in the page itself and publish. const ws2 = new WebSocket(wsURL); ws2.onopen = () => { ws2.send(JSON.stringify(['EVENT', JSON.parse(lateEvent)])); }; ws2.onmessage = (m) => { // Drain OK, then close after 3s observation window. setTimeout(() => { ws2.close(); finish({ ok: true, sawFirst, seenLate, log }); }, 3000); }; } else if (closed && e.data.startsWith('["EVENT","sub-close"') && e.data.indexOf(expectedLate) >= 0) { // BAD: worker forwarded a late event to a closed sub. seenLate.push(e.data); } else if (e.data.startsWith('["__ERROR"')) { finish({ ok: false, error: e.data, log }); } }; w.onerror = (e) => finish({ ok: false, error: e.message, log }); w.postMessage({ type: 'init', mode: 'root', wasmUrl: '/relay-proxy.wasm' }); setTimeout(() => finish({ ok: false, error: 'timeout', sawFirst, seenLate, log }), 20000); """, relay["ws"], first_content, late_content, json.dumps(late_ev), ) result = json.loads(result_str) assert result.get("ok"), f"close test failed: {result}" assert result.get("sawFirst"), f"first event never arrived: {result}" assert not result.get("seenLate"), f"worker forwarded late event after CLOSE: {result.get('seenLate')}" browser.execute_script("if (window.__rpTestWorker) { window.__rpTestWorker.terminate(); window.__rpTestWorker = null; }") def test_relay_proxy_dedupes_duplicate_relay_urls(self, relay, browser): """PROXY with the same URL listed 3x should produce a single connection and a single delivery per event.""" unique_content = f"phase2-8-dedup-{int(time.time() * 1000)}" ev = make_event(TEST_SECKEY, unique_content, kind=1) ws_seed = websocket.create_connection(relay["ws"], timeout=5) try: ws_seed.send(json.dumps(["EVENT", ev])) ws_seed.recv() finally: ws_seed.close() browser.get(relay["url"]) deadline = time.time() + 25 while time.time() < deadline: ready = browser.execute_script( "return document.body.getAttribute('data-wasm-ready') || ''" ) if ready == "1": break time.sleep(0.3) time.sleep(2) result_str = browser.execute_async_script( """ const done = arguments[arguments.length-1]; const wsURL = arguments[0]; const expected = arguments[1]; const w = new Worker('/relay-proxy-wasm-host.mjs', { type: 'module' }); window.__rpTestWorker = w; const log = []; const matchingDeliveries = []; let booted = false, finished = false; const finish = (out) => { if (finished) return; finished = true; done(JSON.stringify(out)); }; w.onmessage = (e) => { if (typeof e.data !== 'string') return; log.push(e.data.slice(0, 160)); if (e.data.startsWith('["READY"]') && !booted) { booted = true; // Same URL three times in the relay list. w.postMessage(JSON.stringify(['PROXY', 'sub-dedup', {kinds: [1], limit: 50}, [wsURL, wsURL, wsURL]])); } else if (e.data.startsWith('["EVENT","sub-dedup"') && e.data.indexOf(expected) >= 0) { matchingDeliveries.push(e.data); // Wait 3s after first delivery to see if duplicates arrive. if (matchingDeliveries.length === 1) { setTimeout(() => finish({ ok: true, matchingDeliveries, log }), 3000); } } else if (e.data.startsWith('["__ERROR"')) { finish({ ok: false, error: e.data, log }); } }; w.onerror = (e) => finish({ ok: false, error: e.message, log }); w.postMessage({ type: 'init', mode: 'root', wasmUrl: '/relay-proxy.wasm' }); setTimeout(() => finish({ ok: false, error: 'timeout', matchingDeliveries, log }), 12000); """, relay["ws"], unique_content, ) result = json.loads(result_str) assert result.get("ok"), f"dedup test failed: {result}" # Exactly one delivery for the deduped relay set. assert len(result["matchingDeliveries"]) == 1, f"expected 1 delivery, got {len(result['matchingDeliveries'])}: {result['matchingDeliveries']}" browser.execute_script("if (window.__rpTestWorker) { window.__rpTestWorker.terminate(); window.__rpTestWorker = null; }") def test_relay_proxy_eose_timer_fires_with_no_relays(self, relay, browser): """PROXY with empty relay list. EOSE should fire after the timer expires (test-overridden to 500ms via SET_PROXY_EOSE_MS).""" browser.get(relay["url"]) deadline = time.time() + 25 while time.time() < deadline: ready = browser.execute_script( "return document.body.getAttribute('data-wasm-ready') || ''" ) if ready == "1": break time.sleep(0.3) time.sleep(2) result_str = browser.execute_async_script( """ const done = arguments[arguments.length-1]; const w = new Worker('/relay-proxy-wasm-host.mjs', { type: 'module' }); window.__rpTestWorker = w; const log = []; let booted = false, finished = false, sentReq = false; let reqStart = 0; const finish = (out) => { if (finished) return; finished = true; done(JSON.stringify(out)); }; w.onmessage = (e) => { if (typeof e.data !== 'string') return; log.push(e.data.slice(0, 160)); if (e.data.startsWith('["READY"]') && !booted) { booted = true; w.postMessage(JSON.stringify(['SET_PROXY_EOSE_MS', 500])); // Empty relay list triggers timer-only path. reqStart = Date.now(); w.postMessage(JSON.stringify(['PROXY', 'sub-eose', {kinds: [99999], limit: 1}, []])); sentReq = true; } else if (sentReq && e.data.startsWith('["EOSE","sub-eose"]')) { const elapsed = Date.now() - reqStart; finish({ ok: true, elapsedMs: elapsed, log }); } else if (e.data.startsWith('["__ERROR"')) { finish({ ok: false, error: e.data, log }); } }; w.onerror = (e) => finish({ ok: false, error: e.message, log }); w.postMessage({ type: 'init', mode: 'root', wasmUrl: '/relay-proxy.wasm' }); setTimeout(() => finish({ ok: false, error: 'timeout', log }), 6000); """ ) result = json.loads(result_str) assert result.get("ok"), f"EOSE timer test failed: {result}" # 500ms timer: should fire within 400-2500ms (loose to absorb worker boot jitter). elapsed = result.get("elapsedMs", 0) assert 300 <= elapsed <= 3000, f"EOSE timer fired in {elapsed}ms (expected ~500): {result}" browser.execute_script("if (window.__rpTestWorker) { window.__rpTestWorker.terminate(); window.__rpTestWorker = null; }") def test_relay_proxy_high_event_volume(self, relay, browser): """Seed THOUSANDS of events into the test relay; PROXY-subscribe; observe whether the worker's bridge calls fail under sustained WS event ingestion.""" N = 2000 # Big tag list per event to inflate the JSON size, simulating # production events with mentions / references. big_tags = [["e", "a" * 64], ["p", "b" * 64], ["t", "tag1"], ["t", "tag2"]] events = [] for i in range(N): content = "x" * 200 + f"-{i}" # ~250 byte content events.append(make_event(TEST_SECKEY, content, kind=42, tags=big_tags, created_at=2000000 + i)) ws_seed = websocket.create_connection(relay["ws"], timeout=15) try: for ev in events: ws_seed.send(json.dumps(["EVENT", ev])) ws_seed.settimeout(30) ok_count = 0 deadline = time.time() + 60 while ok_count < N and time.time() < deadline: try: msg = ws_seed.recv() if json.loads(msg)[0] == "OK": ok_count += 1 except Exception: break print(f"\nseeded {ok_count}/{N} large events to test relay") finally: ws_seed.close() browser.get(relay["url"]) deadline = time.time() + 25 while time.time() < deadline: ready = browser.execute_script( "return document.body.getAttribute('data-wasm-ready') || ''" ) if ready == "1": break time.sleep(0.3) time.sleep(2) result_str = browser.execute_async_script( """ const done = arguments[arguments.length-1]; const wsURL = arguments[0]; const w = new Worker('/relay-proxy-wasm-host.mjs', { type: 'module' }); window.__rpVolWorker = w; const errors = []; let eventCount = 0, gotEose = false; let booted = false; w.onmessage = (e) => { if (typeof e.data !== 'string') return; if (e.data.startsWith('["__ERROR"')) errors.push(e.data); if (e.data.startsWith('["READY"]') && !booted) { booted = true; // No limit on the filter to receive everything the relay holds. w.postMessage(JSON.stringify(['PROXY', 'sub-vol', {kinds: [42], limit: 5000}, [wsURL]])); } else if (e.data.startsWith('["EVENT","sub-vol"')) { eventCount++; } else if (e.data.startsWith('["EOSE","sub-vol"]')) { gotEose = true; setTimeout(() => done(JSON.stringify({ eventCount, errors, gotEose })), 1500); } }; w.onerror = (e) => errors.push('onerror: ' + e.message + ' at ' + e.filename + ':' + e.lineno); w.postMessage({ type: 'init', mode: 'root', wasmUrl: '/relay-proxy.wasm' }); setTimeout(() => done(JSON.stringify({ eventCount, errors, gotEose, finalState: 'timeout' })), 35000); """, relay["ws"], ) result = json.loads(result_str) print(f"\n--- high-volume event ingestion result ---") print(f"events received: {result.get('eventCount')}") print(f"got EOSE: {result.get('gotEose')}") print(f"errors ({len(result.get('errors', []))}):") for e in result.get("errors", [])[:10]: print(f" {e[:300]}") browser.execute_script("if (window.__rpVolWorker) { window.__rpVolWorker.terminate(); window.__rpVolWorker = null; }") # The diagnostic mode wrappers emit __ERROR messages BEFORE re-throwing, # so we have ptr/len/bufLen in the error text. If errors appear, they # are exactly the production bug we are hunting. if result.get("errors"): assert False, f"REPRODUCED: {result['errors'][0][:400]}" assert result.get("eventCount", 0) > 100, f"too few events delivered: {result}" def test_relay_proxy_query_with_seeded_idb(self, relay, browser): """Seed many events into the worker's IDB; query them; verify no RangeError. This is the production-shape regime where the wasm bridge handles a large IDB-result JSON string.""" browser.get(relay["url"]) deadline = time.time() + 25 while time.time() < deadline: ready = browser.execute_script( "return document.body.getAttribute('data-wasm-ready') || ''" ) if ready == "1": break time.sleep(0.3) time.sleep(2) # Seed N events with a single shared kind. # Each event ~600 bytes JSON; 500 events ~300KB total. N = 500 events = [] for i in range(N): content = f"phase2-stress-seed-{i}" events.append(make_event(TEST_SECKEY, content, kind=42, created_at=1000000 + i)) # Send each EVENT to a fresh worker via SaveEvent. The simplest way # is to construct a worker, then drive idb.SaveEvent for each event. # The worker doesn't expose SaveEvent directly via its wire protocol, # so we use the relay's WS to publish, then have the worker PROXY-subscribe # to populate IDB the natural way. ws_seed = websocket.create_connection(relay["ws"], timeout=10) try: for ev in events: ws_seed.send(json.dumps(["EVENT", ev])) # Drain the OK responses (one per event). ws_seed.settimeout(15) ok_count = 0 deadline = time.time() + 20 while ok_count < N and time.time() < deadline: try: msg = ws_seed.recv() if json.loads(msg)[0] == "OK": ok_count += 1 except Exception: break print(f"\nseeded {ok_count}/{N} events to test relay") finally: ws_seed.close() # Subscribe via PROXY; worker writes incoming events to IDB. result_str = browser.execute_async_script( """ const done = arguments[arguments.length-1]; const wsURL = arguments[0]; const w = new Worker('/relay-proxy-wasm-host.mjs', { type: 'module' }); window.__rpStressWorker = w; const errors = []; let eventCount = 0, gotEose = false; let booted = false; w.onmessage = (e) => { if (typeof e.data !== 'string') return; if (e.data.startsWith('["__ERROR"')) errors.push(e.data); if (e.data.startsWith('["READY"]') && !booted) { booted = true; w.postMessage(JSON.stringify(['PROXY', 'sub-stress', {kinds: [42], limit: 1000}, [wsURL]])); } else if (e.data.startsWith('["EVENT","sub-stress"')) { eventCount++; } else if (e.data.startsWith('["EOSE","sub-stress"]')) { gotEose = true; setTimeout(() => done(JSON.stringify({ eventCount, errors, gotEose })), 1000); } }; w.onerror = (e) => errors.push('onerror: ' + e.message + ' at ' + e.filename + ':' + e.lineno); w.postMessage({ type: 'init', mode: 'root', wasmUrl: '/relay-proxy.wasm' }); // Generous timeout because PROXY EOSE timer is 15s by default. setTimeout(() => done(JSON.stringify({ eventCount, errors, gotEose, finalState: 'timeout' })), 25000); """, relay["ws"], ) result = json.loads(result_str) print(f"\n--- seeded-IDB stress result ---") print(f"events received via PROXY: {result.get('eventCount')}") print(f"got EOSE: {result.get('gotEose')}") print(f"errors ({len(result.get('errors', []))}):") for e in result.get("errors", [])[:5]: print(f" {e[:200]}") # Cleanup before assertions to avoid hanging Firefox. browser.execute_script("if (window.__rpStressWorker) { window.__rpStressWorker.terminate(); window.__rpStressWorker = null; }") assert not result.get("errors"), f"worker errored: {result.get('errors', [])[:3]}" # We don't strictly require all N events; just confirm the worker survived. assert result.get("eventCount", 0) > 0, f"worker didn't deliver any events: {result}" def test_relay_proxy_rapid_message_flurry(self, relay, browser): """Send a tight burst of REQ + CLOSE + PROXY + EVENT messages with no inter-message delay, mimicking a feed-re-init storm.""" unique_content = f"phase2-flurry-{int(time.time() * 1000)}" ev = make_event(TEST_SECKEY, unique_content, kind=1) browser.get(relay["url"]) deadline = time.time() + 25 while time.time() < deadline: ready = browser.execute_script( "return document.body.getAttribute('data-wasm-ready') || ''" ) if ready == "1": break time.sleep(0.3) time.sleep(2) result_str = browser.execute_async_script( """ const done = arguments[arguments.length-1]; const wsURL = arguments[0]; const eventJSON = arguments[1]; const w = new Worker('/relay-proxy-wasm-host.mjs', { type: 'module' }); window.__rpFlurryWorker = w; const errors = []; const log = []; let booted = false; w.onmessage = (e) => { if (typeof e.data !== 'string') return; log.push(e.data.slice(0, 80)); if (e.data.startsWith('["__ERROR"')) errors.push(e.data); if (e.data.startsWith('["READY"]') && !booted) { booted = true; // Tight burst: 100 REQs with different subIDs, all in one JS turn. for (let i = 0; i < 100; i++) { w.postMessage('["REQ","sub' + i + '",{"kinds":[' + (i % 20) + '],"limit":50}]'); } // CLOSE half of them. for (let i = 0; i < 50; i++) { w.postMessage('["CLOSE","sub' + i + '"]'); } // 30 PROXY messages with the same relay URL. for (let i = 0; i < 30; i++) { w.postMessage('["PROXY","p' + i + '",{"kinds":[1],"limit":10},["' + wsURL + '"]]'); } // 10 EVENT publishes (signed). for (let i = 0; i < 10; i++) { w.postMessage('["PUBLISH_TO",' + eventJSON + ',["' + wsURL + '"]]'); } setTimeout(() => done(JSON.stringify({ errors, msgCount: log.length, lastLogs: log.slice(-15) })), 8000); } }; w.onerror = (e) => errors.push('onerror: ' + e.message + ' at ' + e.filename + ':' + e.lineno); w.postMessage({ type: 'init', mode: 'root', wasmUrl: '/relay-proxy.wasm' }); setTimeout(() => done(JSON.stringify({ errors, msgCount: log.length, finalState: 'timeout' })), 15000); """, relay["ws"], json.dumps(ev), ) result = json.loads(result_str) print(f"\n--- flurry stress result ---") print(f"msg count: {result.get('msgCount')}") print(f"errors ({len(result.get('errors', []))}):") for e in result.get("errors", [])[:5]: print(f" {e[:200]}") print(f"last 5 messages:") for m in result.get("lastLogs", [])[-5:]: print(f" {m[:120]}") browser.execute_script("if (window.__rpFlurryWorker) { window.__rpFlurryWorker.terminate(); window.__rpFlurryWorker = null; }") assert not result.get("errors"), f"worker errored under message flurry: {result.get('errors', [])[:3]}" def test_relay_proxy_production_boot_sequence(self, relay, browser): """Reproduce the production volume-regime bug: send the same boot-time message sequence the app sends, observe whether the worker's bridge calls start throwing RangeError.""" browser.get(relay["url"]) deadline = time.time() + 25 while time.time() < deadline: ready = browser.execute_script( "return document.body.getAttribute('data-wasm-ready') || ''" ) if ready == "1": break time.sleep(0.3) time.sleep(2) # Hex pubkey; 23 authors filter (matching the production [sub] feed follows=23). pubkey_hex = "00" * 32 authors = [pubkey_hex[:62] + f"{i:02x}" for i in range(23)] feed_filter = json.dumps({"kinds": [1], "authors": authors, "limit": 200}) prof_filter = json.dumps({"kinds": [0, 3, 10002, 10000, 10050]}) result_str = browser.execute_async_script( """ const done = arguments[arguments.length-1]; const wsURL = arguments[0]; const pubkey = arguments[1]; const profFilter = arguments[2]; const feedFilter = arguments[3]; const w = new Worker('/relay-proxy-wasm-host.mjs', { type: 'module' }); window.__rpStressWorker = w; const errors = []; const log = []; let booted = false; const finishAfter = (ms) => setTimeout(() => { done(JSON.stringify({ errors, log: log.slice(-30), msgCount: log.length })); }, ms); w.onmessage = (e) => { if (typeof e.data !== 'string') return; log.push(e.data.slice(0, 200)); if (e.data.startsWith('["__ERROR"')) errors.push(e.data); if (e.data.startsWith('["READY"]') && !booted) { booted = true; // The exact sequence the app sends on boot. w.postMessage('["SET_PUBKEY","' + pubkey + '"]'); w.postMessage('["ENC_KEY","' + ('aa'.repeat(32)) + '"]'); w.postMessage('["PROXY","prof",' + profFilter + ',["' + wsURL + '"]]'); w.postMessage('["PROXY","prof-settings",{"kinds":[30078],"#d":["smesh-settings"],"limit":1},["' + wsURL + '"]]'); w.postMessage('["PROXY","feed",' + feedFilter + ',["' + wsURL + '"]]'); w.postMessage('["PROXY","ntf",{"kinds":[6,7,9735],"#e":[],"limit":500},["' + wsURL + '"]]'); finishAfter(8000); } }; w.onerror = (e) => errors.push('onerror: ' + e.message); w.postMessage({ type: 'init', mode: 'root', wasmUrl: '/relay-proxy.wasm' }); setTimeout(() => done(JSON.stringify({ errors, log: log.slice(-30), msgCount: log.length, finalState: 'never-finished' })), 15000); """, relay["ws"], pubkey_hex, prof_filter, feed_filter, ) result = json.loads(result_str) # Diagnostic-only; print structure. print(f"\n--- production boot stress result ---") print(f"msg count: {result.get('msgCount')}") print(f"errors ({len(result.get('errors', []))}):") for e in result.get("errors", [])[:5]: print(f" {e[:200]}") print(f"last 5 messages:") for m in result.get("log", [])[-5:]: print(f" {m[:160]}") # The bug manifests as __ERROR messages from the worker. assert not result.get("errors"), f"worker errored under production-shape boot: {result.get('errors', [])[:3]}" browser.execute_script("if (window.__rpStressWorker) { window.__rpStressWorker.terminate(); window.__rpStressWorker = null; }") @pytest.mark.skip(reason="DM_HISTORY is handled by the DM worker, not relay-proxy; needs full worker topology") def test_relay_proxy_dm_history_returns_array(self, relay, browser): """DM_HISTORY queries IDB DMs for a peer. Empty database => empty array.""" browser.get(relay["url"]) deadline = time.time() + 25 while time.time() < deadline: ready = browser.execute_script( "return document.body.getAttribute('data-wasm-ready') || ''" ) if ready == "1": break time.sleep(0.3) time.sleep(2) # 64-hex-char dummy peer pubkey. peer = "00" * 32 browser.set_script_timeout(15) result_str = browser.execute_async_script( """ const done = arguments[arguments.length-1]; const peer = arguments[0]; const w = new Worker('/relay-proxy-wasm-host.mjs', { type: 'module' }); window.__rpTestWorker = w; const log = []; let booted = false, finished = false; const finish = (out) => { if (finished) return; finished = true; done(JSON.stringify(out)); }; w.onmessage = (e) => { if (typeof e.data !== 'string') return; log.push(e.data.slice(0, 160)); if (e.data.startsWith('["READY"]') && !booted) { booted = true; // ["DM_HISTORY", peer, limit, until] w.postMessage('["DM_HISTORY","' + peer + '",50,0]'); } else if (e.data.startsWith('["DM_HISTORY",')) { finish({ ok: true, msg: e.data, log }); } else if (e.data.startsWith('["__ERROR"')) { finish({ ok: false, error: e.data, log }); } }; w.onerror = (e) => finish({ ok: false, error: e.message, log }); w.postMessage({ type: 'init', mode: 'root', wasmUrl: '/relay-proxy.wasm' }); setTimeout(() => finish({ ok: false, error: 'timeout', log }), 12000); """, peer, ) result = json.loads(result_str) assert result.get("ok"), f"DM_HISTORY failed: {result}" # Format ["DM_HISTORY", peer, ] - confirm shape. assert peer in result["msg"], f"peer missing from response: {result['msg']}" assert "[]" in result["msg"] or "[{" in result["msg"], f"unexpected DM_HISTORY shape: {result['msg']}" browser.execute_script("if (window.__rpTestWorker) { window.__rpTestWorker.terminate(); window.__rpTestWorker = null; }") def test_relay_proxy_handles_proxy_remote_subscription(self, relay, browser): """Seed an event into the test relay; tell the relay-proxy worker to PROXY-subscribe to that relay; verify the worker forwards the event back. Exercises the full WS pool + remote sub + forward path.""" # Publish a unique kind:1 event to the test relay via raw WS. unique_content = f"phase2-1b-test-{int(time.time())}" ev = make_event(TEST_SECKEY, unique_content, kind=1) ws_seed = websocket.create_connection(relay["ws"], timeout=5) try: ws_seed.send(json.dumps(["EVENT", ev])) # Read OK. resp = ws_seed.recv() assert json.loads(resp)[0] == "OK", f"seed publish failed: {resp}" finally: ws_seed.close() # Now drive a relay-proxy worker via PROXY. browser.get(relay["url"]) deadline = time.time() + 25 while time.time() < deadline: ready = browser.execute_script( "return document.body.getAttribute('data-wasm-ready') || ''" ) if ready == "1": break time.sleep(0.3) time.sleep(2) result_str = browser.execute_async_script( """ const done = arguments[arguments.length-1]; const wsURL = arguments[0]; const expected = arguments[1]; const w = new Worker('/relay-proxy-wasm-host.mjs', { type: 'module' }); window.__rpTestWorker = w; const log = []; let booted = false, finished = false, evMatch = null; const finish = (out) => { if (finished) return; finished = true; done(JSON.stringify(out)); }; w.onmessage = (e) => { if (typeof e.data !== 'string') return; log.push(e.data.slice(0, 160)); if (e.data.startsWith('["READY"]') && !booted) { booted = true; w.postMessage(JSON.stringify(['PROXY', 'sub-proxy', {kinds: [1], limit: 50}, [wsURL]])); } else if (e.data.startsWith('["EVENT","sub-proxy"')) { if (e.data.indexOf(expected) >= 0) { evMatch = e.data; finish({ ok: true, msg: e.data, log }); } } else if (e.data.startsWith('["__ERROR"')) { finish({ ok: false, error: e.data, log }); } }; w.onerror = (e) => finish({ ok: false, error: e.message, log }); w.postMessage({ type: 'init', mode: 'root', wasmUrl: '/relay-proxy.wasm' }); setTimeout(() => finish({ ok: false, error: 'timeout', log }), 12000); """, relay["ws"], unique_content, ) result = json.loads(result_str) assert result.get("ok"), f"PROXY didn't deliver event: {result}" assert unique_content in result.get("msg", ""), f"event content missing: {result}" browser.execute_script("if (window.__rpTestWorker) { window.__rpTestWorker.terminate(); window.__rpTestWorker = null; }")