同一人的 Chara、Yogini 和以后的大运键各走各的缓存。页面日期直接用引擎返回的日子。 Co-Authored-By: Grok (agent loop) Co-authored-by: Cursor <cursoragent@cursor.com>
153 lines
6.2 KiB
TypeScript
153 lines
6.2 KiB
TypeScript
import assert from "node:assert/strict";
|
|
import { readFileSync } from "node:fs";
|
|
import React from "react";
|
|
import { renderToStaticMarkup } from "react-dom/server";
|
|
import test from "node:test";
|
|
|
|
import { ChartDashaTab } from "../src/components/chart-page/chart-dasha-tab.tsx";
|
|
import { chartViewOkSchema, type ChartViewOk } from "../src/lib/chart-view-contract.ts";
|
|
import {
|
|
chartViewEngineCacheKey,
|
|
chartViewEngineCachePath,
|
|
dashaCacheScope,
|
|
} from "../src/lib/chart-view-engine.ts";
|
|
import { assembleChartView, chartDashaLayerRequest } from "../src/lib/chart-view-load.ts";
|
|
import { ANTARDASHA_DERIVED_NOTE, CHARA_ANTARDASHA_DATE_NOTE, YOGINI_ANTARDASHA_UNVERIFIED_NOTE } from "../src/lib/chart-view-labels.ts";
|
|
|
|
Object.assign(globalThis, { React });
|
|
|
|
const fixtureText = readFileSync(new URL("./fixtures/yogini-dasha-api-1990-01-01.golden.json", import.meta.url), "utf8");
|
|
const yoginiFixture = JSON.parse(fixtureText) as {
|
|
sourceCommand: string;
|
|
ayanamsa: string;
|
|
node_mode: string;
|
|
response: Record<string, unknown>;
|
|
};
|
|
const golden = JSON.parse(
|
|
readFileSync(new URL("./fixtures/chart-view-golden.json", import.meta.url), "utf8"),
|
|
) as { chart: Record<string, unknown>; chara: Record<string, unknown> };
|
|
|
|
const profile = {
|
|
name: "示例",
|
|
date: "1990-06-15",
|
|
time: "12:00",
|
|
placeLabel: "北京",
|
|
latitude: 39.9042,
|
|
longitude: 116.4074,
|
|
timezoneOffset: 8,
|
|
timezoneId: "Asia/Shanghai",
|
|
ayanamsa: "raman",
|
|
birthTimeStatus: "confirmed" as const,
|
|
};
|
|
|
|
const person = {
|
|
userId: "user-1",
|
|
subjectId: "self",
|
|
date: "1990-01-01",
|
|
time: "12:00",
|
|
latitude: 28.6139,
|
|
longitude: 77.209,
|
|
timezoneOffset: 5.5,
|
|
ayanamsa: "lahiri",
|
|
nodeMode: "mean",
|
|
};
|
|
|
|
test("the yogini fixture is a real dasha response, not a hand-built shape", () => {
|
|
assert.match(yoginiFixture.sourceCommand, /_compute_dasha_system/);
|
|
assert.match(yoginiFixture.sourceCommand, /dasha=yogini/);
|
|
assert.equal(yoginiFixture.ayanamsa, "lahiri");
|
|
assert.equal(yoginiFixture.node_mode, "mean");
|
|
const periods = yoginiFixture.response.periods;
|
|
assert.ok(Array.isArray(periods) && periods.length > 0);
|
|
});
|
|
|
|
test("the same person gets a different cache key for each dasha system", () => {
|
|
const followUp = { date: person.date, time: person.time };
|
|
const chara = chartDashaLayerRequest("chara", followUp);
|
|
const yogini = chartDashaLayerRequest("yogini", followUp);
|
|
const kalachakra = chartDashaLayerRequest("kalachakra", followUp);
|
|
assert.equal(chara.path, "/api/dasha/chara");
|
|
assert.equal(chara.cacheScope, undefined);
|
|
assert.equal(yogini.path, "/api/dasha");
|
|
assert.equal(yogini.cacheScope, "dasha:yogini");
|
|
assert.equal(yogini.body.dasha, "yogini");
|
|
assert.equal(kalachakra.path, "/api/dasha");
|
|
assert.equal(kalachakra.cacheScope, dashaCacheScope("kalachakra"));
|
|
assert.equal(kalachakra.body.dasha, "kalachakra");
|
|
const key = (path: string, cacheScope?: string) => chartViewEngineCacheKey({
|
|
...person,
|
|
path: chartViewEngineCachePath(path, cacheScope),
|
|
});
|
|
const keys = [
|
|
key(chara.path, chara.cacheScope),
|
|
key(yogini.path, yogini.cacheScope),
|
|
key(kalachakra.path, kalachakra.cacheScope),
|
|
];
|
|
assert.equal(new Set(keys).size, 3);
|
|
assert.notEqual(JSON.stringify(chara.body), JSON.stringify(yogini.body));
|
|
assert.notEqual(JSON.stringify(yogini.body), JSON.stringify(kalachakra.body));
|
|
});
|
|
|
|
async function yoginiView(): Promise<ChartViewOk> {
|
|
const scopes: Array<string | undefined> = [];
|
|
const result = await assembleChartView({
|
|
userId: "user-1",
|
|
profile,
|
|
asOf: "1990-06-01",
|
|
layers: ["chara", "yogini"],
|
|
postEngine: async (path, body, cacheScope) => {
|
|
if (path === "/api/chart") return { status: "ok", payload: golden.chart };
|
|
if (path === "/api/dasha/chara") return { status: "ok", payload: golden.chara };
|
|
if (path === "/api/dasha") {
|
|
scopes.push(cacheScope);
|
|
assert.equal(body.dasha, "yogini");
|
|
return { status: "ok", payload: yoginiFixture.response };
|
|
}
|
|
return { status: "http_error", path, elapsedMs: 1, httpStatus: 500 };
|
|
},
|
|
});
|
|
assert.deepEqual(scopes, ["dasha:yogini"]);
|
|
return chartViewOkSchema.parse(result.body);
|
|
}
|
|
|
|
test("a real yogini response maps dates and labels without deriving them", async () => {
|
|
const view = await yoginiView();
|
|
const track = view.dasha.yogini;
|
|
assert.equal(track.id, "yogini");
|
|
const first = track.periods[0];
|
|
assert.ok(first);
|
|
assert.equal(first.label, "Pingala / 太阳");
|
|
assert.equal(first.derived, false);
|
|
assert.equal(first.start, "1990-01-01");
|
|
assert.equal(first.end, "1990-01-12");
|
|
assert.equal(first.antardashas.length, 1);
|
|
assert.equal(first.antardashas[0]?.label, "Mangala / 月亮");
|
|
assert.equal(first.antardashas[0]?.derived, false);
|
|
assert.equal(first.antardashas[0]?.start, "1990-01-01");
|
|
const dhanya = track.periods.find((period) => period.label === "Dhanya / 木星");
|
|
assert.ok(dhanya);
|
|
assert.equal(dhanya.derived, false);
|
|
assert.equal(dhanya.antardashas.length, 8);
|
|
assert.equal(dhanya.antardashas.every((item) => item.derived === false), true);
|
|
assert.equal(dhanya.current, true);
|
|
assert.equal(dhanya.antardashas.some((item) => item.current), true);
|
|
assert.equal(track.antardashaNote, YOGINI_ANTARDASHA_UNVERIFIED_NOTE);
|
|
});
|
|
|
|
test("the yogini note sits under that track only", async () => {
|
|
const view = await yoginiView();
|
|
const yogini = renderToStaticMarkup(React.createElement(ChartDashaTab, { view, system: "yogini" }));
|
|
assert.equal((yogini.match(/小运未经外部软件核对。/g) ?? []).length, 1);
|
|
assert.match(yogini, /<dt>大运<\/dt>/);
|
|
assert.match(yogini, /<dt>小运<\/dt>/);
|
|
assert.match(yogini, /<dt>小运剩余<\/dt><dd>\d+ 个月<\/dd>/);
|
|
assert.match(yogini, /Dhanya \/ 木星/);
|
|
assert.match(yogini, new RegExp(ANTARDASHA_DERIVED_NOTE));
|
|
assert.doesNotMatch(yogini, new RegExp(CHARA_ANTARDASHA_DATE_NOTE.replace(/[.*+?^${}()|[\]\\]/g, "\\$&")));
|
|
const chara = renderToStaticMarkup(React.createElement(ChartDashaTab, { view }));
|
|
assert.equal((chara.match(/小运未经外部软件核对。/g) ?? []).length, 0);
|
|
assert.match(chara, new RegExp(CHARA_ANTARDASHA_DATE_NOTE.replace(/[.*+?^${}()|[\]\\]/g, "\\$&")));
|
|
assert.match(chara, new RegExp(ANTARDASHA_DERIVED_NOTE));
|
|
assert.match(chara, /aria-selected="true"[^>]*>Chara</);
|
|
});
|