| 1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192 |
- // @vitest-environment node
- import { afterEach, beforeEach, expect, it, vi } from 'vitest'
- import { mkdtempSync, readFileSync, rmSync } from 'node:fs'
- import { tmpdir } from 'node:os'
- import { join } from 'node:path'
- import { RealtimeReceipts } from '../../src/server/realtimeReceipts'
- import { RealtimeBroker, type RealtimePrincipal } from '../../src/server/realtimeBroker'
- let home: string, receipts: RealtimeReceipts
- beforeEach(() => {
- home = mkdtempSync(join(tmpdir(), 'yaoyao-receipt-recovery-'))
- receipts = new RealtimeReceipts(home)
- })
- afterEach(() => { receipts.close(); vi.restoreAllMocks(); rmSync(home, { recursive: true, force: true }) })
- it('retains owner-scoped deduplication after result expiry and database reopen', () => {
- const now = vi.spyOn(Date, 'now').mockReturnValue(100_000)
- receipts.reserve('alice', 'prompt-1', 'private prompt')
- receipts.finish('alice', 'prompt-1', 'confirmed', { result: { status: 'submitted' } })
- now.mockReturnValue(100_000 + 86_400_001)
- receipts.reserve('alice', 'new-command', '{}')
- receipts.close(); receipts = new RealtimeReceipts(home)
- expect(receipts.lookup('alice', 'prompt-1')).toEqual({ requestId: 'prompt-1', state: 'unknown' })
- expect(receipts.reserve('alice', 'prompt-1', 'private prompt')).toMatchObject({ state: 'unknown' })
- expect(() => receipts.reserve('alice', 'prompt-1', 'changed payload')).toThrow('payload conflict')
- expect(receipts.lookup('bob', 'prompt-1')).toBeUndefined()
- expect(receipts.reserve('bob', 'prompt-1', 'private prompt')).toBeUndefined()
- for (const file of ['realtime-receipts.sqlite3', 'realtime-receipts.sqlite3-wal']) {
- expect(readFileSync(join(home, file)).includes(Buffer.from('private prompt'))).toBe(false)
- }
- })
- it('does not call the executor a second time after expired results are compacted', async () => {
- receipts.close()
- const now = vi.spyOn(Date, 'now').mockReturnValue(100_000)
- const broker = new RealtimeBroker(home)
- const perform = vi.fn(async () => ({ result: { status: 'submitted' } }))
- Object.assign(broker, { perform })
- const principal: RealtimePrincipal = { key: 'alice', upstreamKey: 'mock', paired: false,
- valid: () => true, url: async () => { throw new Error('No network allowed') } }
- try {
- const channel = { kind: 'chat', principal } as Parameters<RealtimeBroker['command']>[0]
- const frame = { method: 'prompt.submit', params: { session_id: 'runtime', text: 'do work' } }
- await broker.command(channel, 'prompt-1', frame)
- now.mockReturnValue(100_000 + 86_400_001)
- await broker.command(channel, 'new-prompt', frame)
- // Simulate losing the process-local full response, as on a restart.
- Object.assign(broker, { completed: new Map() })
- expect(await broker.command(channel, 'prompt-1', frame)).toMatchObject({ state: 'unknown' })
- expect(perform).toHaveBeenCalledTimes(2)
- } finally {
- broker.close()
- await Promise.resolve()
- receipts = new RealtimeReceipts(home)
- }
- })
- it.each(['file.attach', 'image.attach_bytes', 'pdf.attach'])('restores %s references without persisting attachment contents', method => {
- const id = method.replaceAll('.', '_')
- receipts.reserve('alice', id, '{}')
- receipts.finish('alice', id, 'confirmed', { result: {
- path: '/files/upload', image_path: '/images/upload', ref_path: 'upload', ref_text: '@file:upload',
- content: 'private bytes', content_base64: 'private bytes', text: 'private bytes',
- } }, { method })
- receipts.close(); receipts = new RealtimeReceipts(home)
- expect(receipts.lookup('alice', id)?.response?.result).toEqual({
- path: '/files/upload', image_path: '/images/upload', ref_path: 'upload', ref_text: '@file:upload',
- })
- })
- it('restores model confirmation metadata only for allowed configuration keys', () => {
- receipts.reserve('alice', 'model', '{}')
- receipts.finish('alice', 'model', 'confirmed', { result: {
- value: 'model --provider provider', deferred: false, confirm_required: true,
- confirm_message: 'Please confirm', warning: 'Cost warning', api_key: 'private key',
- } }, { method: 'config.set', params: { key: 'model' } })
- receipts.close(); receipts = new RealtimeReceipts(home)
- expect(receipts.lookup('alice', 'model')?.response?.result).toEqual({
- value: 'model --provider provider', deferred: false, confirm_required: true,
- confirm_message: 'Please confirm', warning: 'Cost warning',
- })
- receipts.reserve('alice', 'unrelated', '{}')
- receipts.finish('alice', 'unrelated', 'confirmed', { result: { value: 'private key', path: '/private' } },
- { method: 'config.set', params: { key: 'api_key' } })
- expect(receipts.lookup('alice', 'unrelated')?.response?.result).toEqual({})
- })
- it('does not persist unbounded recovery strings', () => {
- receipts.reserve('alice', 'large', '{}')
- receipts.finish('alice', 'large', 'confirmed', { result: { path: 'x'.repeat(4097) } }, { method: 'file.attach' })
- expect(receipts.lookup('alice', 'large')?.response?.result).toEqual({})
- })
|