Files
Jyotisha/frontend/tests/rectification-v10-tool-contract.test.ts
2026-08-15 00:56:06 +08:00

183 lines
5.8 KiB
TypeScript

import assert from "node:assert/strict";
import test from "node:test";
import { PUBLIC_RECTIFICATION_TOOLS } from "../src/lib/rectification-agentic/v9/public-receipt.ts";
import { createRectificationV9Tools } from "../src/mastra/rectification-v9-tools.ts";
import {
CASE_ID,
EVIDENCE_ID,
FOCUS_ID,
RESULT_ID,
CANDIDATE_ID,
TURN_ID,
USER_ID,
fakeAccounting,
} from "./rectification-v9-test-support.ts";
type ToolSchema = {
inputSchema: {
safeParse(value: unknown): { success: boolean };
};
};
const EXACT_TOOL_KEYS = [
"rectification-read-case",
"rectification-set-focus",
"rectification-resolve-focus",
"rectification-record-evidence-batch",
"rectification-propose-evidence",
"rectification-confirm-evidence",
"rectification-revise-evidence",
"rectification-compare-candidates",
"rectification-read-diagnostics",
"rectification-offer-candidates",
"rectification-accept-candidate",
"rectification-confirm-birth-time",
"rectification-close-case",
] as const;
function toolsUnderTest() {
return createRectificationV9Tools({
userId: USER_ID,
caseId: CASE_ID,
turnId: TURN_ID,
accounting: fakeAccounting({}).client as never,
});
}
const validInputs: Record<(typeof EXACT_TOOL_KEYS)[number], Record<string, unknown>> = {
"rectification-read-case": { caseId: CASE_ID },
"rectification-set-focus": {
caseId: CASE_ID,
questionId: "career-month-question",
intent: "clarify_event_date",
targetEvidenceId: EVIDENCE_ID,
targetDomain: "career",
targetKind: "career_entry",
expectedAnswerSchema: { required: ["month"] },
},
"rectification-resolve-focus": {
caseId: CASE_ID,
focusId: FOCUS_ID,
status: "resolved",
evidenceId: EVIDENCE_ID,
},
"rectification-record-evidence-batch": {
caseId: CASE_ID,
focusId: FOCUS_ID,
items: [{
quote: "2016年9月去北京工作",
proposedKind: "career_entry",
subject: "self",
domain: "career",
datePrecision: "month",
occurredFrom: "2016-09",
summary: "2016年9月去北京工作",
}],
},
"rectification-propose-evidence": {
caseId: CASE_ID,
quote: "2016年9月去北京工作",
proposedKind: "career_entry",
subject: "self",
domain: "career",
datePrecision: "month",
occurredFrom: "2016-09",
summary: "2016年9月去北京工作",
},
"rectification-confirm-evidence": {
caseId: CASE_ID,
focusId: FOCUS_ID,
evidenceId: EVIDENCE_ID,
},
"rectification-revise-evidence": {
caseId: CASE_ID,
focusId: FOCUS_ID,
evidenceId: EVIDENCE_ID,
quote: "不是9月,是10月",
datePrecision: "month",
occurredFrom: "2016-10",
summary: "更正为2016年10月去北京工作",
},
"rectification-compare-candidates": { caseId: CASE_ID },
"rectification-read-diagnostics": { caseId: CASE_ID },
"rectification-offer-candidates": { caseId: CASE_ID },
"rectification-accept-candidate": {
caseId: CASE_ID,
resultId: RESULT_ID,
candidateId: CANDIDATE_ID,
},
"rectification-confirm-birth-time": {
caseId: CASE_ID,
resultId: RESULT_ID,
candidateId: CANDIDATE_ID,
consentQuote: "就用05:02",
},
"rectification-close-case": {
caseId: CASE_ID,
reason: "completed_by_user",
},
};
test("V10 exposes exactly the 13 allowlisted rectification tool keys", () => {
const tools = toolsUnderTest();
assert.deepEqual(Object.keys(tools), [...EXACT_TOOL_KEYS]);
assert.deepEqual([...PUBLIC_RECTIFICATION_TOOLS], [...EXACT_TOOL_KEYS]);
assert.equal(new Set(Object.keys(tools)).size, 13);
});
test("all 13 tool input schemas accept their minimal contract and reject unknown fields", () => {
const tools = toolsUnderTest() as unknown as Record<string, ToolSchema>;
for (const toolName of EXACT_TOOL_KEYS) {
const schema = tools[toolName]?.inputSchema;
assert.ok(schema, `${toolName} must expose inputSchema`);
assert.equal(
schema.safeParse(validInputs[toolName]).success,
true,
`${toolName} must accept its documented input`,
);
assert.equal(
schema.safeParse({ ...validInputs[toolName], userId: USER_ID }).success,
false,
`${toolName} must be strict and reject server-owned userId`,
);
assert.equal(
schema.safeParse({ ...validInputs[toolName], candidate_range: { start_time: "04:50", end_time: "05:10" } }).success,
false,
`${toolName} must reject server-owned candidate ranges`,
);
}
});
test("focus/evidence mutation schemas require durable focus and evidence references", () => {
const tools = toolsUnderTest() as unknown as Record<string, ToolSchema>;
const confirm = tools["rectification-confirm-evidence"].inputSchema;
const revise = tools["rectification-revise-evidence"].inputSchema;
const resolve = tools["rectification-resolve-focus"].inputSchema;
assert.equal(confirm.safeParse({ caseId: CASE_ID, evidenceId: EVIDENCE_ID }).success, false);
assert.equal(confirm.safeParse({ caseId: CASE_ID, focusId: FOCUS_ID }).success, false);
assert.equal(revise.safeParse({
...validInputs["rectification-revise-evidence"],
focusId: undefined,
}).success, false);
assert.equal(resolve.safeParse({ caseId: CASE_ID, status: "declined" }).success, false);
});
test("batch item schemas are independently strict and bounded", () => {
const tools = toolsUnderTest() as unknown as Record<string, ToolSchema>;
const schema = tools["rectification-record-evidence-batch"].inputSchema;
const valid = validInputs["rectification-record-evidence-batch"];
const item = (valid.items as Record<string, unknown>[])[0]!;
assert.equal(schema.safeParse({ ...valid, items: [] }).success, false);
assert.equal(schema.safeParse({
...valid,
items: [{ ...item, evidenceId: EVIDENCE_ID }],
}).success, false);
assert.equal(schema.safeParse({
...valid,
items: Array.from({ length: 13 }, () => item),
}).success, false);
});