246 lines
10 KiB
JavaScript
246 lines
10 KiB
JavaScript
const assert = require('node:assert/strict');
|
|
const fs = require('node:fs');
|
|
const path = require('node:path');
|
|
const test = require('node:test');
|
|
|
|
const modulePath = path.join(__dirname, '..', 'public', 'js', 'album-pages.js');
|
|
const AlbumPages = fs.existsSync(modulePath) ? require(modulePath) : {};
|
|
|
|
function fileAccess(name, url) {
|
|
return {
|
|
fileId: '2060000000000000090',
|
|
ossId: '2060000000000000091',
|
|
fileName: name,
|
|
mediaType: 'image/jpeg',
|
|
accessUrl: url
|
|
};
|
|
}
|
|
|
|
function requireFunction(name) {
|
|
assert.equal(typeof AlbumPages[name], 'function', `缺少 AlbumPages.${name}`);
|
|
return AlbumPages[name];
|
|
}
|
|
|
|
test('相册页面只从 URL 读取安全的家谱、相册和照片编号', () => {
|
|
const getCurrentGenealogyId = requireFunction('getCurrentGenealogyId');
|
|
const getCurrentAlbumId = requireFunction('getCurrentAlbumId');
|
|
|
|
assert.equal(getCurrentGenealogyId('?genealogyId=2060000000000000001'), '2060000000000000001');
|
|
assert.equal(getCurrentAlbumId('?albumId=2060000000000000002'), '2060000000000000002');
|
|
assert.equal(getCurrentAlbumId('?albumId=unsafe'), '');
|
|
});
|
|
|
|
test('相册写入只构造 AlbumBody 并固定正常状态', () => {
|
|
const buildAlbumBody = requireFunction('buildAlbumBody');
|
|
|
|
assert.deepEqual(buildAlbumBody({
|
|
albumName: ' 祠堂旧影 ',
|
|
albumDesc: ' 历史照片 ',
|
|
coverOssId: '2060000000000000003',
|
|
sortOrder: '2',
|
|
status: '1',
|
|
albumId: 'must-drop',
|
|
photoCount: 99
|
|
}), {
|
|
albumName: '祠堂旧影',
|
|
albumDesc: '历史照片',
|
|
coverOssId: '2060000000000000003',
|
|
sortOrder: 2,
|
|
status: '0'
|
|
});
|
|
});
|
|
|
|
test('照片写入只构造 AlbumPhotoBody 并转换拍摄时间', () => {
|
|
const buildAlbumPhotoBody = requireFunction('buildAlbumPhotoBody');
|
|
|
|
assert.deepEqual(buildAlbumPhotoBody({
|
|
ossId: '2060000000000000004',
|
|
photoTitle: ' 合影 ',
|
|
photoDesc: ' 清明祭祖 ',
|
|
photographer: ' 宗亲 ',
|
|
shootTime: '2026-07-29T10:30',
|
|
sortOrder: '3',
|
|
status: '1',
|
|
albumId: 'must-drop'
|
|
}), {
|
|
ossId: '2060000000000000004',
|
|
photoTitle: '合影',
|
|
photoDesc: '清明祭祖',
|
|
photographer: '宗亲',
|
|
shootTime: '2026-07-29 10:30:00',
|
|
sortOrder: 3,
|
|
status: '0'
|
|
});
|
|
});
|
|
|
|
test('相册和照片校验上传派生 ID、日期、排序和停用状态', () => {
|
|
const buildAlbumBody = requireFunction('buildAlbumBody');
|
|
const buildAlbumPhotoBody = requireFunction('buildAlbumPhotoBody');
|
|
const validateAlbumBody = requireFunction('validateAlbumBody');
|
|
const validateAlbumPhotoBody = requireFunction('validateAlbumPhotoBody');
|
|
|
|
assert.equal(validateAlbumBody({ albumName: '', status: '0' }), '请填写相册名称');
|
|
assert.equal(
|
|
validateAlbumBody(buildAlbumBody({ albumName: '相册', coverOssId: Number.MAX_SAFE_INTEGER + 1 })),
|
|
'封面文件编号无效,请重新选择文件'
|
|
);
|
|
assert.equal(validateAlbumBody({ albumName: '相册', status: '1' }), '当前 PC 无法重新读取停用相册,暂不开放停用');
|
|
assert.equal(validateAlbumPhotoBody({ ossId: '', status: '0' }), '请选择并上传照片');
|
|
assert.equal(
|
|
validateAlbumPhotoBody(buildAlbumPhotoBody({ ossId: '1', shootTime: '2026-02-30T10:00' })),
|
|
'拍摄时间格式无效'
|
|
);
|
|
assert.equal(
|
|
validateAlbumPhotoBody(buildAlbumPhotoBody({ ossId: '1', sortOrder: '1.5' })),
|
|
'排序值必须是安全整数'
|
|
);
|
|
});
|
|
|
|
test('相册和照片响应使用完整 PC Vo 并拒绝不安全 ID', () => {
|
|
const normalizeAlbum = requireFunction('normalizeAlbum');
|
|
const normalizeAlbumPhoto = requireFunction('normalizeAlbumPhoto');
|
|
const album = normalizeAlbum({
|
|
albumId: '2060000000000000001',
|
|
genealogyId: '2060000000000000002',
|
|
genealogyName: '叶氏家谱',
|
|
albumName: '祠堂旧影',
|
|
albumDesc: '历史照片',
|
|
coverOssId: '2060000000000000003',
|
|
photoCount: 2,
|
|
sortOrder: 1,
|
|
status: '0',
|
|
remark: '旧影'
|
|
});
|
|
const photo = normalizeAlbumPhoto({
|
|
photoId: '2060000000000000004',
|
|
genealogyId: '2060000000000000002',
|
|
albumId: '2060000000000000001',
|
|
albumName: '祠堂旧影',
|
|
photoFile: fileAccess('photo.jpg', 'https://files.example/photo.jpg'),
|
|
photoTitle: '合影',
|
|
photoDesc: '清明祭祖',
|
|
photographer: '宗亲',
|
|
shootTime: '2026-07-29 10:30:00',
|
|
sortOrder: 1,
|
|
status: '0',
|
|
remark: ''
|
|
});
|
|
|
|
assert.equal(album.albumId, '2060000000000000001');
|
|
assert.equal(album.photoCount, 2);
|
|
assert.equal(photo.photoId, '2060000000000000004');
|
|
assert.equal(photo.photoFile.accessUrl, 'https://files.example/photo.jpg');
|
|
assert.equal(normalizeAlbum({
|
|
albumId: Number('2060000000000000001'),
|
|
genealogyId: '2',
|
|
albumName: '相册',
|
|
status: '0'
|
|
}), null);
|
|
});
|
|
|
|
test('相册详情通过列表中的稳定 albumId 重读并匹配', () => {
|
|
const findAlbumById = requireFunction('findAlbumById');
|
|
const albums = [{
|
|
albumId: '1',
|
|
genealogyId: '2',
|
|
albumName: '旧影',
|
|
photoCount: 0,
|
|
status: '0'
|
|
}];
|
|
|
|
assert.equal(findAlbumById(albums, '1').albumName, '旧影');
|
|
assert.equal(findAlbumById(albums, '9'), null);
|
|
assert.equal(findAlbumById({ rows: albums }, '1'), null);
|
|
});
|
|
|
|
test('相册和照片展示转义内容并隐藏 OSS ID 与内部 ID', () => {
|
|
const normalizeAlbum = requireFunction('normalizeAlbum');
|
|
const normalizeAlbumPhoto = requireFunction('normalizeAlbumPhoto');
|
|
const renderAlbumDetail = requireFunction('renderAlbumDetail');
|
|
const renderPhotoList = requireFunction('renderPhotoList');
|
|
const albumHtml = renderAlbumDetail(normalizeAlbum({
|
|
albumId: '1',
|
|
genealogyId: '2',
|
|
albumName: '<img src=x>旧影',
|
|
albumDesc: '<script>alert(1)</script>历史',
|
|
coverOssId: '3',
|
|
photoCount: 1,
|
|
status: '0'
|
|
}));
|
|
const photoHtml = renderPhotoList([normalizeAlbumPhoto({
|
|
photoId: '4',
|
|
genealogyId: '2',
|
|
albumId: '1',
|
|
ossId: '5',
|
|
photoTitle: '<img src=x>合影',
|
|
photoDesc: '祭祖',
|
|
status: '0'
|
|
})]);
|
|
|
|
assert.doesNotMatch(albumHtml + photoHtml, /<script|<img/);
|
|
assert.doesNotMatch(albumHtml + photoHtml, />2<|>3<|>4<|>5</);
|
|
assert.match(albumHtml + photoHtml, /旧影|历史|合影|祭祖/);
|
|
});
|
|
|
|
test('相册页面拆分列表、编辑和照片详情且不允许手填 OSS ID 或状态', () => {
|
|
const projectRoot = path.join(__dirname, '..');
|
|
const listPage = fs.readFileSync(path.join(projectRoot, 'profile-album.html'), 'utf8');
|
|
const editPage = fs.readFileSync(path.join(projectRoot, 'profile-album-edit.html'), 'utf8');
|
|
const detailPage = fs.readFileSync(path.join(projectRoot, 'profile-album-detail.html'), 'utf8');
|
|
|
|
[listPage, editPage, detailPage].forEach((source) => {
|
|
assert.doesNotMatch(source, /data-feature-status="pending"|pending-pages\.js/);
|
|
assert.match(source, /public\/js\/album-pages\.js/);
|
|
});
|
|
assert.match(listPage, /data-album-list/);
|
|
assert.match(editPage, /name="albumName"/);
|
|
assert.match(editPage, /data-album-form[^>]+novalidate/);
|
|
assert.match(editPage, /class="field-required"/);
|
|
assert.match(editPage, /<div class="api-status" data-album-form-status/);
|
|
assert.match(editPage, /name="coverOssId" type="hidden"/);
|
|
assert.match(editPage, /name="status" type="hidden" value="0"/);
|
|
assert.match(detailPage, /data-album-detail/);
|
|
assert.match(detailPage, /data-album-photo-list/);
|
|
assert.match(detailPage, /name="ossId" type="hidden"/);
|
|
assert.match(detailPage, /name="shootTime" type="datetime-local"/);
|
|
assert.match(detailPage, /name="status" type="hidden" value="0"/);
|
|
assert.match(detailPage, /data-album-photo-form novalidate/);
|
|
assert.match(detailPage, /<div class="api-status" data-album-photo-status/);
|
|
assert.doesNotMatch(editPage + detailPage, /type="text"[^>]+(?:coverOssId|ossId)|name="albumId"|name="photoId"/);
|
|
});
|
|
|
|
test('相册列表、详情和表单将加载、空数据、403 与请求失败写入统一状态组件', () => {
|
|
const projectRoot = path.join(__dirname, '..');
|
|
const listPage = fs.readFileSync(path.join(projectRoot, 'profile-album.html'), 'utf8');
|
|
const detailPage = fs.readFileSync(path.join(projectRoot, 'profile-album-detail.html'), 'utf8');
|
|
const source = fs.readFileSync(modulePath, 'utf8');
|
|
|
|
assert.match(listPage, /data-album-list[^>]*>[\s\S]*data-api-state="loading"/);
|
|
assert.match(detailPage, /data-album-detail[^>]*>[\s\S]*data-api-state="loading"/);
|
|
assert.match(detailPage, /data-album-photo-list[^>]*>[\s\S]*data-api-state="loading"/);
|
|
assert.match(source, /root\.ProfileUI && root\.ProfileUI\.setApiState/);
|
|
assert.match(source, /setStatus\('\[data-album-list\]', '正在加载相册…', 'loading'\)/);
|
|
assert.match(source, /setStatus\('\[data-album-list\]', '暂无相册', 'empty'\)/);
|
|
assert.match(source, /setStatus\('\[data-album-photo-list\]', '正在加载照片…', 'loading'\)/);
|
|
assert.match(source, /setStatus\('\[data-album-photo-list\]', '暂无照片', 'empty'\)/);
|
|
assert.match(source, /当前账号无权查看该相册照片。/);
|
|
assert.match(source, /正在读取家谱权限…', 'loading'/);
|
|
assert.match(source, /setStatus\('\[data-album-form-status\]', validation, 'error'\)/);
|
|
assert.match(source, /setStatus\('\[data-album-photo-status\]', validation, 'error'\)/);
|
|
assert.match(source, /getApiStateType\(error\)/);
|
|
});
|
|
|
|
test('相册与照片只接受后端布尔能力字段,并在所有写路径上门控', () => {
|
|
const source = fs.readFileSync(modulePath, 'utf8');
|
|
const canEditAlbums = requireFunction('canEditAlbums');
|
|
|
|
assert.equal(canEditAlbums({ canEditContent: true }), true);
|
|
assert.equal(canEditAlbums({ canManage: true }), true);
|
|
assert.equal(canEditAlbums({ canEditContent: 'true', canManage: false }), false);
|
|
assert.match(source, /canEditContent = canEditAlbums\(detail\)/);
|
|
assert.match(source, /if \(writePending \|\| !canEditContent \|\| !genealogyId \|\| redirectUnauthorized\(api\)\) return;/);
|
|
assert.match(source, /if \(writePending \|\| !canEditContent \|\| !genealogyId \|\| !albumId \|\| redirectUnauthorized\(api\)\) return;/);
|
|
assert.match(source, /if \(writePending \|\| !canEditContent \|\| !genealogyId \|\| !normalizeId\(albumId\) \|\| redirectUnauthorized\(api\)\) return;/);
|
|
assert.match(source, /if \(writePending \|\| !canEditContent \|\| !genealogyId \|\| !albumId \|\| !normalizeId\(photoId\) \|\| redirectUnauthorized\(api\)\) return;/);
|
|
});
|