Files
Jyotisha/frontend/tests/daily-starlanguage-library.test.ts
T

117 lines
5.0 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
import assert from "node:assert/strict";
import { readFileSync } from "node:fs";
import test from "node:test";
import { dailyStarlanguageCardSchema, type DailyStarlanguageEvidence } from "../src/lib/daily-starlanguage.ts";
import {
HOUSE_ACTIONS,
HOUSE_CAUTIONS,
HOUSE_LINES,
PHASE_TAILS,
SIGN_MOODS,
composeDailyCard,
lunarPhase,
moonAgeDays,
type LunarPhase,
} from "../src/lib/daily-starlanguage-library.ts";
// BUG-1109: the home 「今日星语」 comes from a fixed library, not a model.
function evidence(overrides: Partial<DailyStarlanguageEvidence> = {}): DailyStarlanguageEvidence {
return {
today: "2026-09-30",
birthTimeVerified: true,
ascendantSign: "Aries",
moonSign: "Cancer",
transitMoonSign: "Capricorn",
transitMoonHouse: 10,
vimshottari: { mahadasha: "Saturn", antardasha: null, remainingDays: null, nakshatra: null },
narayanaSign: null,
divisional: [],
transit: null,
functionalBenefics: [],
functionalMalefics: [],
missingLayers: [],
...overrides,
};
}
/** One date per phase and per variant: walk forward until every (phase, variant) pair is covered. */
function datesCovering(): Map<string, string> {
const found = new Map<string, string>();
const start = Date.UTC(2026, 0, 1);
for (let offset = 0; offset < 400 && found.size < 12; offset += 1) {
const date = new Date(start + offset * 86_400_000).toISOString().slice(0, 10);
const day = Math.floor((start + offset * 86_400_000) / 86_400_000);
const key = `${lunarPhase(date)}:${day % 3}`;
if (!found.has(key)) found.set(key, date);
}
return found;
}
test("the library is complete: 12 houses × 3 lines, 4 phases × 2 tails, 2 actions and 2 cautions per house, 12 sign moods", () => {
for (let house = 1; house <= 12; house += 1) {
assert.equal(HOUSE_LINES[house]?.length, 3, `house ${house}`);
assert.equal(HOUSE_ACTIONS[house]?.length, 2);
assert.equal(HOUSE_CAUTIONS[house]?.length, 2);
}
for (const phase of ["new", "waxing", "full", "waning"] as LunarPhase[]) assert.equal(PHASE_TAILS[phase].length, 2);
assert.equal(Object.keys(SIGN_MOODS).length, 12);
});
test("every house × phase × variant composes a valid card within the schema limits", () => {
const dates = datesCovering();
assert.equal(dates.size, 12, "all four phases × three variants are reachable");
let cards = 0;
for (let house = 1; house <= 12; house += 1) {
for (const date of dates.values()) {
const card = composeDailyCard(evidence({ transitMoonHouse: house, today: date }), date);
const parsed = dailyStarlanguageCardSchema.safeParse(card);
assert.ok(parsed.success, `${house} ${date}: ${JSON.stringify(card)}`);
assert.ok(card.trend.length <= 90 && card.action.length <= 90 && card.caution.length <= 90);
assert.doesNotMatch(card.trend, /宫主|相位|大运|第\d+宫/, "no jargon in the trend line");
cards += 1;
}
}
assert.equal(cards, 144);
});
test("same date, same card; consecutive days in the same house read differently", () => {
const today = composeDailyCard(evidence(), "2026-09-30");
assert.deepEqual(composeDailyCard(evidence(), "2026-09-30"), today);
const tomorrow = composeDailyCard(evidence(), "2026-10-01");
assert.notEqual(tomorrow.trend, today.trend);
});
test("the lunar phase follows the synodic month from a known new moon", () => {
// 2000-01-06 was a new moon; about 14.8 days later a full moon.
assert.equal(lunarPhase("2000-01-06"), "new");
assert.equal(lunarPhase("2000-01-21"), "full");
assert.equal(lunarPhase("2000-01-13"), "waxing");
assert.equal(lunarPhase("2000-01-28"), "waning");
// Known new moon 2026-09-11 (±1 day) and full moon 2026-09-26.
assert.equal(lunarPhase("2026-09-11"), "new");
assert.equal(lunarPhase("2026-09-26"), "full");
const age = moonAgeDays("2026-09-11");
assert.ok(age < 1.5 || age > 28, `new moon within about a day, age ${age}`);
});
test("without a confirmed birth time the house is not used; the line falls back to the Moon's sign", () => {
const card = composeDailyCard(evidence({ birthTimeVerified: false }), "2026-09-30");
assert.match(card.trend, /^今天月亮在摩羯座,/);
assert.match(card.caution, /出生时间尚未确认/);
assert.ok(dailyStarlanguageCardSchema.safeParse(card).success);
const noHouse = composeDailyCard(evidence({ transitMoonHouse: null }), "2026-09-30");
assert.match(noHouse.trend, /^今天月亮在摩羯座,/);
// Neither house nor sign: the older evidence composer still gives a valid card.
const bare = composeDailyCard(evidence({ transitMoonHouse: null, transitMoonSign: null }), "2026-09-30");
assert.ok(dailyStarlanguageCardSchema.safeParse(bare).success);
});
test("the route calls no model for the daily line", () => {
const route = readFileSync(new URL("../src/app/api/daily-starlanguage/route.ts", import.meta.url), "utf8");
assert.match(route, /composeDailyCard\(evidence, today\)/);
assert.match(route, /source: "library"/);
assert.doesNotMatch(route, /@\/mastra|getDailyStarlanguageAgent|loadLanguageModelCatalog|generate\(/);
});