test_relay_roundtrip.py raw
1 """Relay round-trip tests — verify events flow through the relay and reach the client.
2
3 Tests:
4 1. WebSocket round-trip: publish → subscribe → receive (no browser)
5 2. Relay → Browser: publish event via WS, verify it renders in the feed
6 """
7
8 import json
9 import time
10 import uuid
11
12 import pytest
13 from selenium.webdriver.common.by import By
14
15 from test.nostr_helpers import make_event, TEST_SECKEY, TEST_PUBKEY
16
17
18 class TestWSRoundTrip:
19 """Pure WebSocket relay round-trip — no browser needed."""
20
21 def test_publish_and_receive(self, relay):
22 """Publish an event, subscribe, and verify it comes back."""
23 import websocket
24
25 ws_url = relay["ws"]
26 marker = f"ws-roundtrip-{uuid.uuid4().hex[:8]}"
27 ev = make_event(TEST_SECKEY, marker, kind=1)
28
29 # Publish.
30 pub = websocket.create_connection(ws_url, timeout=5)
31 pub.send(json.dumps(["EVENT", ev]))
32 ok_raw = pub.recv()
33 pub.close()
34 ok = json.loads(ok_raw)
35 assert ok[0] == "OK", f"expected OK, got {ok}"
36 assert ok[2] is True, f"relay rejected event: {ok}"
37
38 # Subscribe and fetch.
39 sub = websocket.create_connection(ws_url, timeout=5)
40 sub_id = f"test-{uuid.uuid4().hex[:8]}"
41 sub.send(
42 json.dumps(["REQ", sub_id, {"ids": [ev["id"]]}])
43 )
44
45 found = False
46 for _ in range(20):
47 msg = json.loads(sub.recv())
48 if msg[0] == "EVENT" and msg[1] == sub_id:
49 assert msg[2]["id"] == ev["id"]
50 assert msg[2]["content"] == marker
51 found = True
52 break
53 if msg[0] == "EOSE":
54 break
55
56 sub.send(json.dumps(["CLOSE", sub_id]))
57 sub.close()
58 assert found, "event not received back from relay"
59
60 def test_live_subscription(self, relay):
61 """Subscribe first, then publish — verify live delivery."""
62 import websocket
63
64 ws_url = relay["ws"]
65 marker = f"live-{uuid.uuid4().hex[:8]}"
66 sub_id = f"live-{uuid.uuid4().hex[:8]}"
67
68 # Subscribe to our test pubkey.
69 sub = websocket.create_connection(ws_url, timeout=10)
70 sub.send(
71 json.dumps(
72 ["REQ", sub_id, {"authors": [TEST_PUBKEY], "kinds": [1], "limit": 5}]
73 )
74 )
75 # Drain EOSE.
76 while True:
77 msg = json.loads(sub.recv())
78 if msg[0] == "EOSE":
79 break
80
81 # Now publish.
82 ev = make_event(TEST_SECKEY, marker, kind=1)
83 pub = websocket.create_connection(ws_url, timeout=5)
84 pub.send(json.dumps(["EVENT", ev]))
85 pub.recv() # OK
86 pub.close()
87
88 # Should arrive on the subscription.
89 sub.settimeout(5)
90 msg = json.loads(sub.recv())
91 assert msg[0] == "EVENT"
92 assert msg[2]["content"] == marker
93
94 sub.send(json.dumps(["CLOSE", sub_id]))
95 sub.close()
96
97
98 class TestBrowserRoundTrip:
99 """Publish events via WebSocket, verify they render in the browser feed."""
100
101 def test_event_appears_in_feed(self, relay, browser, h):
102 """Publish a kind-1 note, set the matching pubkey, verify the note renders."""
103 import websocket
104
105 marker = f"browser-test-{uuid.uuid4().hex[:8]}"
106 ev = make_event(TEST_SECKEY, marker, kind=1)
107
108 # Publish to relay.
109 ws = websocket.create_connection(relay["ws"], timeout=5)
110 ws.send(json.dumps(["EVENT", ev]))
111 ok = json.loads(ws.recv())
112 ws.close()
113 assert ok[2] is True, f"relay rejected: {ok}"
114
115 # Load app with the test pubkey so the feed subscribes.
116 browser.get(relay["url"])
117 time.sleep(1)
118 h.js(f"localStorage.setItem('smesh-pubkey', '{TEST_PUBKEY}')")
119 browser.get(relay["url"])
120
121 # Poll for up to 20s — SW registration + relay connect + subscription.
122 found = False
123 for _ in range(20):
124 time.sleep(1)
125 page_text = h.js("return document.body.innerText") or ""
126 if marker in page_text:
127 found = True
128 break
129
130 if not found:
131 # Diagnostic: dump the page state.
132 status = h.js("return document.body.innerText") or "(empty)"
133 sw_state = h.js(
134 "return navigator.serviceWorker.controller "
135 "? 'active' : 'no controller'"
136 )
137 pytest.fail(
138 f"event '{marker}' not in feed after 20s.\n"
139 f"SW: {sw_state}\nPage: {status[:300]}"
140 )
141
142 def test_localhost_relay_connected(self, relay, browser, h):
143 """Verify the app connects to the localhost relay (our fallback)."""
144 browser.get(relay["url"])
145 time.sleep(1)
146 h.js(f"localStorage.setItem('smesh-pubkey', '{TEST_PUBKEY}')")
147 browser.get(relay["url"])
148 time.sleep(3)
149
150 # Check console for the local relay log line.
151 local_relay = h.js("return document.querySelector('body')?.dataset?.localRelay")
152 # Alternatively, check that the SW connected by looking for console output.
153 # The app logs "local relay: ws://127.0.0.1:3334" on startup.
154 logs = h.js("""
155 var entries = [];
156 if (window.__testLogs) entries = window.__testLogs;
157 return entries.join('|');
158 """)
159 # Even if we can't capture console.log, verify the relay served the page.
160 page = h.js("return document.body.innerText || ''")
161 assert page is not None and len(page) > 0, "page did not load"
162
163 def test_multiple_events_ordered(self, relay, browser, h):
164 """Publish multiple events, verify they appear in the feed."""
165 import websocket
166
167 now = int(time.time())
168 events = []
169 for i in range(3):
170 content = f"order-test-{uuid.uuid4().hex[:6]}-{i}"
171 ev = make_event(TEST_SECKEY, content, kind=1, created_at=now - (2 - i))
172 events.append(ev)
173
174 ws = websocket.create_connection(relay["ws"], timeout=5)
175 for ev in events:
176 ws.send(json.dumps(["EVENT", ev]))
177 ws.recv() # OK
178 ws.close()
179
180 browser.get(relay["url"])
181 time.sleep(1)
182 h.js(f"localStorage.setItem('smesh-pubkey', '{TEST_PUBKEY}')")
183 browser.get(relay["url"])
184
185 # Poll for up to 20s.
186 found_count = 0
187 for _ in range(20):
188 time.sleep(1)
189 page_text = h.js("return document.body.innerText") or ""
190 found_count = sum(1 for ev in events if ev["content"] in page_text)
191 if found_count == len(events):
192 break
193
194 assert found_count == len(events), (
195 f"only {found_count}/{len(events)} events appeared in feed"
196 )
197