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"; const expected = ["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); 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), [ "请帮我梳理目前的事业方向和下一步重点。", "请帮我看看我在关系中容易重复什么模式。", "请帮我分析目前的财富重点、风险和更稳妥的选择。", "请从非医疗诊断的角度,帮我看看近期的身心压力和调整重点。", "请帮我看看我更适合怎样学习,以及如何安排下一步。", "请帮我分析现阶段是否适合迁居、置业或考虑海外发展。", "请帮我看看家庭关系中最需要处理的问题和责任边界。", "请帮我看看未来一年的主要趋势、机会和需要留意的阶段。", "请帮我看看目前适合推进什么,哪些事情需要再等等。", "请结合我的情况,帮我找出现在最值得优先处理的三个问题。", ]); 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", () => { for (const theme of expected) { 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); });