Skip to content

Commit 7f04ae0

Browse files
committed
test(copilot): pin that a failed delivery still reaches the stream buffer
Streaming preview content as deltas is only safe because the replay chain is complete: `publish` persists after enqueuing and unconditionally, and a failed enqueue marks the client disconnected rather than throwing. So an envelope the client never received is still in Redis, and the producer is never told a delivery failed — it cannot advance past a gap the buffer does not have. That invariant was load-bearing and untested. Making persistence conditional on delivery now fails this test.
1 parent 19670b7 commit 7f04ae0

1 file changed

Lines changed: 52 additions & 0 deletions

File tree

apps/sim/lib/copilot/request/session/writer.test.ts

Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -186,4 +186,56 @@ describe('StreamWriter', () => {
186186
}),
187187
])
188188
})
189+
190+
/**
191+
* A delivery failure must not cost the buffer an envelope.
192+
*
193+
* Preview content streams as deltas, so the replay chain is only reconstructible if
194+
* every envelope reaches Redis — including one the client never received. `publish`
195+
* persists after enqueuing and unconditionally, and a failed enqueue marks the
196+
* client disconnected rather than throwing, so the producer is never told a delivery
197+
* failed and never advances past a gap the buffer does not have.
198+
*/
199+
it('persists an envelope whose delivery failed, and stops enqueuing after', async () => {
200+
appendEvents.mockResolvedValue(undefined)
201+
202+
const writer = new StreamWriter({
203+
streamId: 'stream-gap',
204+
chatId: 'chat-gap',
205+
requestId: 'req-gap',
206+
})
207+
208+
let enqueueCalls = 0
209+
const controller = {
210+
enqueue: vi.fn(() => {
211+
enqueueCalls += 1
212+
throw new Error('client gone')
213+
}),
214+
close: vi.fn(),
215+
} as unknown as ReadableStreamDefaultController
216+
217+
writer.attach(controller)
218+
219+
expect(() =>
220+
writer.publish({
221+
type: MothershipStreamV1EventType.text,
222+
payload: { channel: MothershipStreamV1TextChannel.assistant, text: 'one' },
223+
} as StreamEvent)
224+
).not.toThrow()
225+
226+
writer.publish({
227+
type: MothershipStreamV1EventType.text,
228+
payload: { channel: MothershipStreamV1TextChannel.assistant, text: 'two' },
229+
} as StreamEvent)
230+
231+
await writer.flush()
232+
233+
const persisted = appendEvents.mock.calls.flatMap(
234+
([envelopes]: [Array<{ payload?: { text?: string } }>]) => envelopes
235+
)
236+
expect(persisted.map((envelope) => envelope.payload?.text)).toEqual(['one', 'two'])
237+
expect(writer.clientDisconnected).toBe(true)
238+
// The failed enqueue disconnects; nothing is pushed at the dead controller again.
239+
expect(enqueueCalls).toBe(1)
240+
})
189241
})

0 commit comments

Comments
 (0)