待测试
This commit is contained in:
@@ -17,7 +17,7 @@ foreach ($expected in @(
|
||||
'v-model="form.authorName"',
|
||||
'v-model="form.sortOrder"',
|
||||
'pickAndUploadImage',
|
||||
'toConsumerOssId',
|
||||
'coverOssId.value = receipt.ossId',
|
||||
'coverOssId: coverOssId.value',
|
||||
'class="required-mark"',
|
||||
'editor-control--unavailable',
|
||||
|
||||
@@ -34,22 +34,49 @@ const run = async () => {
|
||||
`;
|
||||
|
||||
const requests = [];
|
||||
const feedResponse = {
|
||||
feedId: "9001",
|
||||
genealogyId: "1001",
|
||||
publisherNickName: "家谱用户",
|
||||
feedType: "photo",
|
||||
feedContent: "family memory",
|
||||
mediaOssIds: "2060000000000000001,2060000000000000002",
|
||||
likedByMe: false,
|
||||
likeCount: 0,
|
||||
commentCount: 0,
|
||||
pinned: "0",
|
||||
createTime: "2026-07-27T10:00:00+08:00",
|
||||
};
|
||||
let response = { statusCode: 200, data: { code: 200, data: feedResponse } };
|
||||
globalThis.uni = {
|
||||
request(options) {
|
||||
requests.push(options);
|
||||
queueMicrotask(() => options.success({ statusCode: 200, data: { code: 200, data: {} } }));
|
||||
queueMicrotask(() => options.success(response));
|
||||
return { abort() {} };
|
||||
},
|
||||
};
|
||||
const { appApi } = await import(toDataModuleUrl(`${prelude}\n${moduleBody}`));
|
||||
|
||||
await appApi.createFeed("1001", {
|
||||
const createdFeed = await appApi.createFeed("1001", {
|
||||
feedType: " photo ",
|
||||
feedContent: " family memory ",
|
||||
mediaOssIds: "2060000000000000001,2060000000000000002",
|
||||
sortOrder: "0",
|
||||
status: " 0 ",
|
||||
});
|
||||
assert.deepStrictEqual(createdFeed, {
|
||||
id: "9001",
|
||||
genealogyId: "1001",
|
||||
content: "family memory",
|
||||
publisher: "家谱用户",
|
||||
type: "photo",
|
||||
time: "2026-07-27T10:00:00+08:00",
|
||||
mediaOssIds: "2060000000000000001,2060000000000000002",
|
||||
likeCount: 0,
|
||||
commentCount: 0,
|
||||
likedByMe: false,
|
||||
pinned: "0",
|
||||
});
|
||||
assert.deepStrictEqual(requests.at(-1).data, {
|
||||
feedType: "photo",
|
||||
feedContent: "family memory",
|
||||
@@ -59,6 +86,7 @@ const run = async () => {
|
||||
});
|
||||
assert.strictEqual(requests.at(-1).url, "https://backend-api.ddxcjp.cn/genealogy/app/genealogies/1001/feeds");
|
||||
|
||||
response = { statusCode: 200, data: { code: 200, data: { ...feedResponse, feedContent: "only required" } } };
|
||||
await appApi.createFeed("1001", { feedContent: "only required" });
|
||||
assert.deepStrictEqual(requests.at(-1).data, { feedContent: "only required" });
|
||||
await assert.rejects(
|
||||
@@ -66,11 +94,17 @@ const run = async () => {
|
||||
/mediaOssIds/,
|
||||
);
|
||||
|
||||
const updatedFeed = await appApi.updateFeed("1001", "9001", { feedContent: "family memory" });
|
||||
assert.strictEqual(updatedFeed.id, "9001");
|
||||
assert.strictEqual(requests.at(-1).method, "PUT");
|
||||
|
||||
response = { statusCode: 200, data: { code: 200, data: {} } };
|
||||
|
||||
await appApi.createArticle("1001", {
|
||||
categoryId: "900040001",
|
||||
articleTitle: " title ",
|
||||
articleSummary: " summary ",
|
||||
coverOssId: 900001,
|
||||
coverOssId: "9007199254740993",
|
||||
articleContent: " body ",
|
||||
authorName: " author ",
|
||||
sortOrder: "1",
|
||||
@@ -80,7 +114,7 @@ const run = async () => {
|
||||
categoryId: 900040001,
|
||||
articleTitle: "title",
|
||||
articleSummary: "summary",
|
||||
coverOssId: 900001,
|
||||
coverOssId: "9007199254740993",
|
||||
articleContent: "body",
|
||||
authorName: "author",
|
||||
sortOrder: 1,
|
||||
@@ -91,21 +125,21 @@ const run = async () => {
|
||||
await appApi.createAlbum("1001", {
|
||||
albumName: " old photos ",
|
||||
albumDesc: " family album ",
|
||||
coverOssId: 900001,
|
||||
coverOssId: "9007199254740993",
|
||||
sortOrder: "1",
|
||||
status: " 0 ",
|
||||
});
|
||||
assert.deepStrictEqual(requests.at(-1).data, {
|
||||
albumName: "old photos",
|
||||
albumDesc: "family album",
|
||||
coverOssId: 900001,
|
||||
coverOssId: "9007199254740993",
|
||||
sortOrder: 1,
|
||||
status: "0",
|
||||
});
|
||||
assert.strictEqual(requests.at(-1).url, "https://backend-api.ddxcjp.cn/genealogy/app/genealogies/1001/albums");
|
||||
await assert.rejects(
|
||||
appApi.createAlbum("1001", { albumName: "bad id", coverOssId: "9007199254740992" }),
|
||||
/coverOssId/,
|
||||
appApi.createAlbum("1001", { albumName: "bad id", coverOssId: 900001 }),
|
||||
/OSS ID 字符串/,
|
||||
);
|
||||
|
||||
delete globalThis.uni;
|
||||
|
||||
@@ -14,13 +14,13 @@ foreach ($token in @('form.feedType', 'mediaReceipts', 'mediaOssIds', 'form.sort
|
||||
}
|
||||
|
||||
$f06 = Read-Utf8 'pages/family/f06-article-editor.vue'
|
||||
foreach ($token in @('form.articleSummary', 'coverOssId', 'form.authorName', 'form.sortOrder', 'pickAndUploadImage', 'toConsumerOssId', 'class="required-mark"')) {
|
||||
foreach ($token in @('form.articleSummary', 'coverOssId', 'form.authorName', 'form.sortOrder', 'pickAndUploadImage', 'coverOssId.value = receipt.ossId', 'class="required-mark"')) {
|
||||
Assert-Contains $f06 $token "F06 is missing full article field owner: $token"
|
||||
}
|
||||
if ($f06 -match 'v-model="(?:form\.)?coverOssId"') { throw 'F06 must not expose a raw cover OSS ID input' }
|
||||
|
||||
$f07 = Read-Utf8 'pages/family/f07-album-list.vue'
|
||||
foreach ($token in @('form.albumDesc', 'coverOssId', 'form.sortOrder', 'pickAndUploadImage', 'toConsumerOssId', 'class="required-mark"')) {
|
||||
foreach ($token in @('form.albumDesc', 'coverOssId', 'form.sortOrder', 'pickAndUploadImage', 'coverOssId.value = receipt.ossId', 'class="required-mark"')) {
|
||||
Assert-Contains $f07 $token "F07 is missing full album field owner: $token"
|
||||
}
|
||||
if ($f07 -match 'v-model="(?:form\.)?coverOssId"') { throw 'F07 must not expose a raw cover OSS ID input' }
|
||||
|
||||
@@ -15,8 +15,8 @@ const loadMockModule = async () => {
|
||||
return import(moduleUrl);
|
||||
};
|
||||
|
||||
const assertScopedOwner = ({ list, find, ids, nestedField }) => {
|
||||
assert.deepStrictEqual(list("1001").map((item) => item.id), ids);
|
||||
const assertScopedOwner = ({ list, find, ids, idField = "id", mutationField = "title", nestedField }) => {
|
||||
assert.deepStrictEqual(list("1001").map((item) => item[idField]), ids);
|
||||
assert.deepStrictEqual(list("1002"), [], "另一家谱不得读取 1001 的内容夹具");
|
||||
assert.strictEqual(find("1002", ids[0]), null, "已知 ID 换谱后必须失败关闭");
|
||||
assert.strictEqual(find("1001", "unknown"), null, "未知实体不得回退首条记录");
|
||||
@@ -25,8 +25,8 @@ const assertScopedOwner = ({ list, find, ids, nestedField }) => {
|
||||
|
||||
const listed = list("1001");
|
||||
const original = find("1001", ids[0]);
|
||||
listed[0].title = "被列表调用方修改";
|
||||
assert.notStrictEqual(find("1001", ids[0]).title, "被列表调用方修改");
|
||||
listed[0][mutationField] = "被列表调用方修改";
|
||||
assert.notStrictEqual(find("1001", ids[0])[mutationField], "被列表调用方修改");
|
||||
if (nestedField) {
|
||||
const mutateNested = (item, value) => {
|
||||
if (item[nestedField][0] && typeof item[nestedField][0] === "object") {
|
||||
@@ -68,7 +68,8 @@ const run = async () => {
|
||||
list: listFamilyFeedFixtures,
|
||||
find: findFamilyFeedFixture,
|
||||
ids: ["1", "2"],
|
||||
nestedField: "comments",
|
||||
idField: "feedId",
|
||||
mutationField: "feedContent",
|
||||
});
|
||||
assertScopedOwner({
|
||||
list: listFamilyArticleFixtures,
|
||||
@@ -83,8 +84,11 @@ const run = async () => {
|
||||
nestedField: "photos",
|
||||
});
|
||||
|
||||
for (const item of listFamilyFeedFixtures("1001")) {
|
||||
assert.strictEqual(typeof item.feedId, "string", "feed.feedId 必须保持词法字符串");
|
||||
assert.strictEqual(item.genealogyId, "1001", "feed 必须显式持有家谱身份");
|
||||
}
|
||||
for (const [list, entityName] of [
|
||||
[listFamilyFeedFixtures, "feed"],
|
||||
[listFamilyArticleFixtures, "article"],
|
||||
[listFamilyAlbumFixtures, "album"],
|
||||
]) {
|
||||
@@ -95,9 +99,9 @@ const run = async () => {
|
||||
}
|
||||
|
||||
const firstFeed = findFamilyFeedFixture("1001", "1");
|
||||
firstFeed.comments[0].content = "被详情调用方修改";
|
||||
firstFeed.feedContent = "被详情调用方修改";
|
||||
assert.notStrictEqual(
|
||||
findFamilyFeedFixture("1001", "1").comments[0].content,
|
||||
findFamilyFeedFixture("1001", "1").feedContent,
|
||||
"被详情调用方修改",
|
||||
);
|
||||
|
||||
@@ -113,8 +117,8 @@ const run = async () => {
|
||||
"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",
|
||||
/^import \{ AUTH_VERIFICATION_OPERATION, assertSmsCode \}[^\n]+\r?\n/m,
|
||||
"const AUTH_VERIFICATION_OPERATION = {}; const assertSmsCode = (value) => value;\n",
|
||||
)
|
||||
.replace(
|
||||
/^import \{ GENEALOGY_ACCESS_PRESET, fromApiGenealogyAccess \}[^\n]+\r?\n/m,
|
||||
@@ -135,7 +139,7 @@ const run = async () => {
|
||||
(error) => error?.code === "WRITE_UNAVAILABLE" && /不会保存/.test(error.message),
|
||||
"mock createFeed 必须明确失败,不能污染只读内容 owner",
|
||||
);
|
||||
assert.deepStrictEqual(listFamilyFeedFixtures("1001").map((item) => item.id), ["1", "2"]);
|
||||
assert.deepStrictEqual(listFamilyFeedFixtures("1001").map((item) => item.feedId), ["1", "2"]);
|
||||
|
||||
process.stdout.write("FAMILY-FIXTURE-RUNTIME-SMOKE PASS\n");
|
||||
};
|
||||
|
||||
@@ -48,6 +48,7 @@ const run = async () => {
|
||||
genealogyName: "王氏家谱",
|
||||
surname: "王",
|
||||
regionCode: "110101",
|
||||
coverOssId: "9007199254740993",
|
||||
});
|
||||
|
||||
const request = requests.at(-1);
|
||||
@@ -62,6 +63,7 @@ const run = async () => {
|
||||
genealogyName: "王氏家谱",
|
||||
surname: "王",
|
||||
regionCode: "110101",
|
||||
coverOssId: "9007199254740993",
|
||||
});
|
||||
|
||||
delete globalThis.uni;
|
||||
|
||||
@@ -28,7 +28,7 @@ foreach ($required in @(
|
||||
'v-model="form.addressDetail"',
|
||||
'v-model="form.intro"',
|
||||
'pickAndUploadImage',
|
||||
'toConsumerOssId',
|
||||
'coverOssId.value = receipt.ossId',
|
||||
'coverOssId.value',
|
||||
'GENEALOGY_ACCESS_PRESET_OPTIONS',
|
||||
'toApiGenealogyAccess',
|
||||
@@ -42,7 +42,7 @@ foreach ($required in @(
|
||||
}
|
||||
if ($g03 -match 'v-model="(?:form\.)?regionCode"') { throw 'G03 must not expose a raw region code input' }
|
||||
if ($g03 -match 'v-model="(?:form\.)?coverOssId"') { throw 'G03 must not expose a raw cover OSS ID input' }
|
||||
if ($g03.Contains('AppDialog')) { throw 'G03 region selector must not use the generic dialog card wall' }
|
||||
if ($g03 -match '(?s)<AppDialog[^>]*:visible="regionPickerOpen"') { throw 'G03 region selector must not use the generic dialog card wall' }
|
||||
if ($g03.Contains('继续选择')) { throw 'G03 region selector must not repeat a continuation label on every option' }
|
||||
|
||||
foreach ($forbidden in @(
|
||||
|
||||
+43
-1181
File diff suppressed because it is too large
Load Diff
@@ -14,35 +14,105 @@ foreach ($required in @(
|
||||
"SON: 'children'",
|
||||
'async createRelatedPerson(genealogyId, personId, relationType, payload, requestOptions = {})',
|
||||
'async updatePerson(genealogyId, personId, payload, requestOptions = {})',
|
||||
"'bindingMode',",
|
||||
"['NONE', 'SELF', 'SPECIFIED']",
|
||||
'method: ''PUT''',
|
||||
'requestStrict({'
|
||||
)) {
|
||||
if (-not $api.Contains($required)) { throw "Lineage Apifox write contract missing: $required" }
|
||||
}
|
||||
|
||||
foreach ($forbidden in @('relationType: relationType.value', 'sex: addForm.gender', 'sortOrder:')) {
|
||||
foreach ($forbidden in @('relationType: relationType.value', 'sex: addForm.gender')) {
|
||||
if ($t04.Contains($forbidden)) { throw "T04 must not guess unsupported wire field: $forbidden" }
|
||||
}
|
||||
|
||||
foreach ($source in @($t04, $t05)) {
|
||||
if ($source.Contains('birthClock') -or $source.Contains('deathClock') -or $source.Contains('toDateTime(')) {
|
||||
throw 'Lineage birth/death dates must send the documented yyyy-MM-dd value, without a client-added time component'
|
||||
}
|
||||
}
|
||||
|
||||
foreach ($required in @(
|
||||
'v-model="addForm.personNo"',
|
||||
'v-model="addForm.aliasName"',
|
||||
'v-model="addForm.generation"',
|
||||
'v-model="addForm.generationName"',
|
||||
'v-model="addForm.birthLunar"',
|
||||
'v-model="addForm.birthPlace"',
|
||||
'v-model="addForm.deathLunar"',
|
||||
'v-model="addForm.deathPlace"',
|
||||
'v-model="addForm.burialPlace"',
|
||||
'v-model="addForm.biography"',
|
||||
'v-model="addForm.remark"',
|
||||
'v-model="addForm.sortOrder"',
|
||||
'v-model="addForm.relationName"',
|
||||
'personNo: addForm.personNo',
|
||||
'sex: addForm.sex',
|
||||
'avatarOssId: addForm.avatarOssId',
|
||||
'personStatus: addForm.personStatus',
|
||||
'sortOrder: addForm.sortOrder',
|
||||
'aliasName: addForm.aliasName',
|
||||
'generationName: addForm.generationName',
|
||||
'biography: addForm.biography',
|
||||
'remark: addForm.remark',
|
||||
'...(isFirstMember.value ? { generation: 1 } : {})'
|
||||
'? { generation: 1, relationName: addForm.relationName }',
|
||||
'const sexOptions = Object.freeze([',
|
||||
'const lunarOptions = Object.freeze([',
|
||||
'const personStatusOptions = Object.freeze([',
|
||||
'personOptionLabels',
|
||||
'appApi.getLineagePersonOptions(',
|
||||
'const personOptionsRequestController = createRequestController();',
|
||||
'requestController: personOptionsRequestController',
|
||||
'const bindingModeOptions = Object.freeze([',
|
||||
'bindingMode: addForm.bindingMode',
|
||||
'addForm.bindingMode === "SPECIFIED"',
|
||||
'pickAndUploadImage('
|
||||
)) {
|
||||
if (-not $t04.Contains($required)) { throw "T04 safe optional field missing: $required" }
|
||||
}
|
||||
|
||||
foreach ($required in @(
|
||||
'v-model="editForm.personNo"',
|
||||
'v-model="editForm.generation"',
|
||||
'v-model="editForm.sortOrder"',
|
||||
'v-model="editForm.relationName"',
|
||||
'personNo: editForm.personNo',
|
||||
'sex: editForm.sex',
|
||||
'avatarOssId: editForm.avatarOssId',
|
||||
'personStatus: editForm.personStatus',
|
||||
'sortOrder: editForm.sortOrder',
|
||||
'const sexOptions = Object.freeze([',
|
||||
'const lunarOptions = Object.freeze([',
|
||||
'const personStatusOptions = Object.freeze([',
|
||||
'personOptionLabels',
|
||||
'appApi.getLineagePersonOptions(',
|
||||
'const personOptionsRequestController = createRequestController();',
|
||||
'requestController: personOptionsRequestController',
|
||||
'const bindingModeOptions = Object.freeze([',
|
||||
'bindingMode: editForm.bindingMode',
|
||||
'editForm.bindingMode === "SPECIFIED"',
|
||||
'pickAndUploadImage('
|
||||
)) {
|
||||
if (-not $t05.Contains($required)) { throw "T05 LineagePersonBody field missing: $required" }
|
||||
}
|
||||
|
||||
foreach ($forbidden in @(
|
||||
'v-model="addForm.appUserId"',
|
||||
'v-model="addForm.fatherId"',
|
||||
'v-model="addForm.motherId"',
|
||||
'v-model="addForm.avatarOssId"',
|
||||
'v-model="editForm.appUserId"',
|
||||
'v-model="editForm.fatherId"',
|
||||
'v-model="editForm.motherId"',
|
||||
'v-model="editForm.avatarOssId"'
|
||||
)) {
|
||||
if ($t04.Contains($forbidden) -or $t05.Contains($forbidden)) { throw "Lineage ID or OSS field must not be a raw input: $forbidden" }
|
||||
}
|
||||
|
||||
foreach ($source in @($t04, $t05)) {
|
||||
if ($source.Contains('getProfile({ requestController: profileRequestController })')) {
|
||||
throw 'Lineage identity binding must not fetch and submit the current appUserId for NONE or SELF'
|
||||
}
|
||||
}
|
||||
|
||||
foreach ($required in @('appApi.createRelatedPerson(', 'appApi.createPerson(', 'appApi.updatePerson(', 'failedAction.value = "save"')) {
|
||||
$source = if ($required -eq 'appApi.updatePerson(' -or $required -eq 'failedAction.value = "save"') { $t05 } else { $t04 }
|
||||
if (-not $source.Contains($required)) { throw "Lineage write page missing: $required" }
|
||||
|
||||
@@ -2,33 +2,25 @@ $ErrorActionPreference = 'Stop'
|
||||
$root = Split-Path -Parent $PSScriptRoot
|
||||
$list = Get-Content -LiteralPath (Join-Path $root 'pages/notification/n01-message-center.vue') -Raw -Encoding UTF8
|
||||
$detail = Get-Content -LiteralPath (Join-Path $root 'pages/notification/n02-message-detail.vue') -Raw -Encoding UTF8
|
||||
foreach ($token in @('listNotificationFixtures', 'unreadCount', 'openNotice', ':action="unreadCount > 0')) {
|
||||
if (-not $list.Contains($token)) { throw "N01-N02 flow missing: $token" }
|
||||
$api = Get-Content -LiteralPath (Join-Path $root 'utils/api.js') -Raw -Encoding UTF8
|
||||
|
||||
foreach ($token in @('appApi.getNotifications', 'notifications.length', 'createRequestController', 'isRequestCancelled')) {
|
||||
if (-not $list.Contains($token)) { throw "N01 notification list contract missing: $token" }
|
||||
}
|
||||
if ($list -notmatch '(?s)openPage\(\s*"N02",\s*\{ id: String\(item\.id\) \},\s*"N01",?\s*\)') {
|
||||
throw 'N01 must open N02 through the registered gateway with only lexical id and N01 source'
|
||||
foreach ($token in @('appApi.getNotificationDetail', 'notificationId.value', 'createRequestController', 'onLoad((options)', 'controller.abort()', 'returnTo("N01")')) {
|
||||
if (-not $detail.Contains($token)) { throw "N02 notification detail contract missing: $token" }
|
||||
}
|
||||
foreach ($token in @('findNotificationFixture', 'noticeDetail.unread', 'markAsRead', 'getGenealogyFixtureAccess', 'openNoticeTarget as navigateNoticeTarget', 'noticeDetail.value.targetType', 'noticeDetail.value.targetParams')) {
|
||||
if (-not $detail.Contains($token)) { throw "N02 business detail missing: $token" }
|
||||
foreach ($token in @('normalizeNotificationDetail', 'NOTIFICATION_DETAIL_RESPONSE_INVALID', 'async getNotificationDetail', 'url: `/genealogy/app/notifications/${normalizedNotificationId}`', "method: 'GET'")) {
|
||||
if (-not $api.Contains($token)) { throw "Notification API contract missing: $token" }
|
||||
}
|
||||
if ($detail -notmatch '(?s)const selectedNotice = findNotificationFixture\(noticeId\.value\);.*?if \(.*?!selectedNotice.*?\) \{.*?noticeState\.value = "expired";.*?return;') {
|
||||
throw 'N02 unknown notice identity must fail closed'
|
||||
}
|
||||
if ($detail.Contains('noticeDetail.read') -or -not $detail.Contains('noticeDetail.value.unread = false;')) {
|
||||
throw 'N02 must consume and update the notification owner unread field'
|
||||
}
|
||||
if (-not $detail.Contains('const backToMessages = () => returnTo("N01", {});')) {
|
||||
throw 'N02 must return to N01 through the registered return gateway'
|
||||
}
|
||||
if (-not $detail.Contains('onBackPress((event) => handleBackPress(event, backToMessages));')) {
|
||||
throw 'N02 Android back must reuse the guarded N01 return'
|
||||
}
|
||||
foreach ($source in @($list, $detail)) {
|
||||
foreach ($forbidden in @('detailId', 'const noticeDetails', 'target: "/pages/', 'targetUrl', 'returnUrl', 'uni.navigateTo', 'uni.navigateBack', 'uni.redirectTo', 'uni.reLaunch', '/pages/')) {
|
||||
if ($source.Contains($forbidden)) { throw "N01-N02 retains a duplicate owner or navigation bypass: $forbidden" }
|
||||
|
||||
foreach ($source in @($list, $detail, $api)) {
|
||||
foreach ($forbidden in @('findNotificationFixture', 'noticeDetail.unread', 'markAsRead', 'targetUrl', 'returnUrl')) {
|
||||
if ($source.Contains($forbidden)) { throw "Notification flow retains retired fixture contract: $forbidden" }
|
||||
}
|
||||
}
|
||||
if ($detail -match 'noticeDetail\.value\.target(?![A-Za-z0-9_$])') {
|
||||
throw 'N02 must not consume an executable notification target field'
|
||||
if ($list.Contains('openPage("N02"')) {
|
||||
throw 'N01 must not invent a detail link from the generic notification-list response'
|
||||
}
|
||||
|
||||
Write-Output 'N01-N02-MESSAGE-FLOW-CONTRACT PASS'
|
||||
|
||||
@@ -0,0 +1,87 @@
|
||||
"use strict";
|
||||
|
||||
const assert = require("assert");
|
||||
const fs = require("fs");
|
||||
const path = require("path");
|
||||
|
||||
const toDataModuleUrl = (source) =>
|
||||
`data:text/javascript;base64,${Buffer.from(source).toString("base64")}`;
|
||||
|
||||
const run = async () => {
|
||||
const rawSource = fs.readFileSync(path.join(__dirname, "../utils/api.js"), "utf8");
|
||||
const moduleBody = rawSource.slice(rawSource.indexOf("const successCodes"));
|
||||
const prelude = `
|
||||
const currentUser = {};
|
||||
const genealogies = [];
|
||||
const publicGenealogies = [];
|
||||
const treeMembers = [];
|
||||
const notifications = [];
|
||||
const joinApplications = [];
|
||||
const listFamilyFeedFixtures = () => [];
|
||||
const listFamilyArticleFixtures = () => [];
|
||||
const listFamilyAlbumFixtures = () => [];
|
||||
const listCeremonyFixtures = () => [];
|
||||
const listGrowthRecordFixtures = () => [];
|
||||
const listNotificationFixtures = () => [];
|
||||
const runtimeConfig = { baseUrl: "https://backend-api.ddxcjp.cn", clientId: "client-1", tenantId: "000000" };
|
||||
const hasRemoteConfig = () => true;
|
||||
const resolveRuntimeMode = () => "remote";
|
||||
const AUTH_TAC_SCENE = Object.freeze({});
|
||||
const assertSmsCode = (value) => value;
|
||||
const GENEALOGY_ACCESS_PRESET = Object.freeze({ MEMBER_ONLY: "MEMBER_ONLY" });
|
||||
const fromApiGenealogyAccess = () => null;
|
||||
const session = { getToken: () => "session-1", saveToken() {} };
|
||||
`;
|
||||
|
||||
const requests = [];
|
||||
let response;
|
||||
globalThis.uni = {
|
||||
request(options) {
|
||||
requests.push(options);
|
||||
queueMicrotask(() => options.success(response));
|
||||
return { abort() {} };
|
||||
},
|
||||
};
|
||||
const { appApi } = await import(toDataModuleUrl(`${prelude}\n${moduleBody}`));
|
||||
const detail = {
|
||||
notificationId: "900001",
|
||||
genealogyId: "1001",
|
||||
genealogyNo: "J-1001",
|
||||
genealogyName: "王氏家谱",
|
||||
senderUserId: "101",
|
||||
senderNickName: "管理员",
|
||||
senderPhone: "138****8000",
|
||||
noticeType: "SYSTEM",
|
||||
noticeTitle: "审核完成",
|
||||
noticeContent: "您的申请已审核完成。",
|
||||
bizType: "APPLICATION",
|
||||
bizId: "3001",
|
||||
bizSummary: "入谱申请",
|
||||
publishTime: "2026-07-27T08:00:00+08:00",
|
||||
readStatus: "0",
|
||||
readTime: null,
|
||||
status: "0",
|
||||
remark: "",
|
||||
};
|
||||
|
||||
response = { statusCode: 200, data: { code: 200, data: detail } };
|
||||
const result = await appApi.getNotificationDetail("900001");
|
||||
assert.strictEqual(requests.at(-1).url, "https://backend-api.ddxcjp.cn/genealogy/app/notifications/900001");
|
||||
assert.strictEqual(requests.at(-1).method, "GET");
|
||||
assert.strictEqual(result.notificationId, "900001");
|
||||
assert.strictEqual(result.noticeTitle, "审核完成");
|
||||
assert.strictEqual(result.genealogyId, "1001");
|
||||
assert.strictEqual(result.readTime, "");
|
||||
|
||||
response = { statusCode: 200, data: { code: 200, data: { ...detail, notificationId: "900002" } } };
|
||||
await assert.rejects(appApi.getNotificationDetail("900001"), (error) => error.code === "NOTIFICATION_DETAIL_RESPONSE_INVALID");
|
||||
await assert.rejects(appApi.getNotificationDetail("invalid"), /正整数/);
|
||||
|
||||
delete globalThis.uni;
|
||||
process.stdout.write("NOTIFICATION-DETAIL-API-RUNTIME-SMOKE PASS\n");
|
||||
};
|
||||
|
||||
run().catch((error) => {
|
||||
process.stderr.write(`${error.stack || error.message}\n`);
|
||||
process.exit(1);
|
||||
});
|
||||
@@ -0,0 +1,69 @@
|
||||
"use strict";
|
||||
|
||||
const assert = require("assert");
|
||||
const fs = require("fs");
|
||||
const path = require("path");
|
||||
|
||||
const toDataModuleUrl = (source) =>
|
||||
`data:text/javascript;base64,${Buffer.from(source).toString("base64")}`;
|
||||
|
||||
const run = async () => {
|
||||
const rawSource = fs.readFileSync(path.join(__dirname, "../utils/api.js"), "utf8");
|
||||
const moduleBody = rawSource.slice(rawSource.indexOf("const successCodes"));
|
||||
const prelude = `
|
||||
const currentUser = {};
|
||||
const genealogies = [];
|
||||
const publicGenealogies = [];
|
||||
const treeMembers = [];
|
||||
const notifications = [];
|
||||
const joinApplications = [];
|
||||
const listFamilyFeedFixtures = () => [];
|
||||
const listFamilyArticleFixtures = () => [];
|
||||
const listFamilyAlbumFixtures = () => [];
|
||||
const listCeremonyFixtures = () => [];
|
||||
const listGrowthRecordFixtures = () => [];
|
||||
const listNotificationFixtures = () => [];
|
||||
const runtimeConfig = { baseUrl: "https://backend-api.ddxcjp.cn", clientId: "client-1", tenantId: "000000" };
|
||||
const hasRemoteConfig = () => true;
|
||||
const resolveRuntimeMode = () => "remote";
|
||||
const AUTH_TAC_SCENE = Object.freeze({});
|
||||
const assertSmsCode = (value) => value;
|
||||
const GENEALOGY_ACCESS_PRESET = Object.freeze({ MEMBER_ONLY: "MEMBER_ONLY" });
|
||||
const fromApiGenealogyAccess = () => null;
|
||||
const session = { getToken: () => "session-1", saveToken() {} };
|
||||
`;
|
||||
const requests = [];
|
||||
globalThis.uni = {
|
||||
request(options) {
|
||||
requests.push(options);
|
||||
queueMicrotask(() => options.success({ statusCode: 200, data: { code: 200, data: {} } }));
|
||||
return { abort() {} };
|
||||
},
|
||||
};
|
||||
const { appApi } = await import(toDataModuleUrl(`${prelude}\n${moduleBody}`));
|
||||
const ossId = "9007199254740993";
|
||||
|
||||
await appApi.updateGenealogy("1001", { coverOssId: ossId });
|
||||
assert.strictEqual(requests.at(-1).data.coverOssId, ossId);
|
||||
await appApi.createArticle("1001", { articleTitle: "家风", articleContent: "正文", coverOssId: ossId });
|
||||
assert.strictEqual(requests.at(-1).data.coverOssId, ossId);
|
||||
await appApi.createAlbum("1001", { albumName: "相册", coverOssId: ossId });
|
||||
assert.strictEqual(requests.at(-1).data.coverOssId, ossId);
|
||||
await appApi.createAlbumPhoto("1001", "2001", { ossId });
|
||||
assert.strictEqual(requests.at(-1).data.ossId, ossId);
|
||||
await appApi.createCeremony("1001", { ceremonyType: "祭祖", ceremonyTitle: "清明祭祖", coverOssId: ossId });
|
||||
assert.strictEqual(requests.at(-1).data.coverOssId, ossId);
|
||||
|
||||
await assert.rejects(
|
||||
appApi.createAlbumPhoto("1001", "2001", { ossId: 900001 }),
|
||||
/OSS ID 字符串/,
|
||||
);
|
||||
|
||||
delete globalThis.uni;
|
||||
process.stdout.write("OSS-ID-PAYLOAD-API-RUNTIME-SMOKE PASS\n");
|
||||
};
|
||||
|
||||
run().catch((error) => {
|
||||
process.stderr.write(`${error.stack || error.message}\n`);
|
||||
process.exit(1);
|
||||
});
|
||||
@@ -44,7 +44,7 @@ const run = async () => {
|
||||
};
|
||||
const { appApi } = await import(toDataModuleUrl(`${prelude}\n${moduleBody}`));
|
||||
const profile = {
|
||||
userId: 101,
|
||||
userId: "101",
|
||||
tenantId: "000000",
|
||||
userNo: "U-101",
|
||||
phone: "13800138000",
|
||||
@@ -52,7 +52,7 @@ const run = async () => {
|
||||
realName: "王小明",
|
||||
avatar: 900001,
|
||||
sex: "男",
|
||||
birthday: "1990-01-02T00:00:00",
|
||||
birthday: "1990-01-02",
|
||||
email: "ming@example.com",
|
||||
registerSource: "APP",
|
||||
status: "0",
|
||||
@@ -66,13 +66,21 @@ const run = async () => {
|
||||
assert.strictEqual(profileRequest.timeout, 15000);
|
||||
assert.deepStrictEqual(profileRequest.header, { clientid: "client-1", tenantId: "000000", Authorization: "Bearer session-1" });
|
||||
|
||||
const highPrecisionUserId = "9007199254740993";
|
||||
response = { statusCode: 200, data: { code: 200, data: { ...profile, userId: highPrecisionUserId } } };
|
||||
assert.strictEqual(
|
||||
(await appApi.getProfile()).userId,
|
||||
highPrecisionUserId,
|
||||
"业务用户 ID 必须保留为精确字符串,不能按头像数值转换",
|
||||
);
|
||||
|
||||
response = { statusCode: 200, data: { code: 200, data: profile } };
|
||||
await appApi.updateProfile({
|
||||
nickName: " 新昵称 ",
|
||||
realName: "王小明",
|
||||
avatar: 900001,
|
||||
sex: "女",
|
||||
birthday: "1991-02-03T00:00:00+08:00",
|
||||
birthday: "1991-02-03",
|
||||
email: "new@example.com",
|
||||
});
|
||||
assert.deepStrictEqual(requests.at(-1).data, {
|
||||
@@ -80,12 +88,12 @@ const run = async () => {
|
||||
realName: "王小明",
|
||||
avatar: 900001,
|
||||
sex: "女",
|
||||
birthday: "1991-02-03T00:00:00+08:00",
|
||||
birthday: "1991-02-03",
|
||||
email: "new@example.com",
|
||||
});
|
||||
await assert.rejects(appApi.updateProfile({}), /至少需要一个/);
|
||||
await assert.rejects(appApi.updateProfile({ avatar: Number.MAX_SAFE_INTEGER + 1 }), /安全整数/);
|
||||
await assert.rejects(appApi.updateProfile({ birthday: "1991-02-03" }), /ISO/);
|
||||
await assert.rejects(appApi.updateProfile({ birthday: "1991-02-03T00:00:00+08:00" }), /yyyy-MM-dd/);
|
||||
await assert.rejects(appApi.updateProfile({ email: "bad-email" }), /email/);
|
||||
|
||||
response = { statusCode: 200, data: { code: 200, data: { ...profile, avatar: 0 } } };
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
$ErrorActionPreference = 'Stop'
|
||||
$root = Split-Path -Parent $PSScriptRoot
|
||||
$page = Get-Content -LiteralPath (Join-Path $root 'pages/tree/t01-tree-overview.vue') -Raw -Encoding UTF8
|
||||
foreach ($token in @('const layoutMembers = computed', 'childrenByParent', 'layoutMembers.value', 'GENERATION_GAP', 'MEMBER_GAP')) {
|
||||
foreach ($token in @('const layoutMembers = computed', 'childrenByParent', 'spouseByPersonId', 'parentAnchorX', 'FAMILY_LINK_OFFSET', 'kind: "family"', 'getFamilyPartner', 'spouseRect', 'layoutMembers.value', 'GENERATION_GAP', 'MEMBER_GAP')) {
|
||||
if (-not $page.Contains($token)) { throw "T01 relation layout contract missing: $token" }
|
||||
}
|
||||
if ($page -match 'maxX\s*=.*members\.value.*\.x|maxY\s*=.*members\.value.*\.y') { throw 'T01 canvas must not depend on API pixel coordinates' }
|
||||
|
||||
@@ -58,14 +58,14 @@ const run = async () => {
|
||||
name: "汤文远",
|
||||
generation: 12,
|
||||
generationName: "文",
|
||||
birthDate: "1900-01-02T00:00:00+08:00",
|
||||
birthDate: "1900-01-01T16:00:00.000Z",
|
||||
relationName: "始祖",
|
||||
spouses: [{
|
||||
personId: 1002,
|
||||
name: "李氏",
|
||||
generation: 12,
|
||||
generationName: "文",
|
||||
birthDate: "1905-03-04T00:00:00+08:00",
|
||||
birthDate: "1905-03-03T16:00:00.000Z",
|
||||
}],
|
||||
children: [{
|
||||
personId: 1011,
|
||||
@@ -80,9 +80,20 @@ const run = async () => {
|
||||
generationName: "志",
|
||||
fatherId: 1011,
|
||||
deathDate: "2020-05-06T00:00:00+08:00",
|
||||
}],
|
||||
}],
|
||||
}, {
|
||||
personId: 1031,
|
||||
name: "周明轩",
|
||||
generation: 13,
|
||||
generationName: "正",
|
||||
spouses: [{
|
||||
personId: 1011,
|
||||
name: "汤正明",
|
||||
generation: 13,
|
||||
generationName: "正",
|
||||
}],
|
||||
}],
|
||||
}],
|
||||
},
|
||||
};
|
||||
|
||||
@@ -102,6 +113,7 @@ const run = async () => {
|
||||
{
|
||||
id: "1002",
|
||||
parentId: null,
|
||||
spouseOf: "1001",
|
||||
name: "李氏",
|
||||
relation: "配偶",
|
||||
generation: 12,
|
||||
@@ -132,6 +144,18 @@ const run = async () => {
|
||||
sex: "",
|
||||
personStatus: "",
|
||||
},
|
||||
{
|
||||
id: "1031",
|
||||
parentId: "1001",
|
||||
spouseOf: "1011",
|
||||
name: "周明轩",
|
||||
relation: "配偶",
|
||||
generation: 13,
|
||||
branch: "正字辈",
|
||||
years: "生卒待补",
|
||||
sex: "",
|
||||
personStatus: "",
|
||||
},
|
||||
]);
|
||||
assert.strictEqual(
|
||||
requests[0].url,
|
||||
|
||||
@@ -79,16 +79,30 @@ const run = async () => {
|
||||
genealogyId: "900001001",
|
||||
genealogyName: "联调测试家谱",
|
||||
name: "汤文远",
|
||||
appUserId: "",
|
||||
bindingMode: "NONE",
|
||||
personNo: "",
|
||||
aliasName: "",
|
||||
generation: 12,
|
||||
generationName: "文",
|
||||
avatarOssId: "",
|
||||
relation: "家谱成员",
|
||||
branch: "文字辈",
|
||||
sex: "MALE",
|
||||
birthDate: "1900-01-02",
|
||||
birthLunar: "",
|
||||
deathDate: "",
|
||||
deathLunar: "",
|
||||
years: "1900-01-02—",
|
||||
birthplace: "祖居地",
|
||||
deathPlace: "",
|
||||
burialPlace: "",
|
||||
spouseNames: "",
|
||||
personStatus: "ALIVE",
|
||||
biography: "生平资料",
|
||||
remark: "",
|
||||
relationName: "",
|
||||
sortOrder: null,
|
||||
status: "normal",
|
||||
relatives: [
|
||||
{ id: "900", name: "汤世德", relation: "父亲" },
|
||||
@@ -103,6 +117,89 @@ const run = async () => {
|
||||
assert.strictEqual(requests[0].header.Authorization, "Bearer session-1");
|
||||
assert.strictEqual(requests[0].timeout, 15000);
|
||||
|
||||
nextResponse.data.data.birthDate = "1900-01-02 00:00:00";
|
||||
const spaceDatePerson = await appApi.getPerson("900001001", "1001");
|
||||
assert.strictEqual(spaceDatePerson.birthDate, "1900-01-02");
|
||||
|
||||
nextResponse = {
|
||||
statusCode: 200,
|
||||
data: { code: 200, data: null },
|
||||
};
|
||||
await appApi.createPerson("900001001", {
|
||||
bindingMode: "SPECIFIED",
|
||||
appUserId: "3001",
|
||||
personNo: "P-001",
|
||||
name: "OSS头像成员",
|
||||
aliasName: "阿汤",
|
||||
sex: "0",
|
||||
generation: 1,
|
||||
generationName: "文",
|
||||
fatherId: "3002",
|
||||
motherId: "3003",
|
||||
avatarOssId: "9007199254740993",
|
||||
birthDate: "1900-01-02",
|
||||
birthLunar: "0",
|
||||
birthPlace: "祖居地",
|
||||
deathDate: "2000-01-02",
|
||||
deathLunar: "1",
|
||||
deathPlace: "故乡",
|
||||
burialPlace: "祖茔",
|
||||
personStatus: "0",
|
||||
biography: "成员简介",
|
||||
remark: "测试备注",
|
||||
sortOrder: "2",
|
||||
relationName: "配偶",
|
||||
});
|
||||
assert.deepStrictEqual(requests.at(-1).data, {
|
||||
bindingMode: "SPECIFIED",
|
||||
appUserId: "3001",
|
||||
personNo: "P-001",
|
||||
name: "OSS头像成员",
|
||||
aliasName: "阿汤",
|
||||
sex: "0",
|
||||
generation: 1,
|
||||
generationName: "文",
|
||||
fatherId: "3002",
|
||||
motherId: "3003",
|
||||
avatarOssId: "9007199254740993",
|
||||
birthDate: "1900-01-02",
|
||||
birthLunar: "0",
|
||||
birthPlace: "祖居地",
|
||||
deathDate: "2000-01-02",
|
||||
deathLunar: "1",
|
||||
deathPlace: "故乡",
|
||||
burialPlace: "祖茔",
|
||||
personStatus: "0",
|
||||
biography: "成员简介",
|
||||
remark: "测试备注",
|
||||
sortOrder: 2,
|
||||
relationName: "配偶",
|
||||
});
|
||||
await assert.rejects(
|
||||
appApi.createPerson("900001001", {
|
||||
bindingMode: "NONE",
|
||||
name: "错误头像成员",
|
||||
generation: 1,
|
||||
avatarOssId: 9007199254740992,
|
||||
}),
|
||||
/OSS ID 字符串/,
|
||||
);
|
||||
await assert.rejects(
|
||||
appApi.createPerson("900001001", {
|
||||
bindingMode: "SELF",
|
||||
appUserId: "3001",
|
||||
name: "错误绑定成员",
|
||||
}),
|
||||
/SELF 身份认领不能提供业务用户标识/,
|
||||
);
|
||||
await assert.rejects(
|
||||
appApi.createPerson("900001001", {
|
||||
bindingMode: "SPECIFIED",
|
||||
name: "缺少指定用户成员",
|
||||
}),
|
||||
/指定业务用户绑定必须提供业务用户标识/,
|
||||
);
|
||||
|
||||
nextResponse = {
|
||||
statusCode: 200,
|
||||
data: {
|
||||
|
||||
Reference in New Issue
Block a user