Files
Jyotisha/frontend/tests/subject-birth.test.ts
T
jesse-ux ab6c55f822 feat: keep up to five chart subjects on typed columns
Chart, ephemeris, reports, daily language, consult, and synastry read birth data only through resolveSubjectBirth. The current person is a request parameter outside Home(). /people replaces the settings chart pane, and deleting someone removes that person's chats and reports.
2026-09-25 04:19:05 +08:00

137 lines
4.5 KiB
TypeScript

import assert from "node:assert/strict";
import { readFileSync } from "node:fs";
import test from "node:test";
import { resolveConsultationSubject } from "../src/lib/consultation-subject-resolver.ts";
import {
reportBelongsToSubject,
resolveSubjectBirth,
sessionBelongsToSubject,
} from "../src/lib/subject-birth.ts";
const selfRow = {
name: "虚构甲",
birth_date: "1991-04-07",
reported_birth_time: "09:15:00",
birth_time_source: "family_exact",
birth_time_status: "reported",
birth_place_label: "虚构城",
latitude: 31.2,
longitude: 121.4,
timezone_offset: 8,
timezone_id: "Asia/Shanghai",
ayanamsa: "lahiri",
};
const otherRow = {
id: "11111111-1111-4111-8111-111111111111",
user_id: "user-1",
role: "other",
name: "虚构乙",
birth_date: "1988-03-04",
reported_birth_time: "07:40:00",
birth_time_source: "hospital_record",
birth_time_status: "reported",
birth_place_label: "虚构港",
latitude: 22.3,
longitude: 114.1,
timezone_offset: 8,
timezone_id: "Asia/Shanghai",
ayanamsa: "raman",
};
test("resolveSubjectBirth reads self from profiles and another person from typed columns", async () => {
const self = await resolveSubjectBirth({
userId: "user-1",
subjectId: "self",
loadSelfRow: async () => selfRow,
loadOtherRow: async () => {
throw new Error("self must not read another person");
},
});
assert.equal(self.role, "self");
assert.equal(self.row.birth_date, "1991-04-07");
assert.equal(self.chartable, true);
let requested = "";
const other = await resolveSubjectBirth({
userId: "user-1",
subjectId: otherRow.id,
loadSelfRow: async () => {
throw new Error("another person must not fall back to self");
},
loadOtherRow: async (id) => {
requested = id;
return otherRow;
},
});
assert.equal(requested, otherRow.id);
assert.equal(other.name, "虚构乙");
assert.equal(other.row.reported_birth_time, "07:40:00");
assert.equal(other.row.latitude, 22.3);
});
test("a missing or foreign person fails closed", async () => {
await assert.rejects(
() => resolveSubjectBirth({
userId: "user-1",
subjectId: otherRow.id,
loadSelfRow: async () => selfRow,
loadOtherRow: async () => null,
}),
/subject_not_found/,
);
await assert.rejects(
() => resolveSubjectBirth({
userId: "user-1",
subjectId: otherRow.id,
loadSelfRow: async () => selfRow,
loadOtherRow: async () => ({ ...otherRow, user_id: "user-2" }),
}),
/subject_not_found/,
);
});
test("legacy self sessions stay visible when the filter is self", () => {
assert.equal(sessionBelongsToSubject({ chart_profile_id: null }, "self"), true);
assert.equal(sessionBelongsToSubject({ chart_profile_id: "self" }, "self"), true);
assert.equal(sessionBelongsToSubject({ chart_profile_id: otherRow.id }, "self"), false);
assert.equal(sessionBelongsToSubject({ chart_profile_id: otherRow.id }, otherRow.id), true);
assert.equal(reportBelongsToSubject({ chart_profile_id: null }, "self"), true);
assert.equal(reportBelongsToSubject({ chart_profile_id: otherRow.id }, otherRow.id), true);
});
test("ordinary chat resolves another person through resolveSubjectBirth", async () => {
const resolved = await resolveConsultationSubject({
userId: "user-1",
binding: { chartProfileId: otherRow.id, chartProfileRole: "other", chartProfileName: "伪造" },
loadSelfProfile: async () => selfRow,
loadOwnedChartProfile: async () => ({
id: otherRow.id,
userId: "user-1",
role: "other",
profile: otherRow,
}),
});
assert.equal(resolved.name, "虚构乙");
assert.equal((resolved.profile as { latitude?: number }).latitude, 22.3);
assert.notEqual((resolved.profile as { name?: string }).name, "伪造");
});
test("chart, ephemeris, reports, daily language and consult all use the one birth lookup", () => {
const files = [
"../src/lib/chart-view-service.ts",
"../src/app/api/ephemeris/route.ts",
"../src/app/api/reports/route.ts",
"../src/app/api/daily-starlanguage/route.ts",
"../src/lib/consultation-subject-resolver.ts",
];
for (const file of files) {
const source = readFileSync(new URL(file, import.meta.url), "utf8");
assert.match(source, /loadSubjectBirth|resolveSubjectBirth/);
}
const consult = readFileSync(new URL("../src/app/api/consult/route.ts", import.meta.url), "utf8");
assert.match(consult, /CHART_SUBJECT_SELECT/);
assert.match(consult, /prepareConsultationRoute/);
});