Files
jiapu/tests/feed-pages.test.js
T
fizzleaf fb1743aa2a feat(api): 完善家谱系统API客户端契约
- 实现家谱管理相关方法,包括创建、详情、概览、我的家谱和选项查询
- 添加家谱加入申请功能,支持申请、审核、取消和待审核列表操作
- 集成通知详情获取方法和通知ID安全验证机制
- 完善功德记录、谱文、相册、视频、祭祀活动的完整CRUD操作契约
- 实现家谱成员管理功能,包含成员列表、更新、移除和转让所有者操作
- 优化路径ID验证逻辑,拒绝不安全的数值ID并提供明确错误提示
- 更新测试用例以验证所有新增API方法的路径和请求体白名单机制
2026-07-29 16:57:27 +08:00

270 lines
8.9 KiB
JavaScript

const assert = require('node:assert/strict');
const fs = require('node:fs');
const path = require('node:path');
const test = require('node:test');
const FeedPages = require('../public/js/feed-pages.js');
const rootDir = path.resolve(__dirname, '..');
function read(file) {
return fs.readFileSync(path.join(rootDir, file), 'utf8');
}
test('动态表单只构造最新文档定义的 FamilyFeedBody 字段', () => {
assert.deepEqual(
FeedPages.buildFeedBody({
feedContent: '今天上传一张老照片。',
mediaOssIds: '101,102',
status: '0',
visibility: '2'
}),
{
feedType: 'text',
feedContent: '今天上传一张老照片。',
mediaOssIds: '101,102',
status: '0'
}
);
});
test('动态页只从 URL 读取真实家谱和动态编号', () => {
assert.equal(FeedPages.getCurrentGenealogyId('?genealogyId=900001001'), '900001001');
assert.equal(FeedPages.getCurrentGenealogyId(''), '');
assert.equal(FeedPages.getCurrentFeedId('?feedId=7001'), '7001');
});
test('动态列表要求服务端返回 feedId 和 feedContent', () => {
assert.deepEqual(FeedPages.normalizeFeed({ feedId: 7001, feedContent: '家谱动态' }), {
feedId: '7001',
feedContent: '家谱动态'
});
assert.equal(FeedPages.normalizeFeed({ feedId: 7001, content: '旧字段' }), null);
});
test('评论请求只提交 Apifox 定义的 commentContent 和父评论 ID', () => {
assert.deepEqual(FeedPages.buildCommentBody({
commentContent: '收到',
parentCommentId: '2060000000000000000',
replyUserId: '2060000000000000001'
}), {
commentContent: '收到',
parentCommentId: '2060000000000000000'
});
});
test('评论响应使用 commentContent,并保留已删除评论的占位状态', () => {
assert.deepEqual(FeedPages.normalizeComment({
commentId: '2060000000000000000',
commentContent: '收到',
appUserNickName: '小李',
replyCount: 2,
userDeleted: '0'
}), {
commentId: '2060000000000000000',
commentContent: '收到',
appUserNickName: '小李',
replyCount: 2,
userDeleted: false
});
assert.deepEqual(FeedPages.normalizeComment({
commentId: '2060000000000000001',
commentContent: null,
userDeleted: '1'
}), {
commentId: '2060000000000000001',
commentContent: '该评论已删除',
appUserNickName: undefined,
replyCount: undefined,
userDeleted: true
});
});
test('动态响应保留页面闭环所需的完整 FamilyFeedView 字段和字符串 ID', () => {
assert.deepEqual(FeedPages.normalizeFeed({
feedId: 2060000000000000000n,
genealogyId: '2061000000000000000',
genealogyNo: 'G20260728001',
genealogyName: '叶氏家谱',
publisherUserId: '2062000000000000000',
publisherNickName: '叶子',
publisherStatus: '0',
feedType: 'text',
feedContent: '今日修谱。',
mediaOssIds: '301,302',
likedByMe: true,
likeCount: 8,
commentCount: 3,
pinned: '1',
pinnedTime: '2026-07-28 10:00:00',
sortOrder: 2,
status: '0',
remark: '族长发布',
createTime: '2026-07-28 09:00:00',
updateTime: '2026-07-28 09:30:00'
}), {
feedId: '2060000000000000000',
genealogyId: '2061000000000000000',
genealogyNo: 'G20260728001',
genealogyName: '叶氏家谱',
publisherUserId: '2062000000000000000',
publisherNickName: '叶子',
publisherStatus: '0',
feedType: 'text',
feedContent: '今日修谱。',
mediaOssIds: '301,302',
likedByMe: true,
likeCount: 8,
commentCount: 3,
pinned: '1',
pinnedTime: '2026-07-28 10:00:00',
sortOrder: 2,
status: '0',
remark: '族长发布',
createTime: '2026-07-28 09:00:00',
updateTime: '2026-07-28 09:30:00'
});
});
test('评论响应保留回复、归属、层级和时间字段', () => {
assert.deepEqual(FeedPages.normalizeComment({
commentId: '2060000000000000001',
genealogyId: '2061000000000000000',
feedId: '2060000000000000000',
parentCommentId: '2060000000000000002',
appUserId: '2062000000000000000',
parentAppUserId: '2062000000000000003',
appUserNickName: '叶子',
appUserAvatar: '401',
parentAppUserNickName: '小叶',
commentContent: '收到',
replyCount: 2,
commentLevel: 'reply',
userDeleted: '0',
status: '0',
createTime: '2026-07-28 10:30:00'
}), {
commentId: '2060000000000000001',
genealogyId: '2061000000000000000',
feedId: '2060000000000000000',
parentCommentId: '2060000000000000002',
appUserId: '2062000000000000000',
parentAppUserId: '2062000000000000003',
appUserNickName: '叶子',
appUserAvatar: '401',
parentAppUserNickName: '小叶',
commentContent: '收到',
replyCount: 2,
commentLevel: 'reply',
userDeleted: false,
status: '0',
createTime: '2026-07-28 10:30:00'
});
});
test('动态、评论和回复分页只接受正整数页码并使用固定页面大小', () => {
assert.deepEqual(FeedPages.buildPageQuery('3'), { pageNum: 3, pageSize: 20 });
assert.deepEqual(FeedPages.buildPageQuery('-1'), { pageNum: 1, pageSize: 20 });
assert.deepEqual(FeedPages.buildPageQuery('1.5'), { pageNum: 1, pageSize: 20 });
});
test('动态列表、详情和编辑深链始终同时携带真实家谱和动态 ID', () => {
assert.equal(
FeedPages.buildFeedUrl('detail', '2061000000000000000', '2060000000000000000'),
'profile-feed-detail.html?genealogyId=2061000000000000000&feedId=2060000000000000000'
);
assert.equal(
FeedPages.buildFeedUrl('edit', '2061000000000000000', '2060000000000000000'),
'profile-feed-edit.html?genealogyId=2061000000000000000&feedId=2060000000000000000'
);
assert.equal(
FeedPages.buildFeedUrl('list', '2061000000000000000'),
'profile-feed.html?genealogyId=2061000000000000000'
);
});
test('正常动态保存后进入详情,停用动态保存后返回可读取的列表', () => {
assert.equal(
FeedPages.buildPostSaveUrl('2061000000000000000', '2060000000000000000', '0'),
'profile-feed-detail.html?genealogyId=2061000000000000000&feedId=2060000000000000000'
);
assert.equal(
FeedPages.buildPostSaveUrl('2061000000000000000', '2060000000000000000', '1'),
'profile-feed.html?genealogyId=2061000000000000000'
);
});
test('动态表单省略未填写的可选字段并拒绝契约外状态', () => {
assert.deepEqual(FeedPages.buildFeedBody({
feedContent: '仅发布文字'
}), {
feedType: 'text',
feedContent: '仅发布文字'
});
assert.throws(
() => FeedPages.buildFeedBody({ feedContent: '错误状态', status: 'draft' }),
/状态只能是 0 或 1/
);
});
test('同一动态动作在请求完成前只执行一次', async () => {
let resolveAction;
let calls = 0;
const action = new Promise((resolve) => {
resolveAction = resolve;
});
const first = FeedPages.withActionLock('like:2060', async () => {
calls += 1;
await action;
return 'ok';
});
const second = FeedPages.withActionLock('like:2060', async () => {
calls += 1;
});
assert.equal(first, second);
assert.equal(calls, 1);
resolveAction();
assert.equal(await first, 'ok');
});
test('家谱能力和当前用户决定编辑、管理与本人删除上下文', () => {
assert.deepEqual(
FeedPages.normalizeViewerContext(
{ canEditContent: true, canManage: false },
{ userId: 2062000000000000000n }
),
{
userId: '2062000000000000000',
canEditContent: true,
canManage: false
}
);
});
test('家族圈页面提供真实详情、分页和上传控件,不暴露 OSS ID 或草稿伪语义', () => {
const listPage = read('profile-feed.html');
const editPage = read('profile-feed-edit.html');
const detailPage = read('profile-feed-detail.html');
const script = read('public/js/feed-pages.js');
assert.match(listPage, /data-feed-page-prev/);
assert.match(listPage, /data-feed-page-next/);
assert.match(listPage, /data-feed-create-link[^>]+data-genealogy-context-link|data-genealogy-context-link[^>]+data-feed-create-link/);
assert.match(detailPage, /data-feed-detail-page/);
assert.match(detailPage, /data-feed-detail/);
assert.match(detailPage, /data-feed-detail-comments/);
assert.match(detailPage, /data-feed-list-link[^>]+data-genealogy-context-link|data-genealogy-context-link[^>]+data-feed-list-link/);
assert.doesNotMatch(editPage, /type="text"[^>]+name="mediaOssIds"|name="mediaOssIds"[^>]+type="text"/);
assert.match(editPage, /name="mediaOssIds"[^>]+type="hidden"|type="hidden"[^>]+name="mediaOssIds"/);
assert.match(editPage, /multiple/);
assert.match(editPage, /data-feed-advanced[^>]+hidden/);
assert.match(editPage, /name="status"[^>]+disabled|disabled[^>]+name="status"/);
assert.doesNotMatch(editPage, /草稿|图片 OSS ID/);
assert.match(script, /feedCommentsPage/);
assert.match(script, /feedCommentRepliesPage/);
assert.match(script, /currentProfile/);
assert.match(script, /genealogyDetail/);
assert.match(script, /field\.disabled/);
});