"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 \}[^\n]+\r?\n/m, "const GENEALOGY_ACCESS_PRESET = { MEMBER_ONLY: '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); });