2026-07-26 23:47:30 +08:00
|
|
|
import assert from 'node:assert/strict'
|
|
|
|
|
import test from 'node:test'
|
|
|
|
|
|
|
|
|
|
import {
|
|
|
|
|
clearStoredQueue,
|
2026-07-28 20:29:06 +08:00
|
|
|
getOrCreateQueueEncryptionKey,
|
2026-07-26 23:47:30 +08:00
|
|
|
isBugCollectDisabledPayload,
|
|
|
|
|
openQueue,
|
2026-07-28 20:29:06 +08:00
|
|
|
queueEncryptionKeyStorageKey,
|
2026-07-26 23:47:30 +08:00
|
|
|
sealQueue,
|
|
|
|
|
} from '../src/queue/queueStorage'
|
|
|
|
|
import type { IssueEvent } from '../src/types'
|
|
|
|
|
|
|
|
|
|
const issue: IssueEvent = {
|
|
|
|
|
type: 'issue',
|
|
|
|
|
level: 'error',
|
|
|
|
|
platform: 'android',
|
|
|
|
|
fingerprint: 'fingerprint',
|
|
|
|
|
timestamp: 1_000,
|
|
|
|
|
appKey: 'app',
|
|
|
|
|
release: '8.0.0',
|
|
|
|
|
exception: { type: 'Error', value: 'secret', stacktrace: 'private stack' },
|
|
|
|
|
symbolicationStatus: 'unavailable',
|
|
|
|
|
}
|
|
|
|
|
|
2026-07-28 20:29:06 +08:00
|
|
|
function createMemoryStorage() {
|
|
|
|
|
const values = new Map<string, string>()
|
|
|
|
|
return {
|
|
|
|
|
values,
|
|
|
|
|
async getItem(key: string) {
|
|
|
|
|
return values.get(key) ?? null
|
|
|
|
|
},
|
|
|
|
|
async setItem(key: string, value: string) {
|
|
|
|
|
values.set(key, value)
|
|
|
|
|
},
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
test('persistent queue uses a stable device-generated key and never stores diagnostics as plaintext', async () => {
|
|
|
|
|
const storage = createMemoryStorage()
|
|
|
|
|
const key = await getOrCreateQueueEncryptionKey(storage, 'app')
|
|
|
|
|
const sameKey = await getOrCreateQueueEncryptionKey(storage, 'app')
|
|
|
|
|
assert.ok(key)
|
|
|
|
|
assert.equal(sameKey, key)
|
|
|
|
|
assert.equal(storage.values.get(queueEncryptionKeyStorageKey('app')), key)
|
|
|
|
|
assert.equal(key.includes('signing-key'), false)
|
|
|
|
|
|
|
|
|
|
const encrypted = sealQueue([issue], key, 'bugcollect-queue|app')
|
2026-07-26 23:47:30 +08:00
|
|
|
assert.equal(encrypted.includes('private stack'), false)
|
2026-07-28 20:29:06 +08:00
|
|
|
assert.deepEqual(openQueue(encrypted, key, 'bugcollect-queue|app', 0), [issue])
|
|
|
|
|
assert.throws(() => openQueue(encrypted, key, 'bugcollect-queue|other', 0))
|
2026-07-26 23:47:30 +08:00
|
|
|
const parts = encrypted.split('.')
|
|
|
|
|
parts[2] = `${parts[2][0] === 'A' ? 'B' : 'A'}${parts[2].slice(1)}`
|
2026-07-28 20:29:06 +08:00
|
|
|
assert.throws(() => openQueue(parts.join('.'), key, 'bugcollect-queue|app', 0))
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
test('different applications never share the same local queue key', async () => {
|
|
|
|
|
const storage = createMemoryStorage()
|
|
|
|
|
const [first, second] = await Promise.all([
|
|
|
|
|
getOrCreateQueueEncryptionKey(storage, 'first'),
|
|
|
|
|
getOrCreateQueueEncryptionKey(storage, 'second'),
|
|
|
|
|
])
|
|
|
|
|
assert.ok(first)
|
|
|
|
|
assert.ok(second)
|
|
|
|
|
assert.notEqual(first, second)
|
2026-07-26 23:47:30 +08:00
|
|
|
})
|
|
|
|
|
|
|
|
|
|
test('platform disabled response recognizes the deployed message contract', () => {
|
|
|
|
|
assert.equal(
|
|
|
|
|
isBugCollectDisabledPayload({
|
|
|
|
|
code: 403,
|
|
|
|
|
status: '1',
|
|
|
|
|
message: 'BUGCOLLECT_DISABLED',
|
|
|
|
|
}),
|
|
|
|
|
true,
|
|
|
|
|
)
|
|
|
|
|
assert.equal(isBugCollectDisabledPayload({ code: 'BUGCOLLECT_DISABLED' }), true)
|
|
|
|
|
assert.equal(isBugCollectDisabledPayload({ code: 403, message: 'forbidden' }), false)
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
test('cold-start disable removes a persisted queue before LogQueue is created', async () => {
|
|
|
|
|
const removed: string[] = []
|
|
|
|
|
await clearStoredQueue(
|
|
|
|
|
{
|
|
|
|
|
async removeItem(key) {
|
|
|
|
|
removed.push(key)
|
|
|
|
|
},
|
|
|
|
|
},
|
|
|
|
|
'app',
|
|
|
|
|
)
|
|
|
|
|
assert.deepEqual(removed, ['@xuqm_bugcollect:queue:app'])
|
|
|
|
|
})
|