发布 0.3.0 并增加 RSA 签名验证

This commit is contained in:
mid
2026-08-13 13:32:56 +08:00
parent 32b7cc827c
commit 0ae6db36cb
27 changed files with 2025 additions and 274 deletions
+194
View File
@@ -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)
})
})