import AsyncStorage from '@react-native-async-storage/async-storage' import { FileSystem } from 'react-native-file-access' import type { AudioPipelineResult, LocalAudioInput, } from '../src/features/import/audio-import-types' const mockProcessAudioInput = jest.fn, [LocalAudioInput, unknown]>() jest.mock('../src/features/import/audio-transcription-service', () => ({ processAudioInput: (input: LocalAudioInput, options: unknown) => mockProcessAudioInput(input, options), })) jest.mock('../src/features/meetings/meetings-service', () => ({ failMeetingRecording: jest.fn(async () => undefined), markMeetingProcessingFailure: jest.fn(async () => undefined), queueMeetingRecording: jest.fn(async () => undefined), })) import { clearQueuedAudioForUser, durableQueueTestContract, resumeQueuedAudioForUser, retainQueuedAudioOnlyForUser, type DurableQueueItem, } from '../src/features/recording/durable-processing-queue' const START_MS = 1_700_000_000_000 const USER_A = '11111111-1111-4111-8111-111111111111' const USER_B = '22222222-2222-4222-8222-222222222222' const mockFileSystem = FileSystem as typeof FileSystem & { filesystem: Map } function queued( userId: string, suffix: string, overrides: Partial = {}, ): DurableQueueItem { const path = `${durableQueueTestContract.queueDirectory}/queued-${suffix}.wav` return { schemaVersion: 1, id: suffix, userId, meetingId: null, path, uri: `file://${path}`, fileName: `${suffix}.wav`, mimeType: 'audio/wav', sizeBytes: 5, durationMs: 100, source: 'recording', languageCode: 'ko', status: 'retry', phase: null, attempts: 1, uploadedBytes: 0, nextAttemptAtMs: START_MS + 5_000, lastErrorCode: 'upload', lastErrorMessage: null, createdAtMs: 1, updatedAtMs: 1, ...overrides, } } function result(): AudioPipelineResult { return { historyId: null, audioFileId: 'audio-file', meetingId: null, processingJobId: null, transcript: 'ok', provider: 'test', language: 'ko', durationSeconds: 1, deduplicated: false, } } async function seed(items: DurableQueueItem[]): Promise { for (const item of items) mockFileSystem.filesystem.set(item.path, 'audio') await AsyncStorage.setItem(durableQueueTestContract.storageKey, JSON.stringify(items)) } async function stored(): Promise { return durableQueueTestContract.parseQueue( await AsyncStorage.getItem(durableQueueTestContract.storageKey), ) } async function settle(): Promise { for (let index = 0; index < 5; index += 1) await jest.advanceTimersByTimeAsync(0) } describe('durable queue keeps the signed-out owner recordings (redteam r3-16 #2)', () => { beforeEach(async () => { jest.useFakeTimers({ now: START_MS }) await AsyncStorage.clear() mockFileSystem.filesystem.clear() mockProcessAudioInput.mockReset().mockResolvedValue(result()) }) afterEach(async () => { for (const userId of [USER_A, USER_B]) { await clearQueuedAudioForUser(userId).catch(() => undefined) } await settle() jest.useRealTimers() }) test('keeps the owner items and files, discards other accounts', async () => { const ownerRetry = queued(USER_A, 'aaa001') const ownerPending = queued(USER_A, 'aaa002', { status: 'pending', attempts: 0, nextAttemptAtMs: START_MS }) const other = queued(USER_B, 'bbb001') await seed([ownerRetry, ownerPending, other]) await retainQueuedAudioOnlyForUser(USER_A) expect((await stored()).map(item => item.id).sort()).toEqual(['aaa001', 'aaa002']) expect(mockFileSystem.filesystem.has(ownerRetry.path)).toBe(true) expect(mockFileSystem.filesystem.has(ownerPending.path)).toBe(true) expect(mockFileSystem.filesystem.has(other.path)).toBe(false) }) test('does not spend retries while the owner is signed out, resumes on sign-in', async () => { const item = queued(USER_A, 'aaa003', { nextAttemptAtMs: START_MS }) await seed([item]) await retainQueuedAudioOnlyForUser(USER_A) await jest.advanceTimersByTimeAsync(60 * 60_000) await settle() expect(mockProcessAudioInput).not.toHaveBeenCalled() expect((await stored())[0]?.attempts).toBe(1) await resumeQueuedAudioForUser(USER_A) await settle() expect(mockProcessAudioInput).toHaveBeenCalledTimes(1) expect(await stored()).toEqual([]) }) })