Files
Jyotisha/frontend/tests/timing-output-guard.test.ts
T
Jesse_Chen 52f3c04b3b refactor(chat): remove the post-answer suggestion chips and the table that fed them
Measured use of the three chips above the composer was negligible. They
were also not what they appeared to be: the server looked up a fixed
triplet by session theme and passed it as metadata that overrode
anything the model produced, so the same ten hardcoded sets served every
user regardless of question or chart. That is a plausible reason nobody
pressed them.

Both copies of the per-theme table are gone, reply metadata narrows to
the session title, and the two parse entry points collapse into one now
that they return the same shape. The write schema still tolerates a
suggestions field so a client on the previous bundle does not lose its
message mid-deploy, and stored answers containing the legacy hidden
block are still stripped rather than shown raw.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-08-18 15:44:19 +08:00

184 lines
7.4 KiB
TypeScript

import assert from "node:assert/strict";
import test from "node:test";
import {
GENERAL_NO_BIRTH_TIME_REFUSAL,
guardGeneralNoBirthTimeOutput,
guardPreciseTimingOutput,
} from "../src/lib/timing-output-guard.ts";
import { streamTextResponse } from "../src/lib/stream-text-response.ts";
import { parseAgentReply } from "../src/lib/agent-reply.ts";
import { createBirthTimeModeOutputGuard } from "../src/lib/consultation-birth-time-mode.ts";
test("removes exact dates and months when precise timing is blocked", () => {
const guarded = guardPreciseTimingOutput(
"你会在2027年3月15日结婚,事业将在11月转折。",
);
assert.doesNotMatch(guarded, /2027年3月15日|11月/);
assert.match(guarded, /具体时间已省略/);
});
test("removes guarantee conclusions when the evidence contract is incomplete", () => {
const guarded = guardPreciseTimingOutput("我保证你一定会升职。");
assert.doesNotMatch(guarded, /保证你一定会升职/);
assert.match(guarded, /保证性结论已省略/);
assert.equal(
guardPreciseTimingOutput("You will definitely get promoted."),
"[保证性结论已省略].",
);
});
test("guards a date that crosses streamed chunks", async () => {
async function* reply() {
yield "应期是2027年";
yield "3月15日,但我保证你一定会升职。";
}
const response = streamTextResponse(reply(), {
mode: "mastra",
requestId: "00000000-0000-4000-8000-000000000098",
transformText: guardPreciseTimingOutput,
});
const text = await response.text();
assert.doesNotMatch(text, /2027年3月15日|保证你一定会升职/);
assert.match(text, /保证性结论已省略/);
});
test("guards only visible prose and preserves AYANAM blocks across arbitrary chunk boundaries", async () => {
const suggestions = '<!--AYANAM_SUGGESTIONS:["你一定会升职吗?","D9 是什么?","先完成生时校正"]-->';
const title = "<!--AYANAM_TITLE:一般占星咨询-->";
const longVisiblePrefix = "一般知识不依赖个人星盘。".repeat(100);
async function* reply() {
yield `${longVisiblePrefix}正文说你将在2027年`;
yield "3月15日一定会升职。\n<!";
yield "--AYANAM_SUGGESTIONS:[\"你一定会升职吗?\",\"D9 是什么?\",\"先完成生时校正\"]--";
yield ">\n<!--AYANAM_TIT";
yield "LE:一般占星咨询-->";
}
const response = streamTextResponse(reply(), {
mode: "mastra",
requestId: "00000000-0000-4000-8000-000000000099",
transformText: createBirthTimeModeOutputGuard("general_no_birth_time", false),
});
const text = await response.text();
const parsed = parseAgentReply(text);
assert.doesNotMatch(text, /2027年3月15日|正文说你将在.*一定会升职/);
assert.match(text, /^一般知识不依赖个人星盘/);
assert.match(text, /具体时间已省略|保证性结论已省略/);
assert.equal(text.includes(suggestions), true);
assert.equal(text.includes(title), true);
assert.doesNotMatch(text, /<!\[/);
// The guard must keep a hidden block intact across chunks, but parsing strips it:
// an unsolicited suggestion block must never reach the transcript as text or as data.
assert.doesNotMatch(parsed.text, /AYANAM_SUGGESTIONS|你一定会升职吗/);
assert.equal(parsed.title, "一般占星咨询");
});
test("general mode structurally rejects personalized chart placements in Chinese and English", () => {
const unsafeClaims = [
"你的七宫落入摩羯。",
"盘面显示你的事业宫很强。",
"你的金星落第七宫。",
"你的上升落在巨蟹座。",
"你的 D9 显示婚姻会晚一些。",
"Your Venus is in the 7th house.",
"Your ascendant falls in Cancer.",
"Your D9 chart shows a strong marriage house.",
];
for (const claim of unsafeClaims) {
const guarded = guardGeneralNoBirthTimeOutput(claim);
assert.equal(guarded.includes(GENERAL_NO_BIRTH_TIME_REFUSAL), true, claim);
assert.equal(guarded.includes(claim.replace(/[。.]$/, "")), false, claim);
}
assert.equal(
guardGeneralNoBirthTimeOutput("第七宫在占星概念中常与关系相关。"),
"第七宫在占星概念中常与关系相关。",
);
assert.equal(
guardGeneralNoBirthTimeOutput("Venus is generally associated with relating and values."),
"Venus is generally associated with relating and values.",
);
assert.equal(
guardGeneralNoBirthTimeOutput("你问的第七宫,在占星概念中常与关系相关。"),
"你问的第七宫,在占星概念中常与关系相关。",
);
});
test("general mode rejects personalized chart predicates beyond possessive placements", () => {
const unsafeClaims = [
"您的上升星座为巨蟹。",
"你是巨蟹上升。",
"您有一个巨蟹上升。",
"You have Cancer rising.",
"You are Cancer rising.",
"You have a Cancer ascendant.",
"你拥有巨蟹上升。",
"In your chart, Venus occupies the seventh house.",
"For you, Venus falls in the seventh house.",
];
for (const claim of unsafeClaims) {
const guarded = guardGeneralNoBirthTimeOutput(claim);
const punctuation = claim.endsWith("。") ? "。" : ".";
assert.equal(guarded, `${GENERAL_NO_BIRTH_TIME_REFUSAL}${punctuation}`, claim);
}
});
test("general mode preserves educational chart questions and general knowledge", () => {
const safeStatements = [
"你的问题是第七宫的一般含义。",
"你问的是第七宫的一般含义。",
"你问的第七宫在占星概念中常与关系相关。",
"关于你的第七宫的一般含义。",
"你是一个正在研究第七宫的学生。",
"你可以先了解第七宫的一般概念。",
"你有第七宫的一般问题。",
"你有金星方面的问题,可以继续问。",
"你是第七宫问题的提问者。",
"第七宫在占星概念中常与关系相关。",
"Your question is the general meaning of the seventh house.",
"You asked about the seventh house.",
"You have a question about the seventh house.",
"The seventh house is associated with relationships.",
];
for (const statement of safeStatements) {
assert.equal(guardGeneralNoBirthTimeOutput(statement), statement);
}
});
test("hidden AYANAM comments cannot split a personalized claim around the guard", async () => {
const title = "<!--AYANAM_TITLE:一般占星咨询-->";
const suggestions = '<!--AYANAM_SUGGESTIONS:["了解第七宫的一般概念","先完成生时校正","改问一般知识"]-->';
async function* reply() {
yield "一般知识可以说明概念。你的<!";
yield "--AYANAM_TITLE:一般占星咨询--";
yield ">金星落";
yield "第七宫。\n<!--AYANAM_SUGGEST";
yield 'IONS:["了解第七宫的一般概念","先完成生时校正","改问一般知识"]-->';
}
const response = streamTextResponse(reply(), {
mode: "mastra",
requestId: "00000000-0000-4000-8000-000000000097",
transformText: createBirthTimeModeOutputGuard("general_no_birth_time", false),
});
const text = await response.text();
const parsed = parseAgentReply(text);
assert.match(text, /一般知识可以说明概念/);
assert.match(text, new RegExp(GENERAL_NO_BIRTH_TIME_REFUSAL));
assert.doesNotMatch(text, /你的\s*金星落第七宫/);
assert.equal(text.includes(title), true);
assert.equal(text.includes(suggestions), true);
assert.equal(parsed.title, "一般占星咨询");
assert.doesNotMatch(parsed.text, /AYANAM_SUGGESTIONS|了解第七宫的一般概念/);
});