import assert from "node:assert/strict"; import { readFileSync } from "node:fs"; import test from "node:test"; import { consultationDomainIds, consultationDomainRegistry, normalizeConsultationDomain, validateConsultationDomainPlan, } from "../src/lib/consultation-domain-registry.ts"; import { consultationInputSchema } from "../src/mastra/consultation-workflow.ts"; import { chatSessionWriteSchema } from "../src/lib/chat-session-write-contract.ts"; import { defaultGuidedJyotishTopics, generalGuidedJyotishTopics, } from "../src/lib/guided-jyotish-topics.ts"; // 原值:十个领域,无 parents / children。 // 新值:family 后面加 parents、children(数据卡子领域,引擎侧按 family 计算)。 // 原因:TASK-consult-evidence-card-20260927 D2。会话主题(chat_sessions.theme CHECK)仍是原十个,见 sessionThemes。 const expected = ["career", "marriage", "wealth", "health", "education", "migration", "family", "parents", "children", "annual", "timing", "general"]; const sessionThemes = ["career", "marriage", "wealth", "health", "education", "migration", "family", "annual", "timing", "general"]; test("domain registry is the complete allowlist used by UI and workflow input", () => { assert.deepEqual([...consultationDomainIds], expected); assert.deepEqual(consultationDomainRegistry.map((domain) => domain.id), expected); assert.deepEqual(defaultGuidedJyotishTopics.map((topic) => topic.id), expected); for (const theme of expected) { assert.equal(consultationInputSchema.safeParse({ year: 2000, month: 1, day: 1, hour: 0, minute: 0, lat: 0, lon: 0, tz: 0, city: "Test", question: "test", theme }).success, true); } }); test("aliases normalize to stable persisted domain ids", () => { assert.equal(normalizeConsultationDomain("relationship"), "marriage"); assert.equal(normalizeConsultationDomain("finance"), "wealth"); assert.equal(normalizeConsultationDomain("health-pressure"), "health"); assert.equal(normalizeConsultationDomain("home"), "migration"); assert.equal(normalizeConsultationDomain("unknown"), null); }); test("server domain plans are allowlisted, ordered and deduplicated", () => { assert.deepEqual(validateConsultationDomainPlan(["career", "finance", "career", "home"]), ["career", "wealth", "migration"]); assert.throws(() => validateConsultationDomainPlan([]), /invalid_consultation_domain_plan/); assert.throws(() => validateConsultationDomainPlan(["career", "prashna"]), /unsupported_consultation_domain/); }); test("default guided topics derive every canonical domain with evidence and claim boundaries", () => { // Moved from starter-questions.test.ts when homepage theme cards were removed. const guidedTopicsSource = readFileSync(new URL("../src/lib/guided-jyotish-topics.ts", import.meta.url), "utf8"); // 原值:assert.equal(consultationDomainIds.length, 10);新值:12;原因:D2 新增 parents / children。 assert.equal(consultationDomainIds.length, 12); assert.deepEqual(consultationDomainRegistry.map((domain) => domain.id), [...consultationDomainIds]); assert.match(guidedTopicsSource, /consultationDomainRegistry\.map/); assert.deepEqual(defaultGuidedJyotishTopics.map((topic) => topic.id), [...consultationDomainIds]); assert.equal(defaultGuidedJyotishTopics.length, consultationDomainRegistry.length); for (const [index, topic] of defaultGuidedJyotishTopics.entries()) { const domain = consultationDomainRegistry[index]; assert.equal(topic.id, domain.id); assert.equal(topic.label, domain.label); assert.equal(topic.prompt, domain.prompt); assert.equal(topic.strictWorkflowRoute, domain.strictWorkflowRoute); assert.deepEqual(topic.evidencePreview, [...domain.evidencePreview]); assert.equal(topic.confidenceCap, domain.confidenceCap); assert.equal(topic.claimBoundary, domain.claimBoundary); } const domainById = new Map(consultationDomainRegistry.map((domain) => [domain.id, domain])); assert.match(domainById.get("career")?.requiredLayers.join(" ") ?? "", /D10/); assert.match(domainById.get("marriage")?.requiredLayers.join(" ") ?? "", /D9/); assert.match(domainById.get("wealth")?.requiredLayers.join(" ") ?? "", /Ashtakavarga/); assert.match(domainById.get("timing")?.requiredLayers.join(" ") ?? "", /negative holdout gate/); }); test("general guided topics keep user-centered prompts and the same claim boundaries", () => { // Moved from starter-questions.test.ts. Homepage no longer renders these cards; // the registry remains the contract for personal-report and no-minute copy. assert.deepEqual(generalGuidedJyotishTopics.map((topic) => topic.id), [...consultationDomainIds]); assert.deepEqual(generalGuidedJyotishTopics.map((topic) => topic.prompt), [ "请帮我梳理目前的事业方向和下一步重点。", "请帮我看看我在关系中容易重复什么模式。", "请帮我分析目前的财富重点、风险和更稳妥的选择。", "请从非医疗诊断的角度,帮我看看近期的身心压力和调整重点。", "请帮我看看我更适合怎样学习,以及如何安排下一步。", "请帮我分析现阶段是否适合迁居、置业或考虑海外发展。", "请帮我看看家庭关系中最需要处理的问题和责任边界。", // 新增两行(D2):父母、子女的无出生分钟版问法。 "请帮我看看我和父母之间最需要处理的问题。", "请帮我看看我和孩子相处时最需要留意的地方。", "请帮我看看未来一年的主要趋势、机会和需要留意的阶段。", "请帮我看看目前适合推进什么,哪些事情需要再等等。", "请结合我的情况,帮我找出现在最值得优先处理的三个问题。", ]); for (const topic of generalGuidedJyotishTopics) { const personalTopic = defaultGuidedJyotishTopics.find((candidate) => candidate.id === topic.id); assert.ok(personalTopic); assert.equal(topic.label, personalTopic.label); assert.equal(topic.strictWorkflowRoute, personalTopic.strictWorkflowRoute); assert.deepEqual(topic.evidencePreview, personalTopic.evidencePreview); assert.equal(topic.confidenceCap, personalTopic.confidenceCap); assert.equal(topic.claimBoundary, personalTopic.claimBoundary); assert.notEqual(topic.prompt, personalTopic.prompt); } assert.ok(generalGuidedJyotishTopics.every((topic) => /我/.test(topic.prompt))); assert.ok(generalGuidedJyotishTopics.every((topic) => !/印度占星|一般如何|通常|哪些因素|证据层/.test(topic.prompt))); }); test("session persistence accepts every canonical domain and rejects product routes", () => { // 原值:遍历 expected(全部领域)都能落库。 // 新值:遍历 sessionThemes(chat_sessions.theme CHECK 的十个)能落库;parents / children 不能落库。 // 原因:D2 的两个子领域只作用于数据卡,不改数据库;写入口先挡住,免得撞 CHECK 变成 500。 for (const theme of ["parents", "children"]) { const parsed = chatSessionWriteSchema.safeParse({ title: "Test", theme, model_id: "test", messages: [], session_type: "consultation", rectification_case_id: null, updated_at: "2026-08-14T00:00:00.000Z" }); assert.equal(parsed.success, false, theme); } for (const theme of sessionThemes) { const parsed = chatSessionWriteSchema.safeParse({ title: "Test", theme, model_id: "test", messages: [], session_type: "consultation", rectification_case_id: null, updated_at: "2026-08-14T00:00:00.000Z" }); assert.equal(parsed.success, true, theme); } assert.equal(chatSessionWriteSchema.safeParse({ title: "Test", theme: "muhurta", model_id: "test", messages: [], session_type: "consultation", rectification_case_id: null, updated_at: "2026-08-14T00:00:00.000Z" }).success, false); });