197 lines
8.3 KiB
JavaScript
197 lines
8.3 KiB
JavaScript
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: '<script>alert(1)</script>',
|
|
articleSummary: '<img src=x>',
|
|
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, /<script|<img|secret-oss|secret-remark|coverOssId|sortOrder|status|remark/);
|
|
assert.equal(SiteNewsPages.renderArticleList([]), '');
|
|
});
|
|
|
|
test('站点资讯详情转义正文、保留换行并只开放安全外链', () => {
|
|
const html = SiteNewsPages.renderArticleDetail(articleFixture({
|
|
articleContent: '<script>alert(1)</script>\n第二行'
|
|
}));
|
|
|
|
assert.match(html, /<script>alert\(1\)<\/script><br \/>第二行/);
|
|
assert.match(html, /href="https:\/\/example\.com\/notice"/);
|
|
assert.match(html, /target="_blank"/);
|
|
assert.match(html, /rel="noopener noreferrer"/);
|
|
assert.doesNotMatch(html, /<script|coverOssId|secret-remark/);
|
|
|
|
['javascript:alert(1)', 'data:text/html,test', '/notice', 'notice.html', ''].forEach((value) => {
|
|
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, /<body[^>]*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, /<body[^>]*data-site-article-page/);
|
|
assert.match(detailPage, /<title>资讯详情 - 代代相传<\/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)"/);
|
|
});
|