Use deliverablesRef + limited poll for post-task server docs; pass reconnectRunId across auto-retry; GetRun reads idempotency_key; StartClaimedRun fails closed on UpdateRun; MessageBubble/E2E scope this-turn assistant; correct audit retry/schema19 wording. Co-authored-by: Cursor <cursoragent@cursor.com>
156 lines
4.3 KiB
TypeScript
156 lines
4.3 KiB
TypeScript
import test from 'node:test';
|
|
import assert from 'node:assert/strict';
|
|
import {
|
|
resolvePostTaskActiveDocId,
|
|
shouldSwitchTemplateSlotToServer,
|
|
pollUntilPostTaskServerDoc,
|
|
} from './postTaskDeliverableSync';
|
|
import type { DeliverableItem } from '../types/workbench';
|
|
import type { WorkflowDocumentSummary } from '../api/projectDocuments';
|
|
|
|
const base = (over: Partial<DeliverableItem>): DeliverableItem => ({
|
|
docId: 'doc-x',
|
|
projectId: 6089,
|
|
taskKey: 'intake',
|
|
title: '用户初始需求收集表',
|
|
stage: 'V0.1',
|
|
category: 'workflow',
|
|
creatorId: 1,
|
|
creatorName: 'A',
|
|
isFixedWorkflow: true,
|
|
isTemplateOnly: true,
|
|
lifecycleState: 'drafting',
|
|
isClaimed: false,
|
|
author: '',
|
|
reviewer: '',
|
|
approver: '',
|
|
updatedAt: '',
|
|
governanceMode: 'controlled',
|
|
...over,
|
|
});
|
|
|
|
test('resolvePostTaskActiveDocId prefers server doc_proj for taskKey', () => {
|
|
const docs = [
|
|
base({ docId: 'doc-p6089-intake', isTemplateOnly: true }),
|
|
base({
|
|
docId: 'doc_proj6089_intake',
|
|
isTemplateOnly: false,
|
|
title: '用户初始需求收集表',
|
|
}),
|
|
];
|
|
assert.equal(
|
|
resolvePostTaskActiveDocId({ deliverables: docs, projectId: 6089, taskKey: 'intake' }),
|
|
'doc_proj6089_intake',
|
|
);
|
|
});
|
|
|
|
test('resolvePostTaskActiveDocId honors preferred server doc id', () => {
|
|
const docs = [
|
|
base({ docId: 'doc_proj6089_intake', isTemplateOnly: false }),
|
|
base({
|
|
docId: 'doc_proj6089_user_requirements',
|
|
taskKey: 'user_requirements',
|
|
isTemplateOnly: false,
|
|
}),
|
|
];
|
|
assert.equal(
|
|
resolvePostTaskActiveDocId({
|
|
deliverables: docs,
|
|
projectId: 6089,
|
|
taskKey: 'intake',
|
|
preferredDocId: 'doc_proj6089_intake',
|
|
}),
|
|
'doc_proj6089_intake',
|
|
);
|
|
});
|
|
|
|
test('shouldSwitchTemplateSlotToServer when active is local template slot', () => {
|
|
const docs = [
|
|
base({ docId: 'doc-p6089-intake', isTemplateOnly: true }),
|
|
base({ docId: 'doc_proj6089_intake', isTemplateOnly: false }),
|
|
];
|
|
assert.equal(
|
|
shouldSwitchTemplateSlotToServer({
|
|
activeDocId: 'doc-p6089-intake',
|
|
deliverables: docs,
|
|
projectId: 6089,
|
|
taskKey: 'intake',
|
|
serverDocId: 'doc_proj6089_intake',
|
|
}),
|
|
true,
|
|
);
|
|
assert.equal(
|
|
shouldSwitchTemplateSlotToServer({
|
|
activeDocId: 'doc_proj6089_intake',
|
|
deliverables: docs,
|
|
projectId: 6089,
|
|
taskKey: 'intake',
|
|
serverDocId: 'doc_proj6089_intake',
|
|
}),
|
|
false,
|
|
);
|
|
});
|
|
|
|
test('pollUntilPostTaskServerDoc retries until same taskKey server doc appears', async () => {
|
|
const local = [base({ docId: 'doc-p6089-intake', isTemplateOnly: true })];
|
|
let calls = 0;
|
|
const snaps: DeliverableItem[][] = [];
|
|
const result = await pollUntilPostTaskServerDoc({
|
|
listDocs: async () => {
|
|
calls += 1;
|
|
if (calls < 3) return [];
|
|
return [
|
|
{
|
|
doc_id: 'doc_proj6089_intake',
|
|
project_id: 6089,
|
|
task_key: 'intake',
|
|
title: '用户初始需求收集表',
|
|
lifecycle_state: 'drafting',
|
|
is_template_only: false,
|
|
} satisfies WorkflowDocumentSummary,
|
|
];
|
|
},
|
|
getDeliverables: () => local,
|
|
merge: (prev, docs) => {
|
|
if (docs.length === 0) return prev;
|
|
return [
|
|
...prev.filter((d) => d.docId !== 'doc_proj6089_intake'),
|
|
base({
|
|
docId: docs[0].doc_id,
|
|
isTemplateOnly: false,
|
|
taskKey: docs[0].task_key,
|
|
}),
|
|
];
|
|
},
|
|
projectId: 6089,
|
|
taskKey: 'intake',
|
|
maxAttempts: 5,
|
|
delayMs: 1,
|
|
sleep: async () => undefined,
|
|
onMerged: (merged) => {
|
|
snaps.push(merged);
|
|
},
|
|
});
|
|
assert.equal(result.serverDocId, 'doc_proj6089_intake');
|
|
assert.equal(result.attempts, 3);
|
|
assert.ok(snaps.length >= 3);
|
|
assert.ok(snaps[snaps.length - 1].some((d) => d.docId === 'doc_proj6089_intake'));
|
|
});
|
|
|
|
test('pollUntilPostTaskServerDoc returns null after maxAttempts without relying on setState', async () => {
|
|
const local = [base({ docId: 'doc-p6089-intake', isTemplateOnly: true })];
|
|
const result = await pollUntilPostTaskServerDoc({
|
|
listDocs: async () => [],
|
|
getDeliverables: () => local,
|
|
merge: (prev) => prev,
|
|
projectId: 6089,
|
|
taskKey: 'intake',
|
|
maxAttempts: 3,
|
|
delayMs: 1,
|
|
sleep: async () => undefined,
|
|
});
|
|
assert.equal(result.serverDocId, null);
|
|
assert.equal(result.attempts, 3);
|
|
assert.equal(result.merged[0]?.docId, 'doc-p6089-intake');
|
|
});
|