feat: answer REQ with CLOSED when no relay is configured

Ported from the upstream PR (lnbits/nostrclient#74, idea from #68).
Nothing will ever serve a subscription when nostrclient has no relay
at all, so refuse it per NIP-01 instead of registering it silently.
Keyed on "no relay configured" rather than "none connected": a
configured relay that is merely reconnecting gets the cached
subscription replayed, so refusing the REQ there would be a regression.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_013Tbyw6FwjhEJg3gHfPHxWt
This commit is contained in:
Padreug 2026-09-13 16:07:37 +02:00
commit 7e7120fa82
3 changed files with 43 additions and 3 deletions

View file

@ -25,10 +25,14 @@ class FakeRelayManager:
def __init__(self, relays: dict[str, FakeRelay]):
self.relays = relays
self.published: list[str] = []
self.subscriptions: list[str] = []
def publish_message(self, message: str):
self.published.append(message)
def add_subscription(self, subscription_id: str, filters):
self.subscriptions.append(subscription_id)
def close_subscriptions(self, subscriptions):
pass
@ -139,3 +143,25 @@ async def test_stop_drops_pending_publishes(monkeypatch):
assert router.pending_publishes == {}
assert NostrRouter.received_command_results == {}
@pytest.mark.asyncio
async def test_req_without_configured_relays_is_closed(monkeypatch):
router, ws, manager = _router(monkeypatch, {})
await router._handle_client_to_nostr(json.dumps(["REQ", "sub1", {"kinds": [1]}]))
assert ws.sent == [["CLOSED", "sub1", "error: no relay connections"]]
assert router.subscriptions == []
assert manager.subscriptions == []
@pytest.mark.asyncio
async def test_req_with_configured_but_disconnected_relay_is_kept(monkeypatch):
router, ws, manager = _router(monkeypatch, {"wss://a": FakeRelay(False)})
await router._handle_client_to_nostr(json.dumps(["REQ", "sub1", {"kinds": [1]}]))
assert ws.sent == []
assert len(router.subscriptions) == 1
assert len(manager.subscriptions) == 1