fb1743aa2a
- 实现家谱管理相关方法,包括创建、详情、概览、我的家谱和选项查询 - 添加家谱加入申请功能,支持申请、审核、取消和待审核列表操作 - 集成通知详情获取方法和通知ID安全验证机制 - 完善功德记录、谱文、相册、视频、祭祀活动的完整CRUD操作契约 - 实现家谱成员管理功能,包含成员列表、更新、移除和转让所有者操作 - 优化路径ID验证逻辑,拒绝不安全的数值ID并提供明确错误提示 - 更新测试用例以验证所有新增API方法的路径和请求体白名单机制
155 lines
6.4 KiB
JavaScript
155 lines
6.4 KiB
JavaScript
const assert = require('node:assert/strict');
|
|
const fs = require('node:fs');
|
|
const path = require('node:path');
|
|
const test = require('node:test');
|
|
|
|
const VideoPages = require('../public/js/video-pages.js');
|
|
|
|
test('视频页只从 URL 读取真实家谱和视频编号', () => {
|
|
assert.equal(VideoPages.getCurrentGenealogyId('?genealogyId=2060000000000000001&videoId=2060000000000000002'), '2060000000000000001');
|
|
assert.equal(VideoPages.getCurrentVideoId('?genealogyId=2060000000000000001&videoId=2060000000000000002'), '2060000000000000002');
|
|
assert.equal(VideoPages.getCurrentGenealogyId(''), '');
|
|
assert.equal(VideoPages.getCurrentVideoId('?videoId=unsafe'), '');
|
|
});
|
|
|
|
test('视频写入只构造 YAML VideoBody 字段并保持 OSS ID 为字符串', () => {
|
|
assert.deepEqual(VideoPages.buildVideoBody({
|
|
videoTitle: ' 家族活动记录 ',
|
|
videoDesc: ' 清明祭祖活动视频 ',
|
|
coverOssId: '2060000000000000001',
|
|
videoOssId: '2060000000000000002',
|
|
durationSeconds: '181',
|
|
sortOrder: '3',
|
|
status: '0',
|
|
publisherUserId: 'should-not-send',
|
|
viewCount: 99
|
|
}), {
|
|
videoTitle: '家族活动记录',
|
|
videoDesc: '清明祭祖活动视频',
|
|
coverOssId: '2060000000000000001',
|
|
videoOssId: '2060000000000000002',
|
|
durationSeconds: 181,
|
|
sortOrder: 3,
|
|
status: '0'
|
|
});
|
|
});
|
|
|
|
test('视频校验标题、上传文件、时长、排序和状态', () => {
|
|
assert.equal(VideoPages.validateVideoBody({ videoTitle: '', videoOssId: '1' }), '请填写视频标题');
|
|
assert.equal(VideoPages.validateVideoBody({ videoTitle: '标题' }), '请先选择并上传视频文件');
|
|
assert.equal(VideoPages.validateVideoBody({ videoTitle: '标题', videoOssId: '0' }), '视频文件上传结果无效');
|
|
assert.equal(VideoPages.validateVideoBody({ videoTitle: '标题', videoOssId: '1', coverOssId: 'abc' }), '封面上传结果无效');
|
|
assert.equal(VideoPages.validateVideoBody({ videoTitle: '标题', videoOssId: '1', durationSeconds: -1 }), '视频时长不能小于 0');
|
|
assert.equal(VideoPages.validateVideoBody({ videoTitle: '标题', videoOssId: '1', sortOrder: undefined, invalidSortOrder: true }), '排序值必须是安全整数');
|
|
assert.equal(VideoPages.validateVideoBody({ videoTitle: '标题', videoOssId: '1', status: '1' }), '当前 PC 无法重新读取停用视频,暂不开放停用');
|
|
assert.equal(VideoPages.validateVideoBody({ videoTitle: '标题', videoOssId: '1', status: '0' }), '');
|
|
});
|
|
|
|
test('视频响应使用 PC VideoVo 字段并拒绝不安全长 ID', () => {
|
|
assert.deepEqual(VideoPages.normalizeVideo({
|
|
videoId: '2060000000000000001',
|
|
genealogyId: '2060000000000000002',
|
|
genealogyNo: 'G20260729001',
|
|
genealogyName: '叶氏家谱',
|
|
surname: '叶',
|
|
videoTitle: '祭祖活动记录',
|
|
videoDesc: '活动影像',
|
|
coverOssId: '2060000000000000003',
|
|
videoOssId: '2060000000000000004',
|
|
durationSeconds: 180,
|
|
publisherUserId: '2060000000000000005',
|
|
publisherNickName: '叶子',
|
|
publisherPhone: '19100000000',
|
|
publishTime: '2026-07-29 10:00:00',
|
|
viewCount: 12,
|
|
sortOrder: 1,
|
|
status: '0',
|
|
remark: '归档'
|
|
}), {
|
|
videoId: '2060000000000000001',
|
|
genealogyId: '2060000000000000002',
|
|
genealogyNo: 'G20260729001',
|
|
genealogyName: '叶氏家谱',
|
|
surname: '叶',
|
|
videoTitle: '祭祖活动记录',
|
|
videoDesc: '活动影像',
|
|
coverOssId: '2060000000000000003',
|
|
videoOssId: '2060000000000000004',
|
|
durationSeconds: 180,
|
|
publisherUserId: '2060000000000000005',
|
|
publisherNickName: '叶子',
|
|
publisherPhone: '19100000000',
|
|
publishTime: '2026-07-29 10:00:00',
|
|
viewCount: 12,
|
|
sortOrder: 1,
|
|
status: '0',
|
|
remark: '归档'
|
|
});
|
|
assert.equal(VideoPages.normalizeVideo({
|
|
videoId: Number('2060000000000000001'),
|
|
genealogyId: '2',
|
|
videoTitle: '标题',
|
|
videoOssId: '3',
|
|
status: '0'
|
|
}), null);
|
|
});
|
|
|
|
test('视频渲染展示业务信息但不直接暴露 OSS ID 和发布人手机号', () => {
|
|
const video = VideoPages.normalizeVideo({
|
|
videoId: '1',
|
|
genealogyId: '2',
|
|
videoTitle: '<script>alert(1)</script>',
|
|
videoDesc: '活动影像',
|
|
coverOssId: '3',
|
|
videoOssId: '4',
|
|
durationSeconds: 180,
|
|
publisherUserId: '5',
|
|
publisherNickName: '叶子',
|
|
publisherPhone: '19100000000',
|
|
publishTime: '2026-07-29 10:00:00',
|
|
viewCount: 12,
|
|
sortOrder: 1,
|
|
status: '0'
|
|
});
|
|
const html = VideoPages.renderVideoDetail(video, true);
|
|
|
|
assert.doesNotMatch(html, /<script>/);
|
|
assert.match(html, /活动影像/);
|
|
assert.match(html, /叶子/);
|
|
assert.match(html, /已上传/);
|
|
assert.doesNotMatch(html, />3</);
|
|
assert.doesNotMatch(html, />4</);
|
|
assert.doesNotMatch(html, /19100000000/);
|
|
});
|
|
|
|
test('视频文件元数据时长按秒取整且拒绝无效值', () => {
|
|
assert.equal(VideoPages.normalizeDurationSeconds(180.2), 181);
|
|
assert.equal(VideoPages.normalizeDurationSeconds(0), 0);
|
|
assert.equal(VideoPages.normalizeDurationSeconds(-1), undefined);
|
|
assert.equal(VideoPages.normalizeDurationSeconds(Infinity), undefined);
|
|
});
|
|
|
|
test('视频列表和编辑页开放真实 PC 功能且不允许手填 OSS ID', () => {
|
|
const root = path.join(__dirname, '..');
|
|
const listPage = fs.readFileSync(path.join(root, 'profile-video.html'), 'utf8');
|
|
const editPage = fs.readFileSync(path.join(root, 'profile-video-edit.html'), 'utf8');
|
|
const styles = fs.readFileSync(path.join(root, 'public', 'css', 'profile-module.css'), 'utf8');
|
|
|
|
assert.doesNotMatch(listPage, /data-feature-status="pending"/);
|
|
assert.match(listPage, /data-video-list/);
|
|
assert.match(listPage, /data-video-detail/);
|
|
assert.match(listPage, /data-video-management hidden/);
|
|
assert.match(listPage, /src="public\/js\/video-pages\.js"/);
|
|
assert.match(editPage, /data-video-form data-video-management hidden/);
|
|
assert.match(editPage, /name="videoTitle"/);
|
|
assert.match(editPage, /name="videoDesc"/);
|
|
assert.match(editPage, /name="coverOssId" type="hidden"/);
|
|
assert.match(editPage, /name="videoOssId" type="hidden"/);
|
|
assert.match(editPage, /name="durationSeconds"[^>]+readonly/);
|
|
assert.doesNotMatch(editPage, /name="(?:coverOssId|videoOssId)"[^>]*type="text"/);
|
|
assert.match(editPage, /public\/js\/md5\.js/);
|
|
assert.match(editPage, /public\/js\/upload-pages\.js/);
|
|
assert.match(editPage, /public\/js\/video-pages\.js/);
|
|
assert.match(styles, /\[data-video-management\]\[hidden\]\s*\{[^}]*display:\s*none\s*!important/s);
|
|
});
|