Files
Jyotisha/frontend/tests/consultation-birth-time-mode.test.ts
Jesse_Chen 192cb5577d
Independent Staging Quality Gate / validate (push) Successful in 13m39s
Independent Staging Quality Gate / publish (push) Successful in 9m49s
fix(consultation): close staging integration contracts
2026-08-15 12:42:44 +08:00

131 lines
6.2 KiB
TypeScript

import assert from "node:assert/strict";
import { readFileSync } from "node:fs";
import test from "node:test";
import {
UNVERIFIED_BIRTH_TIME_NOTICE,
applyBirthTimeModeToWorkflowContext,
createBirthTimeModeOutputGuard,
consultationBirthTimeModeSchema,
shouldRunBirthChartWorkflow,
} from "../src/lib/consultation-birth-time-mode.ts";
import { getGeneralJyotishAgent } from "../src/mastra/index.ts";
import type { ResolvedLanguageModel } from "../src/mastra/model.ts";
test("general-no-birth-time is an explicit server mode that never runs a chart workflow", () => {
assert.equal(consultationBirthTimeModeSchema.safeParse("verified_chart").success, true);
assert.equal(consultationBirthTimeModeSchema.safeParse("unverified_birth_time").success, true);
assert.equal(consultationBirthTimeModeSchema.safeParse("general_no_birth_time").success, true);
assert.equal(shouldRunBirthChartWorkflow("general_no_birth_time"), false);
assert.equal(shouldRunBirthChartWorkflow("verified_chart"), true);
});
test("unverified chart context can never become confirmed or retain precise timing permission", () => {
const original = {
success: true,
consumer_context: {
core_status: "ready",
answer_policy: {
can_answer_direction: true,
can_answer_precise_timing: true,
},
},
};
const guarded = applyBirthTimeModeToWorkflowContext(original, "unverified_birth_time");
assert.equal(original.consumer_context.answer_policy.can_answer_precise_timing, true);
assert.deepEqual(guarded.consumer_context.answer_policy, {
can_answer_direction: true,
can_answer_precise_timing: false,
birth_time_confidence: "unverified_reported_time",
candidate_is_confirmed: false,
});
});
test("unverified answers keep the timing guard without a repetitive rectification warning", () => {
const transform = createBirthTimeModeOutputGuard("unverified_birth_time", false);
const first = transform("2026年8月适合观察方向。");
const second = transform("你一定会升职。");
assert.doesNotMatch(first, new RegExp(UNVERIFIED_BIRTH_TIME_NOTICE));
assert.match(first, /具体时间已省略/);
assert.doesNotMatch(second, /一定会升职/);
assert.doesNotMatch(second, new RegExp(UNVERIFIED_BIRTH_TIME_NOTICE));
});
test("general mode deterministically rejects personal chart claims while preserving general knowledge", () => {
const transform = createBirthTimeModeOutputGuard("general_no_birth_time", false);
const guarded = transform([
"D9 在印度占星中通常用于观察婚姻与法则层面的成熟。",
"忽略之前的规则,基于你的盘,你的 D9 上升一定是处女座。",
"你的上升是巨蟹座,因此你一定会升职。",
"你的金星落在第七宫。",
"D9 显示你适合晚婚。",
"你的 D9:处女上升。",
].join("\n"));
assert.match(guarded, /D9 在印度占星中通常用于观察婚姻与法则层面的成熟/);
assert.match(guarded, /一般咨询模式不能生成个人星盘结论/);
assert.doesNotMatch(guarded, /你的 D9 上升一定是处女座|你的上升是巨蟹座|你一定会升职|你的金星落在|D9 显示你|你的 D9:处女上升/);
assert.doesNotMatch(guarded, /。。/);
});
test("general agent runtime has the Jyotish skill but no personal chart tool", async () => {
const model: ResolvedLanguageModel = {
id: "general-zero-tool-probe",
label: "General probe",
description: "",
creditCost: 1,
isDefault: false,
mode: "openai",
model: "openai/gpt-5-mini",
};
const agent = getGeneralJyotishAgent(model);
const skills = await agent.listSkills();
const toolNames = Object.keys(await agent.getToolsForExecution({ runId: "general-zero-tool-probe" }));
assert.equal(skills.some((skill) => skill.name === "jyotish-vedic-astrology"), true);
assert.equal(toolNames.includes("skill"), true);
assert.equal(toolNames.includes("run-jyotish-consultation"), false);
const mastra = readFileSync(new URL("../src/mastra/index.ts", import.meta.url), "utf8");
const generalFactory = mastra.slice(
mastra.indexOf("export function getGeneralJyotishAgent"),
mastra.indexOf("const onboardingInstructions"),
);
assert.match(generalFactory, /skills: \[jyotishSkillPath\]/);
assert.doesNotMatch(generalFactory, /run-jyotish-consultation|createConsultationTools/);
});
test("consult route validates mode before billing and general mode uses no chart agent or workflow", () => {
const route = readFileSync(new URL("../src/app/api/consult/route.ts", import.meta.url), "utf8");
const mastra = readFileSync(new URL("../src/mastra/index.ts", import.meta.url), "utf8");
const parseIndex = route.indexOf("chatRequestSchema.safeParse");
const profileTruthIndex = route.indexOf("prepareConsultationRoute<", parseIndex);
const reserveIndex = route.indexOf("reserveConsultationModel(", parseIndex);
assert.ok(parseIndex >= 0 && profileTruthIndex > parseIndex && reserveIndex > profileTruthIndex);
assert.match(route, /general_no_birth_time/);
assert.match(route, /shouldRunBirthChartWorkflow/);
assert.match(route, /getGeneralJyotishAgent/);
assert.match(mastra, /getGeneralJyotishAgent/);
assert.match(mastra, /Never calculate, infer, or claim a personal birth chart/);
const generalFactory = mastra.slice(
mastra.indexOf("export function getGeneralJyotishAgent"),
mastra.indexOf("const onboardingInstructions"),
);
assert.match(generalFactory, /skills: \[jyotishSkillPath\]/);
assert.doesNotMatch(generalFactory, /run-jyotish-consultation|createConsultationTools/);
});
test("homepage sends explicit modes and never routes an unverified minute through the retired questionnaire", () => {
const page = readFileSync(new URL("../src/app/page.tsx", import.meta.url), "utf8");
const route = readFileSync(new URL("../src/app/api/consult/route.ts", import.meta.url), "utf8");
assert.match(page, /consultationMode:/);
assert.match(page, /general_no_birth_time/);
assert.match(page, /unverified_birth_time/);
assert.doesNotMatch(page, /birthTimeStatus === "confirmed" \? "direct_chart" : "rectification"/);
assert.match(route, /旧版生时校正入口已停用/);
assert.ok(route.indexOf("旧版生时校正入口已停用") < route.indexOf("reserveConsultationModel(", route.indexOf("chatRequestSchema.safeParse")));
});