170 lines
7.0 KiB
JavaScript
170 lines
7.0 KiB
JavaScript
"use strict";
|
|
|
|
const assert = require("assert");
|
|
const fs = require("fs");
|
|
const path = require("path");
|
|
|
|
const run = async () => {
|
|
const source = fs.readFileSync(path.join(__dirname, "../data/mock.js"), "utf8");
|
|
const contractSource = fs.readFileSync(
|
|
path.join(__dirname, "../utils/genealogy-contracts.js"),
|
|
"utf8",
|
|
);
|
|
const contractUrl = `data:text/javascript;base64,${Buffer.from(contractSource).toString("base64")}`;
|
|
const moduleUrl = `data:text/javascript;base64,${Buffer.from(source.replace("../utils/genealogy-contracts.js", contractUrl)).toString("base64")}`;
|
|
const {
|
|
treeMembers,
|
|
findTreeMemberFixture,
|
|
findTreeMemberPresentationFixture,
|
|
listTreeMemberFixtures,
|
|
listTreeMemberPresentationFixtures,
|
|
} = await import(moduleUrl);
|
|
|
|
assert.deepStrictEqual(
|
|
treeMembers.map((member) => member.id),
|
|
["101", "102", "103", "104", "105", "106"],
|
|
"T01/T03-T08 must consume the same six-member fixture identity set",
|
|
);
|
|
assert.strictEqual(new Set(treeMembers.map((member) => member.id)).size, treeMembers.length);
|
|
|
|
const ids = new Set(treeMembers.map((member) => member.id));
|
|
const supportedStatuses = new Set(["privacy", "deceased", "forbidden"]);
|
|
for (const member of treeMembers) {
|
|
for (const field of [
|
|
"genealogyId",
|
|
"name",
|
|
"relation",
|
|
"generationName",
|
|
"branch",
|
|
"years",
|
|
"birthDate",
|
|
"deathDate",
|
|
"birthplace",
|
|
"status",
|
|
"summary",
|
|
"note",
|
|
]) {
|
|
assert.strictEqual(typeof member[field], "string", `${member.id}.${field} must be a string`);
|
|
}
|
|
assert.strictEqual(member.genealogyId, "1001");
|
|
assert(Number.isInteger(member.generation) && member.generation > 0);
|
|
assert.strictEqual(
|
|
Object.prototype.hasOwnProperty.call(member, "canEdit"),
|
|
false,
|
|
`${member.id} must not publish a fixture-only backend capability`,
|
|
);
|
|
assert(
|
|
member.parentId === "" || typeof member.parentId === "string",
|
|
`${member.id}.parentId must be an empty string or a string identity`,
|
|
);
|
|
assert(supportedStatuses.has(member.status), `${member.id} has an unsupported status`);
|
|
assert(Array.isArray(member.relatives));
|
|
if (member.parentId) {
|
|
assert(ids.has(member.parentId), `${member.id} has an unknown parent`);
|
|
const parent = treeMembers.find((candidate) => candidate.id === member.parentId);
|
|
assert.strictEqual(
|
|
member.generation,
|
|
parent.generation + 1,
|
|
`${member.id} must be exactly one generation below its parent`,
|
|
);
|
|
}
|
|
assert.notStrictEqual(member.parentId, member.id, `${member.id} cannot be its own parent`);
|
|
assert.strictEqual(
|
|
new Set(member.relatives.map((relative) => relative.personId)).size,
|
|
member.relatives.length,
|
|
`${member.id} cannot repeat a relative identity`,
|
|
);
|
|
for (const relative of member.relatives) {
|
|
assert(ids.has(relative.personId), `${member.id} has an unknown relative`);
|
|
assert(relative.personId !== member.id, `${member.id} cannot be its own relative`);
|
|
assert.strictEqual(typeof relative.relation, "string");
|
|
}
|
|
}
|
|
|
|
for (const member of treeMembers) {
|
|
const visited = new Set([member.id]);
|
|
let parentId = member.parentId;
|
|
while (parentId) {
|
|
assert(!visited.has(parentId), `${member.id} has a cyclic parent chain`);
|
|
visited.add(parentId);
|
|
parentId = treeMembers.find((candidate) => candidate.id === parentId)?.parentId || "";
|
|
}
|
|
}
|
|
|
|
assert.strictEqual(findTreeMemberFixture("1001", "unknown"), null);
|
|
assert.strictEqual(findTreeMemberFixture("1002", "101"), null);
|
|
assert.deepStrictEqual(listTreeMemberFixtures("1002"), []);
|
|
for (const id of ids) assert.strictEqual(findTreeMemberFixture("1001", id)?.id, id);
|
|
|
|
const firstList = listTreeMemberFixtures("1001");
|
|
firstList[0].name = "changed";
|
|
firstList[0].relatives[0].relation = "changed";
|
|
assert.notStrictEqual(findTreeMemberFixture("1001", "101").name, "changed");
|
|
assert.notStrictEqual(findTreeMemberFixture("1001", "101").relatives[0].relation, "changed");
|
|
const firstMember = findTreeMemberFixture("1001", "101");
|
|
firstMember.name = "changed again";
|
|
firstMember.relatives[0].relation = "changed again";
|
|
assert.notStrictEqual(findTreeMemberFixture("1001", "101").name, "changed again");
|
|
assert.notStrictEqual(
|
|
findTreeMemberFixture("1001", "101").relatives[0].relation,
|
|
"changed again",
|
|
);
|
|
|
|
const restrictedPresentation = findTreeMemberPresentationFixture("1001", "102");
|
|
assert.deepStrictEqual(
|
|
Object.keys(restrictedPresentation).sort(),
|
|
["appUserId", "genealogyId", "generation", "id", "name", "relation", "status"],
|
|
"restricted member presentation must not carry private profile fields",
|
|
);
|
|
assert.strictEqual(restrictedPresentation.status, "privacy");
|
|
assert.strictEqual(findTreeMemberPresentationFixture("1001", "101").summary.length > 0, true);
|
|
assert.deepStrictEqual(listTreeMemberPresentationFixtures("1002"), []);
|
|
const restrictedListMember = listTreeMemberPresentationFixtures("1001").find(
|
|
(member) => member.id === "103",
|
|
);
|
|
assert.strictEqual(Object.prototype.hasOwnProperty.call(restrictedListMember, "note"), false);
|
|
|
|
globalThis.__treeFixtureOwner = await import(moduleUrl);
|
|
const apiSource = fs
|
|
.readFileSync(path.join(__dirname, "../utils/api.js"), "utf8")
|
|
.replace(
|
|
/^import \{[\s\S]*?\} from '@\/data\/mock\.js'\r?\n/,
|
|
"const { currentUser, genealogies, publicGenealogies, treeMembers, familyContent, notifications, joinApplications, listFamilyFeedFixtures, listFamilyArticleFixtures, listFamilyAlbumFixtures } = globalThis.__treeFixtureOwner;\n",
|
|
)
|
|
.replace(
|
|
/^import \{ hasRemoteConfig[^\n]+\r?\n/m,
|
|
"const hasRemoteConfig = () => false; const resolveRuntimeMode = () => 'mock'; const runtimeConfig = { baseUrl: '', clientId: 'test', tenantId: 'test' };\n",
|
|
)
|
|
.replace(
|
|
/^import \{ AUTH_TAC_SCENE, assertSmsCode \}[^\n]+\r?\n/m,
|
|
"const AUTH_TAC_SCENE = {}; const assertSmsCode = (value) => value;\n",
|
|
)
|
|
.replace(
|
|
/^import \{ GENEALOGY_ACCESS_PRESET, fromApiGenealogyAccess \}[^\n]+\r?\n/m,
|
|
"const GENEALOGY_ACCESS_PRESET = { MEMBER_ONLY: 'MEMBER_ONLY' }; const fromApiGenealogyAccess = () => GENEALOGY_ACCESS_PRESET.MEMBER_ONLY;\n",
|
|
)
|
|
.replace(
|
|
/^import \{ session \}[^\n]+\r?\n/m,
|
|
"const session = { getToken: () => '', saveToken: () => {} };\n",
|
|
);
|
|
const apiUrl = `data:text/javascript;base64,${Buffer.from(apiSource).toString("base64")}`;
|
|
const { appApi } = await import(apiUrl);
|
|
const lengthBeforeCreate = treeMembers.length;
|
|
await assert.rejects(
|
|
appApi.createPerson("1001", { name: "汤新", generation: 15, fatherId: "104" }),
|
|
(error) => error?.code === "WRITE_UNAVAILABLE" && /不会保存/.test(error.message),
|
|
);
|
|
assert.strictEqual(
|
|
treeMembers.length,
|
|
lengthBeforeCreate,
|
|
"local createPerson must reject without mutating the shared fixture owner",
|
|
);
|
|
|
|
process.stdout.write("TREE-MEMBER-FIXTURE-RUNTIME-SMOKE PASS\n");
|
|
};
|
|
|
|
run().catch((error) => {
|
|
process.stderr.write(`${error.stack || error.message}\n`);
|
|
process.exit(1);
|
|
});
|