import { describe, expect, test, beforeEach } from 'bun:test' import type { RequestPermissionRequest } from '@agentclientprotocol/sdk' import { permissionQueue, currentPermission, requestPermissionFromUser, respondToPermission, rejectCurrentPermission } from '@/app/ai/acp/permission' function makeRequest( options: { optionId: string; kind: string; name: string }[] = [ { optionId: 'allow', kind: 'allow_once', name: 'Allow once' }, { optionId: 'reject', kind: 'reject_once', name: 'Reject' } ] ): RequestPermissionRequest { return { sessionId: 'session-1', options: options.map((o) => ({ optionId: o.optionId, kind: o.kind as 'allow_once' | 'allow_always' | 'reject_once' | 'reject_always', name: o.name })), toolCall: { sessionUpdate: 'tool_call_update', toolCallId: 'tc-1', status: 'pending' as const } } } describe('acp-permission', () => { beforeEach(() => { permissionQueue.value = [] }) test('requestPermissionFromUser queues an entry', () => { void requestPermissionFromUser(makeRequest()) expect(permissionQueue.value).toHaveLength(1) expect(currentPermission.value).not.toBeNull() expect(currentPermission.value?.request.sessionId).toBe('session-1') respondToPermission('allow') }) test('respondToPermission resolves with selected optionId', async () => { const promise = requestPermissionFromUser(makeRequest()) respondToPermission('allow') const result = await promise expect(result.outcome.outcome).toBe('selected') expect(result.outcome.optionId).toBe('allow') expect(permissionQueue.value).toHaveLength(0) }) test('rejectCurrentPermission picks reject option', async () => { const promise = requestPermissionFromUser(makeRequest()) rejectCurrentPermission() const result = await promise expect(result.outcome.optionId).toBe('reject') }) test('rejectCurrentPermission falls back to first option when no reject kind', async () => { const req = makeRequest([{ optionId: 'only-allow', kind: 'allow_once', name: 'Allow' }]) const promise = requestPermissionFromUser(req) rejectCurrentPermission() const result = await promise expect(result.outcome.optionId).toBe('only-allow') }) test('queue handles multiple concurrent requests in order', async () => { const p1 = requestPermissionFromUser( makeRequest([{ optionId: 'a1', kind: 'allow_once', name: 'First' }]) ) const p2 = requestPermissionFromUser( makeRequest([{ optionId: 'a2', kind: 'allow_once', name: 'Second' }]) ) expect(permissionQueue.value).toHaveLength(2) expect(currentPermission.value?.request.options[0].optionId).toBe('a1') respondToPermission('a1') const r1 = await p1 expect(r1.outcome.optionId).toBe('a1') expect(currentPermission.value?.request.options[0].optionId).toBe('a2') respondToPermission('a2') const r2 = await p2 expect(r2.outcome.optionId).toBe('a2') expect(permissionQueue.value).toHaveLength(0) }) test('respondToPermission is no-op when queue is empty', () => { expect(permissionQueue.value).toHaveLength(0) respondToPermission('anything') expect(permissionQueue.value).toHaveLength(0) }) test('rejectCurrentPermission is no-op when queue is empty', () => { expect(permissionQueue.value).toHaveLength(0) rejectCurrentPermission() expect(permissionQueue.value).toHaveLength(0) }) test('timer cleared on manual resolution (no double resolve)', async () => { const promise = requestPermissionFromUser(makeRequest()) respondToPermission('allow') const result = await promise expect(result.outcome.optionId).toBe('allow') // Timer should be cleared — wait to ensure no stale timeout fires await new Promise((resolve) => { setTimeout(resolve, 50) }) expect(permissionQueue.value).toHaveLength(0) }) test('timeout auto-rejects after delay', async () => { const req = makeRequest() // Override timeout for test speed by creating request and manually triggering the timer const promise = requestPermissionFromUser(req) const entry = permissionQueue.value[0] expect(entry).toBeDefined() // Fast-forward: manually fire the timer callback clearTimeout(entry.timer) const timerFn = () => { const idx = permissionQueue.value.indexOf(entry) if (idx === -1) return permissionQueue.value = permissionQueue.value.filter((e) => e !== entry) entry.resolve({ outcome: { outcome: 'selected', optionId: req.options.find((o) => o.kind.startsWith('reject'))?.optionId ?? req.options[0]?.optionId ?? '' } }) } timerFn() const result = await promise expect(result.outcome.optionId).toBe('reject') expect(permissionQueue.value).toHaveLength(0) }) })