// 渲染管线安全测试(§13 前端测试组): // 1) sanitize schema 快照:禁 script/iframe/style/事件属性,允许任务列表 checkbox; // 2) 对恶意 Markdown 的冒烟用例:渲染输出无脚本执行面。 import { describe, it, expect } from 'vitest' import { render } from '@testing-library/react' import MarkdownViewer from '../src/components/MarkdownViewer' import { sanitizeSchema } from '../src/lib/sanitize' const tagNames = (sanitizeSchema as { tagNames?: string[] }).tagNames ?? [] describe('sanitize schema 白名单', () => { it('禁止危险标签', () => { for (const dangerous of ['script', 'iframe', 'object', 'embed', 'form', 'style', 'link', 'meta']) { expect(tagNames).not.toContain(dangerous) } }) it('允许任务列表 checkbox(input type=checkbox + checked/disabled)', () => { const attrs = (sanitizeSchema as { attributes: Record }).attributes expect(attrs.input).toBeDefined() }) }) describe('渲染管线对恶意 Markdown 的冒烟', () => { const malicious = [ '', '', '[click](javascript:alert(1))', '', 'x', '
x
', '', ].join('\n\n') it('输出不包含任何脚本/事件/危险协议', () => { const { container } = render() expect(window.__xss).toBeUndefined() expect(container.querySelector('script')).toBeNull() expect(container.querySelector('iframe')).toBeNull() expect(container.querySelectorAll('[onclick], [onerror], [onload]')).toHaveLength(0) container.querySelectorAll('a').forEach((a) => { const href = a.getAttribute('href') ?? '' expect(href.startsWith('javascript:')).toBe(false) }) expect(container.innerHTML.includes('style=')).toBe(false) }) it('任务列表渲染为 disabled checkbox', () => { const { container } = render() const boxes = container.querySelectorAll('input[type="checkbox"]') expect(boxes.length).toBe(2) boxes.forEach((b) => expect(b.hasAttribute('disabled')).toBe(true)) }) it('普通 GFM(表格/删除线)正常渲染', () => { const { container } = render( , ) expect(container.querySelector('table')).not.toBeNull() expect(container.querySelector('del')).not.toBeNull() }) it('不泄漏 react-markdown 的 node prop 到 DOM', () => { const { container } = render() expect(container.innerHTML.includes('node=')).toBe(false) }) }) // 供断言使用 declare global { interface Window { __xss?: number } }