import test from 'node:test'; import assert from 'node:assert/strict'; import { mkdtemp, rm } from 'node:fs/promises'; import { tmpdir } from 'node:os'; import { join } from 'node:path'; import { JobStore } from '../src/job-store.mjs'; test('清理旧重试任务时不删除新任务的幂等映射', async t => { const directory = await mkdtemp(join(tmpdir(), 'yuban-job-store-test-')); t.after(() => rm(directory, { recursive: true, force: true })); const store = new JobStore(directory, 86_400_000); await store.init(); const common = { ownerKey: 'owner', idempotencyKey: 'same-key', status: 'failed', stage: 'failed', progress: 10, createdAt: '2026-08-05T00:00:00.000Z', updatedAt: '2026-08-05T00:00:00.000Z', }; await store.create({ ...common, id: 'old', expiresAt: '2026-08-05T00:01:00.000Z' }); await store.create({ ...common, id: 'new', status: 'queued', expiresAt: '2026-08-07T00:00:00.000Z', }); await store.cleanupExpired(Date.parse('2026-08-06T00:00:00.000Z')); assert.equal(store.findByIdempotency('same-key')?.id, 'new'); }); test('同一任务的并发状态写入使用独立临时文件', async t => { const directory = await mkdtemp(join(tmpdir(), 'yuban-job-store-race-test-')); t.after(() => rm(directory, { recursive: true, force: true })); const store = new JobStore(directory, 86_400_000); await store.init(); const now = new Date().toISOString(); await store.create({ id: 'race-job', status: 'running', stage: 'transcribing', progress: 50, createdAt: now, updatedAt: now, expiresAt: new Date(Date.now() + 86_400_000).toISOString(), }); await Promise.all([ store.update('race-job', { progress: 60 }), store.update('race-job', { heartbeatAt: now }), ]); assert.equal(store.get('race-job').id, 'race-job'); });