97 lines
3.5 KiB
TypeScript
97 lines
3.5 KiB
TypeScript
const mockClearHistory = jest.fn()
|
|
const mockClearEntitlements = jest.fn()
|
|
const mockClearPreferences = jest.fn()
|
|
const mockCancelRecorder = jest.fn()
|
|
const mockClearQueuedAudio = jest.fn()
|
|
const mockClearActions = jest.fn()
|
|
const mockClearGenerationKeys = jest.fn()
|
|
const mockDeletePush = jest.fn()
|
|
|
|
jest.mock('../src/features/history/history-cache', () => ({
|
|
clearAllHistoryCaches: () => mockClearHistory(),
|
|
}))
|
|
jest.mock('../src/lib/entitlement-context', () => ({
|
|
clearAllEntitlementCaches: () => mockClearEntitlements(),
|
|
}))
|
|
jest.mock('../src/lib/preferences-context', () => ({
|
|
clearAllUserPreferenceCaches: () => mockClearPreferences(),
|
|
}))
|
|
jest.mock('../src/lib/audio-recorder', () => ({
|
|
audioRecorder: { cancel: () => mockCancelRecorder() },
|
|
}))
|
|
jest.mock('../src/features/recording/durable-processing-queue', () => ({
|
|
clearAllQueuedAudio: () => mockClearQueuedAudio(),
|
|
}))
|
|
jest.mock('../src/features/actions/action-service', () => ({
|
|
clearAllActionHistories: () => mockClearActions(),
|
|
}))
|
|
jest.mock('../src/features/templates', () => ({
|
|
clearEveryGenerationIdempotencyKey: () => mockClearGenerationKeys(),
|
|
}))
|
|
jest.mock('../src/features/notifications/notification-native', () => ({
|
|
deleteNativePushRegistration: () => mockDeletePush(),
|
|
}))
|
|
|
|
import {
|
|
AccountLocalDataPurgeError,
|
|
purgeAllAccountLocalData,
|
|
} from '../src/lib/account-local-data'
|
|
|
|
const operations = [
|
|
mockClearHistory,
|
|
mockClearEntitlements,
|
|
mockClearPreferences,
|
|
mockCancelRecorder,
|
|
mockClearQueuedAudio,
|
|
mockClearActions,
|
|
mockClearGenerationKeys,
|
|
mockDeletePush,
|
|
]
|
|
|
|
describe('account local privacy purge', () => {
|
|
beforeEach(() => {
|
|
for (const operation of operations) operation.mockReset().mockResolvedValue(undefined)
|
|
})
|
|
|
|
it('awaits every account-scoped cleanup operation', async () => {
|
|
await expect(purgeAllAccountLocalData()).resolves.toBeUndefined()
|
|
for (const operation of operations) expect(operation).toHaveBeenCalledTimes(1)
|
|
})
|
|
|
|
it('finishes the remaining cleanup work and returns a stable failure', async () => {
|
|
let finishQueueCleanup: (() => void) | null = null
|
|
mockClearHistory.mockRejectedValue(new Error('private filesystem detail'))
|
|
mockClearQueuedAudio.mockReturnValue(new Promise<void>((resolve) => {
|
|
finishQueueCleanup = resolve
|
|
}))
|
|
|
|
const purge = purgeAllAccountLocalData()
|
|
let settled = false
|
|
void purge.finally(() => { settled = true }).catch(() => undefined)
|
|
await Promise.resolve()
|
|
expect(settled).toBe(false)
|
|
finishQueueCleanup?.()
|
|
|
|
await expect(purge).rejects.toBeInstanceOf(AccountLocalDataPurgeError)
|
|
await expect(purge).rejects.not.toThrow('private filesystem detail')
|
|
for (const operation of operations) expect(operation).toHaveBeenCalledTimes(1)
|
|
})
|
|
|
|
it('single-flights concurrent privacy boundaries and allows a later retry', async () => {
|
|
let finishQueueCleanup: (() => void) | null = null
|
|
mockClearQueuedAudio.mockReturnValueOnce(new Promise<void>((resolve) => {
|
|
finishQueueCleanup = resolve
|
|
}))
|
|
|
|
const first = purgeAllAccountLocalData()
|
|
const concurrent = purgeAllAccountLocalData()
|
|
expect(concurrent).toBe(first)
|
|
for (const operation of operations) expect(operation).toHaveBeenCalledTimes(1)
|
|
|
|
finishQueueCleanup?.()
|
|
await expect(Promise.all([first, concurrent])).resolves.toEqual([undefined, undefined])
|
|
|
|
await expect(purgeAllAccountLocalData()).resolves.toBeUndefined()
|
|
for (const operation of operations) expect(operation).toHaveBeenCalledTimes(2)
|
|
})
|
|
})
|