a9753fde6b
fix(app-state) - missing key Blocked state, history sync status & 120s timeout
113 lines
3.7 KiB
TypeScript
113 lines
3.7 KiB
TypeScript
import { Boom } from '@hapi/boom'
|
|
import { proto } from '../../../WAProto/index.js'
|
|
import {
|
|
decodeSyncdMutations,
|
|
decodeSyncdPatch,
|
|
decodeSyncdSnapshot,
|
|
isAppStateSyncIrrecoverable,
|
|
isMissingKeyError,
|
|
MAX_SYNC_ATTEMPTS,
|
|
newLTHashState
|
|
} from '../../Utils/chat-utils'
|
|
|
|
const missingKeyFn = async () => null
|
|
|
|
describe('App State Sync', () => {
|
|
describe('missing key errors are marked with isMissingKey (Blocked in WA Web)', () => {
|
|
it('decodeSyncdPatch throws with isMissingKey on missing key', async () => {
|
|
const msg: proto.ISyncdPatch = {
|
|
keyId: { id: Buffer.from('missing-key') },
|
|
mutations: [],
|
|
version: { version: 1 as any },
|
|
snapshotMac: Buffer.alloc(32),
|
|
patchMac: Buffer.alloc(32)
|
|
}
|
|
|
|
try {
|
|
await decodeSyncdPatch(msg, 'regular_low', newLTHashState(), missingKeyFn, () => {}, true)
|
|
fail('should have thrown')
|
|
} catch (error: any) {
|
|
expect(isMissingKeyError(error)).toBe(true)
|
|
}
|
|
})
|
|
|
|
it('decodeSyncdSnapshot throws with isMissingKey on missing snapshot key', async () => {
|
|
const snapshot: proto.ISyncdSnapshot = {
|
|
version: { version: 1 as any },
|
|
records: [],
|
|
keyId: { id: Buffer.from('missing-key') },
|
|
mac: Buffer.alloc(32)
|
|
}
|
|
|
|
try {
|
|
await decodeSyncdSnapshot('regular_low', snapshot, missingKeyFn, undefined, true)
|
|
fail('should have thrown')
|
|
} catch (error: any) {
|
|
expect(isMissingKeyError(error)).toBe(true)
|
|
}
|
|
})
|
|
|
|
it('decodeSyncdMutations throws with isMissingKey on missing mutation key', async () => {
|
|
const records: proto.ISyncdRecord[] = [
|
|
{
|
|
keyId: { id: Buffer.from('missing-key') },
|
|
value: { blob: Buffer.alloc(64) },
|
|
index: { blob: Buffer.alloc(32) }
|
|
}
|
|
]
|
|
|
|
try {
|
|
await decodeSyncdMutations(records, newLTHashState(), missingKeyFn, () => {}, true)
|
|
fail('should have thrown')
|
|
} catch (error: any) {
|
|
expect(isMissingKeyError(error)).toBe(true)
|
|
}
|
|
})
|
|
|
|
it('missing key errors are NOT irrecoverable on first attempt', async () => {
|
|
const error = new Boom('missing key', { data: { isMissingKey: true } })
|
|
expect(isMissingKeyError(error)).toBe(true)
|
|
expect(isAppStateSyncIrrecoverable(error, 1)).toBe(false)
|
|
})
|
|
})
|
|
|
|
describe('isAppStateSyncIrrecoverable', () => {
|
|
it('should NOT be irrecoverable for status 400 (dead code path removed)', () => {
|
|
expect(isAppStateSyncIrrecoverable(new Boom('test', { statusCode: 400 }), 1)).toBe(false)
|
|
})
|
|
|
|
it('should NOT be irrecoverable for status 404 (missing key is Blocked, not Fatal)', () => {
|
|
expect(isAppStateSyncIrrecoverable(new Boom('test', { statusCode: 404 }), 1)).toBe(false)
|
|
})
|
|
|
|
it('should NOT be irrecoverable for status 405', () => {
|
|
expect(isAppStateSyncIrrecoverable(new Boom('test', { statusCode: 405 }), 1)).toBe(false)
|
|
})
|
|
|
|
it('should NOT be irrecoverable for status 406', () => {
|
|
expect(isAppStateSyncIrrecoverable(new Boom('test', { statusCode: 406 }), 1)).toBe(false)
|
|
})
|
|
|
|
it('should be irrecoverable for TypeError', () => {
|
|
expect(isAppStateSyncIrrecoverable(new TypeError('WASM crash'), 1)).toBe(true)
|
|
})
|
|
|
|
it('should be irrecoverable when attempts >= MAX_SYNC_ATTEMPTS', () => {
|
|
expect(isAppStateSyncIrrecoverable(new Error('generic'), MAX_SYNC_ATTEMPTS)).toBe(true)
|
|
})
|
|
|
|
it('should NOT be irrecoverable for generic error below max attempts', () => {
|
|
expect(isAppStateSyncIrrecoverable(new Error('generic'), 1)).toBe(false)
|
|
})
|
|
|
|
it('should NOT be irrecoverable for non-fatal status codes', () => {
|
|
expect(isAppStateSyncIrrecoverable(new Boom('server error', { statusCode: 500 }), 1)).toBe(false)
|
|
})
|
|
|
|
it('should handle null/undefined error gracefully', () => {
|
|
expect(isAppStateSyncIrrecoverable(null, MAX_SYNC_ATTEMPTS)).toBe(true)
|
|
expect(isAppStateSyncIrrecoverable(undefined, 1)).toBe(false)
|
|
})
|
|
})
|
|
})
|