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