"use strict"; const assert = require("assert"); const fs = require("fs"); const path = require("path"); const loadMockModule = 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")}`; return import(moduleUrl); }; const assertScopedOwner = ({ list, find, ids, nestedField }) => { assert.deepStrictEqual(list("1001").map((item) => item.id), ids); assert.deepStrictEqual(list("1002"), [], "另一家谱不得读取 1001 的内容夹具"); assert.strictEqual(find("1002", ids[0]), null, "已知 ID 换谱后必须失败关闭"); assert.strictEqual(find("1001", "unknown"), null, "未知实体不得回退首条记录"); assert.strictEqual(find("", ids[0]), null, "缺少 genealogyId 必须失败关闭"); assert.strictEqual(find("1001", ""), null, "缺少实体 ID 必须失败关闭"); const listed = list("1001"); const original = find("1001", ids[0]); listed[0].title = "被列表调用方修改"; assert.notStrictEqual(find("1001", ids[0]).title, "被列表调用方修改"); if (nestedField) { const mutateNested = (item, value) => { if (item[nestedField][0] && typeof item[nestedField][0] === "object") { item[nestedField][0].__contractMutation = value; } else { item[nestedField][0] = value; } }; mutateNested(listed[0], "被列表调用方修改"); assert(!JSON.stringify(find("1001", ids[0])[nestedField]).includes("被列表调用方修改")); mutateNested(original, "被详情调用方修改"); assert(!JSON.stringify(find("1001", ids[0])[nestedField]).includes("被详情调用方修改")); } }; const run = async () => { const fixtures = await loadMockModule(); const { listFamilyFeedFixtures, findFamilyFeedFixture, listFamilyArticleFixtures, findFamilyArticleFixture, listFamilyAlbumFixtures, findFamilyAlbumFixture, } = fixtures; for (const selector of [ listFamilyFeedFixtures, findFamilyFeedFixture, listFamilyArticleFixtures, findFamilyArticleFixture, listFamilyAlbumFixtures, findFamilyAlbumFixture, ]) { assert.strictEqual(typeof selector, "function", "F 系列只读夹具必须由共享查询函数拥有"); } assertScopedOwner({ list: listFamilyFeedFixtures, find: findFamilyFeedFixture, ids: ["1", "2"], nestedField: "comments", }); assertScopedOwner({ list: listFamilyArticleFixtures, find: findFamilyArticleFixture, ids: ["101", "102", "103"], nestedField: "paragraphs", }); assertScopedOwner({ list: listFamilyAlbumFixtures, find: findFamilyAlbumFixture, ids: ["201", "202", "203"], nestedField: "photos", }); for (const [list, entityName] of [ [listFamilyFeedFixtures, "feed"], [listFamilyArticleFixtures, "article"], [listFamilyAlbumFixtures, "album"], ]) { for (const item of list("1001")) { assert.strictEqual(typeof item.id, "string", `${entityName}.id 必须保持词法字符串`); assert.strictEqual(item.genealogyId, "1001", `${entityName} 必须显式持有家谱身份`); } } const firstFeed = findFamilyFeedFixture("1001", "1"); firstFeed.comments[0].content = "被详情调用方修改"; assert.notStrictEqual( findFamilyFeedFixture("1001", "1").comments[0].content, "被详情调用方修改", ); globalThis.__familyFixtureOwner = fixtures; 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, notifications, joinApplications, listFamilyFeedFixtures, listFamilyArticleFixtures, listFamilyAlbumFixtures, listCeremonyFixtures, listGrowthRecordFixtures } = globalThis.__familyFixtureOwner;\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); assert.deepStrictEqual((await appApi.getFeeds("1001")).map((item) => item.id), ["1", "2"]); assert.deepStrictEqual((await appApi.getArticles("1001")).map((item) => item.id), ["101", "102", "103"]); assert.deepStrictEqual((await appApi.getAlbums("1001")).map((item) => item.id), ["201", "202", "203"]); assert.deepStrictEqual(await appApi.getFeeds("1002"), []); await assert.rejects( appApi.createFeed("1001", { feedContent: "不得写入正式夹具" }), (error) => error?.code === "WRITE_UNAVAILABLE" && /不会保存/.test(error.message), "mock createFeed 必须明确失败,不能污染只读内容 owner", ); assert.deepStrictEqual(listFamilyFeedFixtures("1001").map((item) => item.id), ["1", "2"]); process.stdout.write("FAMILY-FIXTURE-RUNTIME-SMOKE PASS\n"); }; run().catch((error) => { process.stderr.write(`${error.stack || error.message}\n`); process.exit(1); });