const assert = require('node:assert/strict'); const fs = require('node:fs'); const path = require('node:path'); const test = require('node:test'); const SiteNewsPages = require('../public/js/site-news-pages.js'); const newsPage = fs.readFileSync(path.resolve(__dirname, '../news.html'), 'utf8'); const detailPage = fs.readFileSync(path.resolve(__dirname, '../article-detail.html'), 'utf8'); const articleId = '2062179707935264769'; function articleFixture(overrides) { return Object.assign({ articleId, articleType: 'notice', articleTitle: '平台公告', articleSummary: '公告摘要', articleContent: '第一行\n第二行', coverOssId: '2062179707935264701', externalUrl: 'https://example.com/notice', publishTime: '2026-07-30 09:00:00', sortOrder: 1, status: '0', remark: 'internal-only' }, overrides); } test('站点文章只保留完整 SiteArticleVo 的安全展示字段和字符串长 ID', () => { assert.deepEqual(SiteNewsPages.normalizeArticle(articleFixture()), { articleId, articleType: 'notice', articleTitle: '平台公告', articleSummary: '公告摘要', articleContent: '第一行\n第二行', externalUrl: 'https://example.com/notice', publishTime: '2026-07-30 09:00:00' }); assert.equal(SiteNewsPages.normalizeArticle(articleFixture({ articleId: Number.MAX_SAFE_INTEGER + 1 })), null); assert.equal(SiteNewsPages.normalizeArticle(articleFixture({ articleId: '0' })), null); }); test('站点文章拒绝 SQL 字典外类型、停用记录和空标题', () => { assert.equal(SiteNewsPages.normalizeArticle(articleFixture({ articleType: 'culture' })), null); assert.equal(SiteNewsPages.normalizeArticle(articleFixture({ status: '1' })), null); assert.equal(SiteNewsPages.normalizeArticle(articleFixture({ articleTitle: ' ' })), null); assert.equal(SiteNewsPages.normalizeArticleType('news'), 'news'); assert.equal(SiteNewsPages.normalizeArticleType('notice'), 'notice'); assert.equal(SiteNewsPages.normalizeArticleType('download'), 'download'); assert.equal(SiteNewsPages.normalizeArticleType('culture'), ''); }); test('站点文章列表只接受直接数组且任一非法元素使整批失败', () => { assert.equal(SiteNewsPages.normalizeArticleList([articleFixture()]).length, 1); assert.deepEqual(SiteNewsPages.normalizeArticleList([]), []); assert.deepEqual(SiteNewsPages.normalizeArticleList({ rows: [articleFixture()] }), []); assert.deepEqual(SiteNewsPages.normalizeArticleList([ articleFixture(), articleFixture({ articleId: '' }) ]), []); }); test('站点资讯列表使用响应 ID 生成详情链接并隐藏内部字段', () => { const html = SiteNewsPages.renderArticleList([articleFixture({ articleTitle: '', articleSummary: '', coverOssId: 'secret-oss', remark: 'secret-remark' })]); assert.match(html, /article-detail\.html\?articleId=2062179707935264769/); assert.match(html, /公告/); assert.match(html, /<script>/); assert.match(html, /<img src=x>/); assert.doesNotMatch(html, / { const html = SiteNewsPages.renderArticleDetail(articleFixture({ articleContent: '\n第二行' })); assert.match(html, /<script>alert\(1\)<\/script>
第二行/); assert.match(html, /href="https:\/\/example\.com\/notice"/); assert.match(html, /target="_blank"/); assert.match(html, /rel="noopener noreferrer"/); assert.doesNotMatch(html, / { const unsafeHtml = SiteNewsPages.renderArticleDetail(articleFixture({ externalUrl: value })); assert.doesNotMatch(unsafeHtml, /data-external-link/); }); }); test('站点文章外链只接受绝对 HTTP 或 HTTPS', () => { assert.equal( SiteNewsPages.normalizeExternalUrl(' https://example.com/notice '), 'https://example.com/notice' ); assert.equal(SiteNewsPages.normalizeExternalUrl('http://example.com'), 'http://example.com/'); assert.equal(SiteNewsPages.normalizeExternalUrl('javascript:alert(1)'), ''); assert.equal(SiteNewsPages.normalizeExternalUrl('/notice'), ''); }); test('资讯列表只提交已确认分类和系统 limit', async () => { const calls = []; const api = { async siteArticles(query) { calls.push(query); return [articleFixture()]; } }; assert.equal((await SiteNewsPages.loadArticles(api, 'notice')).length, 1); assert.equal((await SiteNewsPages.loadArticles(api, '')).length, 1); assert.deepEqual(calls, [ { articleType: 'notice', limit: 100 }, { limit: 100 } ]); await assert.rejects(SiteNewsPages.loadArticles(api, 'culture'), /资讯分类无效/); await assert.rejects( SiteNewsPages.loadArticles({ async siteArticles() { return { rows: [articleFixture()] }; } }, ''), /资讯列表响应无效/ ); }); test('资讯详情只精确匹配 URL 中由列表产生的文章 ID', async () => { const api = { async siteArticles(query) { assert.deepEqual(query, { limit: 100 }); return [ articleFixture(), articleFixture({ articleId: '2062179707935264770', articleTitle: '另一条' }) ]; } }; assert.equal((await SiteNewsPages.loadArticleDetail(api, articleId)).articleTitle, '平台公告'); await assert.rejects( SiteNewsPages.loadArticleDetail(api, '2062179707935264771'), /资讯不存在或已下线/ ); await assert.rejects( SiteNewsPages.loadArticleDetail(api, Number.MAX_SAFE_INTEGER + 1), /资讯编号无效/ ); }); test('详情 URL 只读取安全字符串文章 ID', () => { assert.equal(SiteNewsPages.readArticleId('?articleId=' + articleId), articleId); assert.equal(SiteNewsPages.readArticleId('?articleId=0'), ''); assert.equal(SiteNewsPages.readArticleId('?articleId=unsafe'), ''); assert.equal(SiteNewsPages.readArticleId(''), ''); }); test('资讯列表和详情将加载、空数据、失败及无权限写入统一状态组件', () => { const source = fs.readFileSync(path.resolve(__dirname, '../public/js/site-news-pages.js'), 'utf8'); assert.equal(SiteNewsPages.isForbidden({ status: 403 }), true); assert.equal(SiteNewsPages.isForbidden({ status: 401 }), false); assert.equal(SiteNewsPages.isMissingArticleError(new Error('资讯不存在或已下线')), true); assert.match(source, /root\.ProfileUI && root\.ProfileUI\.setApiState/); assert.match(source, /setSiteState\(list, 'loading', '正在读取资讯…'\)/); assert.match(source, /setSiteState\(detail, 'loading', '正在读取资讯详情…'\)/); assert.match(source, /isForbidden\(error\) \? 'forbidden' : 'error'/); assert.match(source, /isForbidden\(error\) \? 'forbidden' : \(isMissingArticleError\(error\) \? 'empty' : 'error'\)/); }); test('官网资讯列表与详情页加载同一个公开内容 owner 且不暴露业务 ID', () => { assert.match(newsPage, /]*data-site-news-page/); assert.match(newsPage, /data-site-news-list/); assert.match(newsPage, /data-site-news-status[^>]*role="status"/); assert.match(newsPage, /data-api-state="loading"/); assert.match(newsPage, /news\.html\?articleType=news/); assert.match(newsPage, /news\.html\?articleType=notice/); assert.match(newsPage, /news\.html\?articleType=download/); assert.match(newsPage, /src="public\/js\/site-news-pages\.js"/); assert.match(newsPage, /src="public\/js\/profile-common\.js"/); assert.match(detailPage, /]*data-site-article-page/); assert.match(detailPage, /资讯详情 - 代代相传<\/title>/); assert.match(detailPage, /data-site-article-detail/); assert.match(detailPage, /data-site-article-status[^>]*role="status"/); assert.match(detailPage, /data-api-state="loading"/); assert.match(detailPage, /src="public\/js\/profile-common\.js"/); assert.match(detailPage, /src="public\/js\/site-news-pages\.js"/); assert.doesNotMatch(detailPage, /src="public\/js\/article-pages\.js"/); assert.doesNotMatch(detailPage, /href="article-detail\.html"/); assert.doesNotMatch(newsPage + detailPage, /name="(?:articleId|coverOssId)"/); });