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: '旧影', albumDesc: '历史', coverOssId: '3', photoCount: 1, status: '0' })); const photoHtml = renderPhotoList([normalizeAlbumPhoto({ photoId: '4', genealogyId: '2', albumId: '1', ossId: '5', photoTitle: '合影', photoDesc: '祭祖', status: '0' })]); assert.doesNotMatch(albumHtml + photoHtml, /2<|>3<|>4<|>5 { 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, /
]+(?: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;/); });