Files
Jyotisha/frontend/tests/consultation-plan.test.ts
T

127 lines
4.4 KiB
TypeScript

import assert from "node:assert/strict";
import test from "node:test";
import { z } from "zod";
import {
ConsultationPlanValidationError,
consultationPlanMaxCreditCost,
createConsultationPlan,
validateConsultationPlan,
} from "../src/lib/consultation-plan.ts";
import { consultationDomainIds } from "../src/lib/consultation-domain-registry.ts";
for (const theme of consultationDomainIds) {
test(`creates a bounded server-owned ${theme} plan`, () => {
const plan = createConsultationPlan({
userIntent: "请回答我的问题",
theme,
consultationMode: "verified_chart",
modelCreditCost: 1,
});
assert.equal(plan.plan_version, "consultation-plan-v2");
assert.equal(plan.birthTimeMode, "verified_chart");
assert.equal(plan.precisionBoundary, "server_evidence_required");
assert.equal(plan.maxCreditCost, consultationPlanMaxCreditCost);
});
}
test("rejects a theme outside the consultation allowlist", () => {
assert.throws(
() => createConsultationPlan({
userIntent: "请回答我的问题",
theme: "medical" as "career",
consultationMode: "verified_chart",
}),
(error: unknown) => error instanceof z.ZodError,
);
});
test("server birth-time mode fixes the precision boundary", () => {
assert.equal(createConsultationPlan({
userIntent: "未来什么时候适合行动",
theme: "timing",
consultationMode: "unverified_birth_time",
}).precisionBoundary, "precise_timing_blocked");
assert.equal(createConsultationPlan({
userIntent: "什么是上升星座",
theme: "general",
consultationMode: "general_no_birth_time",
}).precisionBoundary, "precise_timing_blocked");
});
test("rejects mode or precision tampering", () => {
const plan = createConsultationPlan({
userIntent: "未来什么时候适合行动",
theme: "timing",
consultationMode: "unverified_birth_time",
});
assert.throws(
() => validateConsultationPlan({ ...plan, precisionBoundary: "server_evidence_required" }, {
consultationMode: "unverified_birth_time",
modelCreditCost: 1,
}),
(error: unknown) => error instanceof ConsultationPlanValidationError
&& error.code === "plan_precision_mismatch",
);
assert.throws(
() => validateConsultationPlan(plan, {
consultationMode: "verified_chart",
modelCreditCost: 1,
}),
(error: unknown) => error instanceof ConsultationPlanValidationError
&& error.code === "plan_mode_mismatch",
);
});
test("rejects model cost above the release consultation ceiling", () => {
assert.throws(
() => createConsultationPlan({
userIntent: "事业如何",
theme: "career",
consultationMode: "verified_chart",
modelCreditCost: consultationPlanMaxCreditCost + 1,
}),
(error: unknown) => error instanceof ConsultationPlanValidationError
&& error.code === "plan_cost_ceiling_exceeded",
);
});
test("rejects non-positive or fractional model costs", () => {
for (const modelCreditCost of [0, -1, 0.5, Number.NaN, Number.POSITIVE_INFINITY]) {
assert.throws(
() => createConsultationPlan({
userIntent: "事业如何",
theme: "career",
consultationMode: "verified_chart",
modelCreditCost,
}),
(error: unknown) => error instanceof ConsultationPlanValidationError
&& error.code === "plan_cost_ceiling_exceeded",
);
}
});
test("deep-freezes the server-owned plan and nested evidence lists", () => {
const plan = createConsultationPlan({
userIntent: "事业如何",
theme: "career",
consultationMode: "verified_chart",
modelCreditCost: 1,
});
const mutable = plan as unknown as {
precisionBoundary: string;
requestedDomains: string[];
requiredEvidenceCategories: string[];
};
assert.equal(Object.isFrozen(plan), true);
assert.equal(Object.isFrozen(plan.requestedDomains), true);
assert.equal(Object.isFrozen(plan.requiredEvidenceCategories), true);
assert.throws(() => { mutable.precisionBoundary = "precise_timing_blocked"; }, TypeError);
assert.throws(() => mutable.requestedDomains.push("timing"), TypeError);
assert.throws(() => mutable.requiredEvidenceCategories.splice(0, 1), TypeError);
assert.equal(plan.precisionBoundary, "server_evidence_required");
assert.deepEqual(plan.requestedDomains, ["career"]);
assert.deepEqual(plan.requiredEvidenceCategories, ["natal_foundation", "domain"]);
});