From 47083514c64e94af7035257d4aa707594420aec5 Mon Sep 17 00:00:00 2001 From: Tobi Olusanya Date: Sun, 30 Aug 2026 16:38:42 +0100 Subject: [PATCH] fix(lib): stop collapsing getStreamAddress failures into 'stream not found' --- app/stream/[id]/__tests__/page.test.tsx | 18 +++++++++++++++++ lib/stream.test.ts | 4 ++-- lib/stream.ts | 26 ++++++++++++------------- 3 files changed, 33 insertions(+), 15 deletions(-) diff --git a/app/stream/[id]/__tests__/page.test.tsx b/app/stream/[id]/__tests__/page.test.tsx index ab0a74e..51cf7ac 100644 --- a/app/stream/[id]/__tests__/page.test.tsx +++ b/app/stream/[id]/__tests__/page.test.tsx @@ -146,6 +146,24 @@ describe('StreamPage (app/stream/[id]/page.tsx)', () => { expect(container.textContent).toContain('Stream not found.'); }); + it('surfaces the underlying error instead of "Stream not found" when the address lookup fails', async () => { + mockUseWallet.mockReturnValue({ + publicKey: 'GWALLET', + connected: true, + } as unknown as ReturnType); + mockAddr.mockRejectedValue(new Error('rpc down')); + + await act(async () => { + root.render(React.createElement(StreamPage)); + await Promise.resolve(); + await Promise.resolve(); + await Promise.resolve(); + }); + + expect(container.textContent).toContain('rpc down'); + expect(container.textContent).not.toContain('Stream not found'); + }); + it('derives isSender/isRecipient correctly and renders StreamActions once loaded', async () => { mockUseWallet.mockReturnValue({ publicKey: 'GSENDER', diff --git a/lib/stream.test.ts b/lib/stream.test.ts index 90f765e..a0ff3e2 100644 --- a/lib/stream.test.ts +++ b/lib/stream.test.ts @@ -65,10 +65,10 @@ describe('getStreamAddress', () => { expect(await getStreamAddress(SENDER, 999n)).toBeNull(); }); - it('returns null (not throw) when the underlying call rejects', async () => { + it('lets failures propagate so callers can tell "not found" from "RPC down"', async () => { mockSimulateReadOnly.mockRejectedValue(new Error('rpc down')); const { getStreamAddress } = await import('./stream.js'); - expect(await getStreamAddress(SENDER, 1n)).toBeNull(); + await expect(getStreamAddress(SENDER, 1n)).rejects.toThrow('rpc down'); }); }); diff --git a/lib/stream.ts b/lib/stream.ts index 16f4fef..43ae812 100644 --- a/lib/stream.ts +++ b/lib/stream.ts @@ -50,19 +50,19 @@ export async function getStreamAddress( options?: { signal?: AbortSignal }, ): Promise { if (isMock()) return MOCK_ADDRESSES[streamId.toString()] ?? null; - try { - const result = await simulateReadOnly( - source, - FACTORY()!, - 'stream_address', - [nativeToScVal(streamId, { type: 'u64' })], - options, - ); - if (result.switch().name === 'scvVoid') return null; - return Address.fromScVal(result).toString(); - } catch { - return null; - } + const result = await simulateReadOnly( + source, + FACTORY()!, + 'stream_address', + [nativeToScVal(streamId, { type: 'u64' })], + options, + ); + // scvVoid is DripFactory::stream_address returning Option::None — the only + // case that means "this stream ID does not exist". RPC/network failures and + // a misconfigured factory env var must propagate so callers don't mistake + // an outage for a missing stream. + if (result.switch().name === 'scvVoid') return null; + return Address.fromScVal(result).toString(); } /**