import assert from "node:assert/strict"; import { spawnSync } from "node:child_process"; import { readFileSync } from "node:fs"; import { fileURLToPath } from "node:url"; import test from "node:test"; import { parseEphemerisOkResponse } from "../src/lib/ephemeris-contract.ts"; import { eventsFromEngine, natalHouseFromSigns, panchangaFactorsFromEngine, transitsFromEngine, } from "../src/lib/ephemeris-view.ts"; const routeSource = readFileSync(new URL("../src/app/api/ephemeris/route.ts", import.meta.url), "utf8"); const viewSource = readFileSync(new URL("../src/lib/ephemeris-view.ts", import.meta.url), "utf8"); const frontendRoot = fileURLToPath(new URL("../", import.meta.url)); const panchangaGolden = JSON.parse(readFileSync(new URL("./fixtures/ephemeris-panchanga-range.golden.json", import.meta.url), "utf8")); const natalGolden = JSON.parse(readFileSync(new URL("./fixtures/ephemeris-chart-natal.golden.json", import.meta.url), "utf8")); const transitGolden = JSON.parse(readFileSync(new URL("./fixtures/ephemeris-chart-transit-2026-09-15.golden.json", import.meta.url), "utf8")); const completeRow = { name: "Example", birth_date: "1990-04-09", reported_birth_time: "13:24", active_birth_time: "13:24", birth_time: "13:24", birth_time_status: "confirmed", latitude: 31.19, longitude: 121.44, timezone_offset: 8, timezone_id: "Asia/Shanghai", ayanamsa: "raman", }; function executeRoute(input: { readonly user: boolean; readonly row: Record | null; readonly other?: Record | null; readonly subject?: string; readonly date?: string; readonly dates?: readonly string[]; readonly eventsStatus?: number; readonly abortOnFetch?: boolean; }) { const script = String.raw` import { mock } from "node:test"; import { readFileSync } from "node:fs"; import { join } from "node:path"; import { pathToFileURL } from "node:url"; const moduleUrl = (path) => pathToFileURL(process.cwd() + "/" + path).href; const user = ${input.user ? "true" : "false"}; const row = ${JSON.stringify(input.row)}; const other = ${JSON.stringify(input.other ?? null)}; const subject = ${JSON.stringify(input.subject ?? "")}; const dates = ${JSON.stringify(input.dates ?? [input.date ?? "2026-09-15"])}; const eventsStatus = ${input.eventsStatus ?? 404}; const abortOnFetch = ${input.abortOnFetch ? "true" : "false"}; const fixture = (name) => JSON.parse(readFileSync(join(process.cwd(), "tests/fixtures", name), "utf8")); const panchanga = fixture("ephemeris-panchanga-range.golden.json"); const natal = fixture("ephemeris-chart-natal.golden.json"); const transit = fixture("ephemeris-chart-transit-2026-09-15.golden.json"); let billingImported = false; mock.module("@/lib/consultation-billing", { namedExports: { charge() { billingImported = true; throw new Error("billing must not run"); }, }, }); mock.module("@/mastra", { namedExports: { getAgent() { throw new Error("mastra must not run"); }, }, }); mock.module("@/lib/request-rate-limit", { namedExports: { consumeUserRequestRateLimit: () => ({ ok: true }), }, }); mock.module("@/lib/daily-starlanguage", { namedExports: { calendarDateInTimeZone: () => "2026-09-15", }, }); mock.module("@/lib/supabase/server", { namedExports: { createServerSupabaseClient: async () => ({ auth: { getUser: async () => ({ data: { user: user ? { id: "user-1" } : null }, error: null }) }, from: (table) => ({ select() { return this; }, eq() { return this; }, async maybeSingle() { return { data: table === "chart_profiles" ? other : row, error: null }; }, }), }), }, }); const upstream = []; let signalAborted = null; const controller = new AbortController(); globalThis.fetch = async (inputUrl, init) => { const url = String(inputUrl); const body = init?.body ? JSON.parse(String(init.body)) : {}; if (abortOnFetch && signalAborted === null) { controller.abort(); signalAborted = Boolean(init?.signal?.aborted); } upstream.push({ url, body, signalAborted: Boolean(init?.signal?.aborted) }); if (url.endsWith("/api/panchanga_range")) { return new Response(JSON.stringify(panchanga), { status: 200, headers: { "content-type": "application/json" } }); } if (url.endsWith("/api/chart")) { const packet = Number(body.year) === 1990 ? natal : transit; return new Response(JSON.stringify(packet), { status: 200, headers: { "content-type": "application/json" } }); } if (url.endsWith("/api/ephemeris_events")) { return new Response(JSON.stringify({ error: "not_found" }), { status: eventsStatus, headers: { "content-type": "application/json" } }); } throw new Error("unexpected_upstream " + url); }; const { GET } = await import(moduleUrl("src/app/api/ephemeris/route.ts")); const responses = []; for (const date of dates) { const response = await GET(new Request("https://staging.jyotisha.chat/api/ephemeris?date=" + date + (subject ? "&subject=" + encodeURIComponent(subject) : ""), abortOnFetch ? { signal: controller.signal } : undefined)); responses.push({ status: response.status, body: await response.json() }); } console.log(JSON.stringify({ status: responses[0].status, body: responses[0].body, responses, upstream, billingImported, signalAborted, })); `; const result = spawnSync(process.execPath, [ "--experimental-test-module-mocks", "--import", "tsx", "--input-type=module", "--eval", script, ], { cwd: frontendRoot, encoding: "utf8" }); assert.equal(result.status, 0, result.stderr || result.stdout); const line = result.stdout.trim().split("\n").at(-1) ?? "{}"; return JSON.parse(line) as { status: number; body: Record; responses: Array<{ status: number; body: Record }>; upstream: Array<{ url: string; body: Record; signalAborted: boolean }>; billingImported: boolean; signalAborted: boolean | null; }; } test("the ephemeris BFF does not import billing or mastra", () => { assert.match(routeSource, /createServerSupabaseClient/); // 原值: the route selected ACCOUNT_BIRTH_SELECT itself. // 新值: birth is loaded only through loadSubjectBirth. // 原因: D13. One lookup for chart, ephemeris, reports, daily language and chat. assert.match(routeSource, /loadSubjectBirth/); assert.match(routeSource, /globalBirthProfileFromAccountRow/); assert.match(routeSource, /consumeUserRequestRateLimit\("ephemeris"/); assert.match(routeSource, /"\/api\/panchanga_range"/); assert.match(routeSource, /"\/api\/chart"/); assert.match(routeSource, /"\/api\/ephemeris_events"/); assert.match(routeSource, /eventsFromEngine\(eventsRaw\)/); assert.doesNotMatch(routeSource, /consultation-billing|@\/mastra/); assert.doesNotMatch(viewSource, /consultation-billing|@\/mastra/); }); test("ephemeris natal and transit chart requests skip VedAstro overview", () => { assert.equal((routeSource.match(/skip_vedastro_main_entry_overview:\s*true/g) ?? []).length, 2); const result = executeRoute({ user: true, row: completeRow }); const chartCalls = result.upstream.filter((item) => item.url.endsWith("/api/chart")); assert.ok(chartCalls.length >= 2); for (const call of chartCalls) { assert.equal(call.body.skip_vedastro_main_entry_overview, true); } }); test("unauthenticated ephemeris requests are 401", () => { const result = executeRoute({ user: false, row: null }); assert.equal(result.status, 401); assert.equal(result.body.status, "unauthenticated"); assert.equal(result.upstream.length, 0); assert.equal(result.billingImported, false); }); test("another person's ephemeris natal request uses that person's birth, and a missing person fails closed", () => { const otherId = "11111111-1111-4111-8111-111111111111"; const other = { id: otherId, user_id: "user-1", role: "other", name: "虚构乙", birth_date: "1988-03-04", reported_birth_time: "07:40:00", birth_time_status: "reported", latitude: 22.3, longitude: 114.1, timezone_offset: 8, timezone_id: "Asia/Shanghai", ayanamsa: "raman", }; const found = executeRoute({ user: true, row: completeRow, other, subject: otherId }); const natal = found.upstream.find((item) => item.url.endsWith("/api/chart") && item.body.year === 1988); assert.ok(natal); assert.equal(natal?.body.lat, 22.3); assert.equal(natal?.body.lon, 114.1); assert.equal(found.upstream.some((item) => item.body.year === 1990), false); assert.equal(found.billingImported, false); const missing = executeRoute({ user: true, row: completeRow, other: null, subject: otherId }); assert.equal(missing.status, 409); assert.equal(missing.body.reason, "subject_not_found"); assert.equal(missing.upstream.some((item) => item.url.endsWith("/api/chart")), false); assert.equal(missing.billingImported, false); }); test("panchanga still returns without a birth profile, and natal-relative stays empty", () => { const result = executeRoute({ user: true, row: { timezone_id: "Asia/Shanghai" } }); assert.equal(result.status, 200); const parsed = parseEphemerisOkResponse(result.body); assert.ok(parsed); assert.equal(parsed?.panchanga.available, true); assert.equal(parsed?.panchanga.factors.length, 5); assert.equal(parsed?.transits.available, false); assert.equal(parsed?.transits.natalRelativeAvailable, false); assert.equal(parsed?.transits.natalRelativeNote, "missing_natal"); assert.equal(parsed?.events.status, "unavailable"); assert.equal(result.upstream.some((item) => item.url.endsWith("/api/chart")), false); assert.equal(result.billingImported, false); }); test("ephemeris_events failure degrades instead of failing the page", () => { const result = executeRoute({ user: true, row: completeRow, eventsStatus: 404 }); assert.equal(result.status, 200); const parsed = parseEphemerisOkResponse(result.body); assert.ok(parsed); assert.equal(parsed?.panchanga.available, true); assert.equal(parsed?.transits.available, true); assert.equal(parsed?.transits.natalRelativeAvailable, true); assert.equal(parsed?.events.status, "unavailable"); assert.equal(result.billingImported, false); assert.ok(result.upstream.some((item) => item.url.endsWith("/api/ephemeris_events"))); }); test("golden engine packets pass the narrow response contract", () => { const factors = panchangaFactorsFromEngine(panchangaGolden, "2026-09-15"); assert.equal(factors.length, 5); assert.equal(factors.find((item) => item.key === "tithi")?.value, "Shukla Panchami"); assert.equal(factors.find((item) => item.key === "nakshatra")?.detail, "第 4 足"); const transits = transitsFromEngine({ transitChart: transitGolden, natalChart: natalGolden }); assert.equal(transits.bodies.length, 9); assert.equal(transits.natalRelativeAvailable, true); const sun = transits.bodies.find((item) => item.planet === "Sun"); assert.equal(sun?.signLabel, "狮子座"); assert.equal(sun?.natalHouse, natalHouseFromSigns("Leo", "Cancer")); assert.equal(sun?.natalHouse, 2); const parsed = parseEphemerisOkResponse({ status: "ok", date: "2026-09-15", today: "2026-09-15", timezoneId: "Asia/Shanghai", panchanga: { available: true, ayanamsaNote: "lahiri_not_account", factors }, transits: { available: true, natalRelativeAvailable: true, natalRelativeNote: "shown", ayanamsa: "raman", bodies: transits.bodies, }, events: { status: "unavailable" }, }); assert.ok(parsed); assert.equal(eventsFromEngine(null).status, "unavailable"); assert.equal(eventsFromEngine({ error: "not_found" }).status, "unavailable"); }); test("the same account loads the natal chart once across two dates and forwards the request signal", () => { assert.match(routeSource, /request\.signal/); assert.match(routeSource, /readChartViewEngineCache/); assert.match(routeSource, /writeChartViewEngineCache/); const result = executeRoute({ user: true, row: completeRow, dates: ["2026-09-15", "2026-09-16"], }); assert.equal(result.responses.length, 2); assert.equal(result.responses[0]?.body.date, "2026-09-15"); assert.equal(result.responses[1]?.body.date, "2026-09-16"); const charts = result.upstream.filter((item) => item.url.endsWith("/api/chart")); const natal = charts.filter((item) => item.body.year === 1990); const transit = charts.filter((item) => item.body.year !== 1990); assert.equal(natal.length, 1); assert.equal(transit.length, 2); assert.equal(result.billingImported, false); const aborted = executeRoute({ user: true, row: completeRow, abortOnFetch: true }); assert.equal(aborted.signalAborted, true); assert.equal(aborted.upstream[0]?.signalAborted, true); });