test(evidence): 阶段4端到端预览验收
Plan 4 Task 8:真实 hesupipe 全链(register 副本复用 → import → publish → build → preview)临时库端到端验收,断言 21/21 PASS + 浏览器时刻 6 截图 无破版无横向溢出。新增 scripts/e2e-real.mjs(chain/serve 两子命令)与 docs/evidence/phase4-preview.md + phase4-assertions.json + screenshots/。 验收内修补(预览服务器,均有回归测试锁定): - Location 头 ASCII 化:非 ASCII 码位按 UTF-8 百分号编码——原样 setHeader 触发 ERR_INVALID_CHAR 崩 500 或 latin1 mojibake(1746 redirect 全量 校验显影 2+3 例);manifest 原串保持权威,测试 2b 锁定。 - CSP 放行内联样式:Astro 5 scoped 样式内联 <style> 被 default-src 'self' 拦截(浏览器时刻显影,HTTP 级不可见);测试 6 断言同步。 Co-Authored-By: Claude Code <noreply@anthropic.com>
This commit is contained in:
@@ -182,7 +182,21 @@ describe('预览服务器(真实构建产物 + 手构 manifest 扩展)', ()
|
||||
status: 410,
|
||||
indexable: false,
|
||||
}
|
||||
const extended: RouteManifest = { ...manifest!, routes: [...manifest!.routes, ...probeRows, goneRow] }
|
||||
// Task 8 实证形态:redirect location 含非 ASCII(全角括号/间隔号——真实 hesupipe
|
||||
// 1746 redirect 全量校验发现原样发头 ERR_INVALID_CHAR 崩 500 / latin1 乱码)
|
||||
const nonAsciiRedirectRow: RouteManifestRoute = {
|
||||
exact_key: '/nonascii-redirect/',
|
||||
query_policy: 'ignore',
|
||||
query_key: null,
|
||||
kind: 'redirect',
|
||||
status: 301,
|
||||
indexable: false,
|
||||
location: '/wp-content/uploads/2024/10/HDPE-carat-pipe-(1)·.jpg',
|
||||
}
|
||||
const extended: RouteManifest = {
|
||||
...manifest!,
|
||||
routes: [...manifest!.routes, ...probeRows, goneRow, nonAsciiRedirectRow],
|
||||
}
|
||||
|
||||
preview = createPreviewServer({ distDir, routeManifest: extended, host: '127.0.0.1', port: 0 })
|
||||
const addr = await preview.start()
|
||||
@@ -228,6 +242,19 @@ describe('预览服务器(真实构建产物 + 手构 manifest 扩展)', ()
|
||||
expect(res.headers.get('location')).toBe('/wp-content/uploads/2026/10/fixture-a.png')
|
||||
})
|
||||
|
||||
it('2b. redirect location 含非 ASCII:Location 头按 UTF-8 百分号编码发(不崩 500/不乱码);迁移页内嵌原串', async () => {
|
||||
const res = await fetch(new URL('/nonascii-redirect/', `http://127.0.0.1:${port}/`), {
|
||||
redirect: 'manual',
|
||||
})
|
||||
expect(res.status).toBe(301)
|
||||
// 全角( U+FF08 → %EF%BC%88 / )U+FF09 → %EF%BC%89 / · U+00B7 → %C2%B7
|
||||
expect(res.headers.get('location')).toBe(
|
||||
'/wp-content/uploads/2024/10/HDPE-carat-pipe-%EF%BC%881%EF%BC%89%C2%B7.jpg',
|
||||
)
|
||||
const text = await res.text()
|
||||
expect(text).toContain('/wp-content/uploads/2024/10/HDPE-carat-pipe-(1)·.jpg')
|
||||
})
|
||||
|
||||
it('3. asset 行 200 + MIME:png 真实行 content_type;jpg/webp/pdf 扩展名映射;未知扩展 octet-stream', async () => {
|
||||
const png = await get('/wp-content/uploads/2026/10/fixture-a.png')
|
||||
expect(png.status).toBe(200)
|
||||
@@ -266,13 +293,17 @@ describe('预览服务器(真实构建产物 + 手构 manifest 扩展)', ()
|
||||
expect(res.text.length).toBeLessThan(1024)
|
||||
})
|
||||
|
||||
it('6. 全响应 X-Robots-Tag: noindex, nofollow;HTML 响应 CSP default-src self(asset 无 CSP)', async () => {
|
||||
it('6. 全响应 X-Robots-Tag: noindex, nofollow;HTML 响应 CSP(style-src 放开内联——Astro scoped 样式;asset 无 CSP)', async () => {
|
||||
for (const p of ['/', '/definitely-not-a-route/', '/gone-page/', '/wp-content/uploads/2026/10/fixture-a.png']) {
|
||||
const res = await get(p)
|
||||
expect(res.headers.get('x-robots-tag'), p).toBe('noindex, nofollow')
|
||||
}
|
||||
expect((await get('/')).headers.get('content-security-policy')).toBe("default-src 'self'")
|
||||
expect((await get('/definitely-not-a-route/')).headers.get('content-security-policy')).toBe("default-src 'self'")
|
||||
expect((await get('/')).headers.get('content-security-policy')).toBe(
|
||||
"default-src 'self'; style-src 'self' 'unsafe-inline'",
|
||||
)
|
||||
expect((await get('/definitely-not-a-route/')).headers.get('content-security-policy')).toBe(
|
||||
"default-src 'self'; style-src 'self' 'unsafe-inline'",
|
||||
)
|
||||
expect((await get('/wp-content/uploads/2026/10/fixture-a.png')).headers.get('content-security-policy')).toBeNull()
|
||||
})
|
||||
|
||||
|
||||
Reference in New Issue
Block a user