发布 0.3.0 并增加 RSA 签名验证
This commit is contained in:
@@ -0,0 +1,194 @@
|
||||
import { describe, expect, it, vi } from 'vitest'
|
||||
import { useTransactionalFileWorker } from '../src/composables/useTransactionalFileWorker'
|
||||
|
||||
type TestResponse = {
|
||||
type: 'parsed'
|
||||
id: number
|
||||
value: string
|
||||
} | {
|
||||
type: 'calculated'
|
||||
id: number
|
||||
value: string
|
||||
} | {
|
||||
type: 'error'
|
||||
id: number
|
||||
operation: 'parse' | 'calculate'
|
||||
error: string
|
||||
}
|
||||
|
||||
class FakeWorker {
|
||||
onmessage: ((event: MessageEvent<TestResponse>) => void) | null = null
|
||||
onerror: ((event: ErrorEvent) => void) | null = null
|
||||
onmessageerror: ((event: MessageEvent) => void) | null = null
|
||||
messages: Array<Record<string, unknown>> = []
|
||||
terminated = false
|
||||
|
||||
postMessage(message: Record<string, unknown>) {
|
||||
this.messages.push(message)
|
||||
}
|
||||
|
||||
terminate() {
|
||||
this.terminated = true
|
||||
}
|
||||
|
||||
respond(response: TestResponse) {
|
||||
this.onmessage?.({ data: response } as MessageEvent<TestResponse>)
|
||||
}
|
||||
|
||||
fail() {
|
||||
this.onerror?.({ preventDefault: vi.fn() } as unknown as ErrorEvent)
|
||||
}
|
||||
}
|
||||
|
||||
function file(name: string): File {
|
||||
return { name, size: 1 } as File
|
||||
}
|
||||
|
||||
function createHarness() {
|
||||
const workers: FakeWorker[] = []
|
||||
const parsed: string[] = []
|
||||
const calculated: string[] = []
|
||||
const parseErrors: string[] = []
|
||||
const calculationErrors: string[] = []
|
||||
const pendingWorkerErrors: boolean[] = []
|
||||
const activeWorkerError = vi.fn()
|
||||
const manager = useTransactionalFileWorker<Record<string, unknown>, TestResponse>({
|
||||
createWorker: () => {
|
||||
const worker = new FakeWorker()
|
||||
workers.push(worker)
|
||||
return worker as unknown as Worker
|
||||
},
|
||||
onParsed: (response, selected) => parsed.push(`${selected.name}:${response.value}`),
|
||||
onCalculated: (response) => calculated.push(response.value),
|
||||
onParseError: (message) => parseErrors.push(message),
|
||||
onCalculationError: (message) => calculationErrors.push(message),
|
||||
onPendingWorkerError: (hasCurrentFile) => pendingWorkerErrors.push(hasCurrentFile),
|
||||
onActiveWorkerError: activeWorkerError,
|
||||
})
|
||||
return {
|
||||
manager,
|
||||
workers,
|
||||
parsed,
|
||||
calculated,
|
||||
parseErrors,
|
||||
calculationErrors,
|
||||
pendingWorkerErrors,
|
||||
activeWorkerError,
|
||||
}
|
||||
}
|
||||
|
||||
describe('transactional file Worker', () => {
|
||||
it('commits a parsed candidate and calculates with its Worker', () => {
|
||||
const harness = createHarness()
|
||||
expect(harness.manager.parseFile(file('first.hex'))).toBe(true)
|
||||
expect(harness.manager.parsing.value).toBe(true)
|
||||
expect(harness.workers[0].messages[0]).toMatchObject({ type: 'parse', id: 1 })
|
||||
|
||||
harness.workers[0].respond({ type: 'parsed', id: 1, value: 'ready' })
|
||||
expect(harness.manager.parsing.value).toBe(false)
|
||||
expect(harness.parsed).toEqual(['first.hex:ready'])
|
||||
|
||||
expect(harness.manager.calculate({ parameters: 'crc-32' })).toBe('started')
|
||||
expect(harness.manager.calculating.value).toBe(true)
|
||||
expect(harness.workers[0].messages[1]).toEqual({ type: 'calculate', id: 2, parameters: 'crc-32' })
|
||||
harness.workers[0].respond({ type: 'calculated', id: 2, value: 'result' })
|
||||
expect(harness.calculated).toEqual(['result'])
|
||||
expect(harness.manager.calculating.value).toBe(false)
|
||||
})
|
||||
|
||||
it('supports an explicit parse-request payload without changing default callers', () => {
|
||||
const workers: FakeWorker[] = []
|
||||
const manager = useTransactionalFileWorker<Record<string, unknown>, TestResponse>({
|
||||
createWorker: () => {
|
||||
const worker = new FakeWorker()
|
||||
workers.push(worker)
|
||||
return worker as unknown as Worker
|
||||
},
|
||||
createParseRequest: (id, selected) => ({ type: 'parse', id, file: selected, mode: 'intel-hex' }),
|
||||
onParsed: vi.fn(),
|
||||
onCalculated: vi.fn(),
|
||||
onParseError: vi.fn(),
|
||||
onCalculationError: vi.fn(),
|
||||
onPendingWorkerError: vi.fn(),
|
||||
onActiveWorkerError: vi.fn(),
|
||||
})
|
||||
|
||||
const selected = file('firmware.hex')
|
||||
manager.parseFile(selected)
|
||||
expect(workers[0].messages[0]).toEqual({ type: 'parse', id: 1, file: selected, mode: 'intel-hex' })
|
||||
})
|
||||
|
||||
it('preserves the active Worker when a replacement parse fails', () => {
|
||||
const harness = createHarness()
|
||||
harness.manager.parseFile(file('first.hex'))
|
||||
harness.workers[0].respond({ type: 'parsed', id: 1, value: 'first' })
|
||||
|
||||
harness.manager.parseFile(file('invalid.hex'))
|
||||
harness.workers[1].respond({ type: 'error', id: 2, operation: 'parse', error: 'bad checksum' })
|
||||
expect(harness.parseErrors).toEqual(['bad checksum'])
|
||||
expect(harness.workers[0].terminated).toBe(false)
|
||||
expect(harness.workers[1].terminated).toBe(true)
|
||||
expect(harness.manager.calculate({})).toBe('started')
|
||||
})
|
||||
|
||||
it('atomically replaces the active Worker and ignores its stale result', () => {
|
||||
const harness = createHarness()
|
||||
harness.manager.parseFile(file('first.hex'))
|
||||
harness.workers[0].respond({ type: 'parsed', id: 1, value: 'first' })
|
||||
harness.manager.calculate({})
|
||||
|
||||
harness.manager.parseFile(file('second.hex'))
|
||||
harness.workers[1].respond({ type: 'parsed', id: 3, value: 'second' })
|
||||
expect(harness.workers[0].terminated).toBe(true)
|
||||
expect(harness.parsed).toEqual(['first.hex:first', 'second.hex:second'])
|
||||
expect(harness.manager.calculating.value).toBe(false)
|
||||
|
||||
harness.workers[0].respond({ type: 'calculated', id: 2, value: 'stale' })
|
||||
expect(harness.calculated).toEqual([])
|
||||
})
|
||||
|
||||
it('distinguishes pending Worker failures with and without a current file', () => {
|
||||
const harness = createHarness()
|
||||
harness.manager.parseFile(file('first.hex'))
|
||||
harness.workers[0].fail()
|
||||
expect(harness.pendingWorkerErrors).toEqual([false])
|
||||
|
||||
harness.manager.parseFile(file('current.hex'))
|
||||
harness.workers[1].respond({ type: 'parsed', id: 2, value: 'current' })
|
||||
harness.manager.parseFile(file('replacement.hex'))
|
||||
harness.workers[2].fail()
|
||||
expect(harness.pendingWorkerErrors).toEqual([false, true])
|
||||
expect(harness.workers[1].terminated).toBe(false)
|
||||
})
|
||||
|
||||
it('terminates active and pending Workers and ignores late responses', () => {
|
||||
const harness = createHarness()
|
||||
harness.manager.parseFile(file('first.hex'))
|
||||
harness.workers[0].respond({ type: 'parsed', id: 1, value: 'first' })
|
||||
harness.manager.parseFile(file('second.hex'))
|
||||
harness.manager.destroyWorkers()
|
||||
|
||||
expect(harness.workers.every((worker) => worker.terminated)).toBe(true)
|
||||
harness.workers[1].respond({ type: 'parsed', id: 2, value: 'late' })
|
||||
expect(harness.parsed).toEqual(['first.hex:first'])
|
||||
expect(harness.manager.parsing.value).toBe(false)
|
||||
expect(harness.manager.calculating.value).toBe(false)
|
||||
})
|
||||
|
||||
it('reports Worker construction failure without entering a busy state', () => {
|
||||
const pendingWorkerErrors: boolean[] = []
|
||||
const manager = useTransactionalFileWorker<Record<string, unknown>, TestResponse>({
|
||||
createWorker: () => { throw new Error('unsupported') },
|
||||
onParsed: vi.fn(),
|
||||
onCalculated: vi.fn(),
|
||||
onParseError: vi.fn(),
|
||||
onCalculationError: vi.fn(),
|
||||
onPendingWorkerError: (hasCurrentFile) => pendingWorkerErrors.push(hasCurrentFile),
|
||||
onActiveWorkerError: vi.fn(),
|
||||
})
|
||||
|
||||
expect(manager.parseFile(file('first.hex'))).toBe(false)
|
||||
expect(pendingWorkerErrors).toEqual([false])
|
||||
expect(manager.parsing.value).toBe(false)
|
||||
})
|
||||
})
|
||||
Reference in New Issue
Block a user