Files
wehub-resource-sync d18ada4ee7
Docker Publish / docker (web, apps/web/Dockerfile, web) (push) Failing after 0s
Docker Publish / docker (api, apps/api/Dockerfile, api) (push) Failing after 1s
Publish Extension / detect-version (push) Has been skipped
Publish Extension / submit (push) Has been cancelled
chore: import upstream snapshot with attribution
2026-07-13 12:24:41 +08:00

158 lines
4.4 KiB
TypeScript

/**
* Three-host equivalence: CLI `--vidbee-local` slot.
*
* The kernel-level test in `packages/task-queue/__integration__/three-host-equivalence.test.ts`
* stands in for the CLI by spinning up a third TaskQueueAPI in-process.
* This file proves that `createLocalClient` (the production code path the
* CLI uses for `--vidbee-local`) wraps that same kernel without changing
* the resulting Task / projection state.
*
* Together they fulfil the design doc's three-host-equivalence acceptance
* for the CLI side (§10).
*/
import { describe, expect, it } from 'vitest'
import { TaskQueueAPI } from '@vidbee/task-queue'
import type {
Executor,
ExecutorEvents,
ExecutorRun,
Task
} from '@vidbee/task-queue'
import { MemoryPersistAdapter, projectTaskToLegacy } from '@vidbee/task-queue'
import { createLocalClient } from '../src/transport/local-client'
interface ScriptedExecutor extends Executor {}
const SUCCESS_SCRIPT = (filePath: string) => (events: ExecutorEvents, ctx: { taskId: string; attemptId: string }) => {
events.onSpawn({
taskId: ctx.taskId,
attemptId: ctx.attemptId,
pid: 4242,
pidStartedAt: 1000,
kind: 'yt-dlp',
spawnedAt: 1
})
events.onProgress({
taskId: ctx.taskId,
attemptId: ctx.attemptId,
progress: {
percent: 0.5,
bytesDownloaded: 50,
bytesTotal: 100,
speedBps: 100,
etaMs: 1000,
ticks: 1
},
enteredProcessing: false
})
events.onProgress({
taskId: ctx.taskId,
attemptId: ctx.attemptId,
progress: {
percent: 0.95,
bytesDownloaded: 95,
bytesTotal: 100,
speedBps: 100,
etaMs: 100,
ticks: 2
},
enteredProcessing: true
})
events.onFinish({
taskId: ctx.taskId,
attemptId: ctx.attemptId,
result: {
type: 'success',
output: { filePath, size: 1024, durationMs: null, sha256: null }
},
closedAt: 2,
stdoutTail: '',
stderrTail: ''
})
}
const makeScripted = (filePath: string): ScriptedExecutor => ({
run(ctx, events) {
queueMicrotask(() => SUCCESS_SCRIPT(filePath)(events, ctx))
return { cancel: async () => {}, pause: async () => {} } as ExecutorRun
}
})
const stripVolatile = (task: Readonly<Task>): unknown => {
const c = JSON.parse(JSON.stringify(task)) as Record<string, unknown>
delete c.id
delete c.createdAt
delete c.updatedAt
delete c.enteredStatusAt
delete c.pid
delete c.pidStartedAt
delete c.nextRetryAt
return c
}
const stripVolatileProjection = (task: Readonly<Task>): unknown => {
const p = projectTaskToLegacy(task) as unknown as Record<string, unknown>
delete p.id
delete p.createdAt
delete p.startedAt
delete p.completedAt
delete p.nextRetryAt
return p
}
const wait = async (predicate: () => boolean, timeoutMs = 2_000): Promise<void> => {
const deadline = Date.now() + timeoutMs
while (Date.now() < deadline) {
if (predicate()) return
await new Promise((r) => setTimeout(r, 5))
}
throw new Error('wait: predicate did not hold within timeout')
}
describe('createLocalClient — three-host equivalence (CLI slot)', () => {
it('produces the same Task/projection as a bare TaskQueueAPI', async () => {
const filePath = '/tmp/sample.mp4'
// Reference host: a TaskQueueAPI configured exactly like the kernel's
// own three-host test does it.
const refExec = makeScripted(filePath)
const ref = new TaskQueueAPI({
persist: new MemoryPersistAdapter(),
executor: refExec,
maxConcurrency: 4,
rng: () => 0.5,
filePresent: () => true
})
await ref.start()
// CLI host: production createLocalClient with the same fake executor.
const cli = await createLocalClient({
persist: 'memory',
executor: makeScripted(filePath),
filePresent: () => true,
rng: () => 0.5,
maxConcurrency: 4
})
try {
const input = { url: 'https://example.com/v', kind: 'video' as const, title: 'sample' }
const refAdd = await ref.add({ input })
const cliAdd = await cli.add!({ input })
await wait(() => ref.get(refAdd.id)?.status === 'completed')
await wait(() => cli.api.get(cliAdd.id)?.status === 'completed')
const refTask = ref.get(refAdd.id)!
const cliTask = cli.api.get(cliAdd.id)!
expect(stripVolatile(cliTask)).toStrictEqual(stripVolatile(refTask))
expect(stripVolatileProjection(cliTask)).toStrictEqual(stripVolatileProjection(refTask))
} finally {
await ref.stop()
await cli.shutdown()
}
})
})