Files
Jyotisha/frontend/tests/rectification-turn-intent-classifier.test.ts
T
Jesse_Chen 85db4591d3
Independent Staging Quality Gate / validate (push) Successful in 19m52s
Independent Staging Quality Gate / publish (push) Successful in 53m47s
fix(rectification): resolve typed focus answers deterministically
2026-08-27 20:31:05 +08:00

108 lines
4.2 KiB
TypeScript

import assert from "node:assert/strict";
import { readFileSync } from "node:fs";
import test from "node:test";
import {
optionIdForAnswerClass,
parseRectificationTurnIntent,
} from "../src/lib/rectification-agentic/v9/turn-intent-classifier.ts";
import type { ConversationFocus } from "../src/lib/rectification-agentic/v9/tool-service.ts";
import { CASE_ID, FOCUS_ID } from "./rectification-v9-test-support.ts";
function focusWithOptions(expectedAnswerSchema: Record<string, unknown>): ConversationFocus {
return {
id: FOCUS_ID,
caseId: CASE_ID,
questionId: "question-1",
intent: "distinguish_candidates",
targetEvidenceId: null,
targetDomain: "career",
targetKind: "career_change",
expectedAnswerSchema,
status: "active",
askedAt: "2026-08-27T00:00:00.000Z",
resolvedAt: null,
};
}
const SHUFFLED_CHOICE = {
choice: {
prompt: "2023 年前后,工作状态是否出现明显变化?",
option_a: "这段时间没有明显变化",
option_b: "记不清当时的情况",
option_c: "变化明显而且时间吻合",
option_d: "有变化但程度比较弱",
options: [
{ key: "A", label: "这段时间没有明显变化", answer_class: "no" },
{ key: "B", label: "记不清当时的情况", answer_class: "unsure" },
{ key: "C", label: "变化明显而且时间吻合", answer_class: "yes" },
{ key: "D", label: "有变化但程度比较弱", answer_class: "weak_yes" },
],
},
};
test("turn intent parser enforces answer_class only for current-focus answers", () => {
assert.deepEqual(parseRectificationTurnIntent({
intent: "answer_current_focus",
answer_class: "no",
}), {
intent: "answer_current_focus",
answer_class: "no",
});
assert.deepEqual(parseRectificationTurnIntent({
intent: "provide_new_evidence",
answer_class: null,
}), {
intent: "provide_new_evidence",
answer_class: null,
});
assert.equal(parseRectificationTurnIntent({
intent: "answer_current_focus",
answer_class: null,
}), null);
assert.equal(parseRectificationTurnIntent({
intent: "stop_rectification",
answer_class: "no",
}), null);
assert.equal(parseRectificationTurnIntent({
intent: "unclear",
answer_class: null,
extra: true,
}), null);
});
test("answer classes resolve through each dynamic option instead of A/B/C/D position", () => {
const focus = focusWithOptions(SHUFFLED_CHOICE);
assert.equal(optionIdForAnswerClass(focus, "no"), "A");
assert.equal(optionIdForAnswerClass(focus, "unsure"), "B");
assert.equal(optionIdForAnswerClass(focus, "yes"), "C");
assert.equal(optionIdForAnswerClass(focus, "weak_yes"), "D");
});
test("missing or invalid dynamic choice schemas fail closed", () => {
assert.equal(optionIdForAnswerClass(focusWithOptions({}), "no"), null);
assert.equal(optionIdForAnswerClass(focusWithOptions({
choice: {
...SHUFFLED_CHOICE.choice,
options: SHUFFLED_CHOICE.choice.options.map(({ key, label }) => ({ key, label })),
},
}), "no"), null);
});
test("production intent handling contains no semantic regex or positional text parser", () => {
const route = readFileSync(new URL("../src/app/api/rectification/agent/route.ts", import.meta.url), "utf8");
const classifier = readFileSync(new URL("../src/lib/rectification-agentic/v9/turn-intent-classifier.ts", import.meta.url), "utf8");
const choiceCard = readFileSync(new URL("../src/lib/rectification-agentic/v9/choice-card.ts", import.meta.url), "utf8");
const inference = readFileSync(new URL("../src/lib/rectification-agentic/v9/inference-adapter.ts", import.meta.url), "utf8");
const source = [route, classifier, choiceCard, inference].join("\n");
const fastPath = route.slice(
route.indexOf('if (action === "message")'),
route.indexOf("const requestTime"),
);
assert.doesNotMatch(source, /USER_STOP_PATTERN|parseChoiceKeyFromUserMessage/);
assert.doesNotMatch(classifier + fastPath, /\.test\([^\n]*(?:userMessage|user_message|message)/);
assert.doesNotMatch(classifier + fastPath, /(?:userMessage|user_message|message)\.(?:match|search|includes|startsWith|endsWith)\(/);
assert.ok(route.indexOf("classifyRectificationTurnIntent") < route.indexOf("runV9AgentTurn({"));
assert.doesNotMatch(route, /classified\.answer_class!/);
});