待测试

This commit is contained in:
rain
2026-07-27 17:17:31 +08:00
parent 1eae3bbef4
commit 04287e6777
35 changed files with 4238 additions and 22750 deletions
+1 -1
View File
@@ -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',
+42 -8
View File
@@ -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;
+2 -2
View File
@@ -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 -11
View File
@@ -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");
};
+2
View File
@@ -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;
+2 -2
View File
@@ -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 @(
File diff suppressed because it is too large Load Diff
+74 -4
View File
@@ -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" }
+15 -23
View File
@@ -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);
});
+69
View File
@@ -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);
});
+13 -5
View File
@@ -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 -1
View File
@@ -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' }
+27 -3
View File
@@ -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,
+97
View File
@@ -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: {