fix(preview+pipeline): 资产键编码归一——7 张全角名图片预览 404 修复;证据文档叙述修正
spec 复审发现(浏览器可见缺陷):manifest 资产行 exact_key 有 7 例为裸形 (文件名含全角括号/顿号/间隔号)——html/redirect 键源数据侧已编码、asset 键来自文件名本体是裸形;浏览器对裸非 ASCII 一律百分号编码后发请求,预览 按请求字节逐字匹配 → 裸键永不命中 → 两页 3 张图破图(dist 文件在盘; 生产面 nginx 先解码不受影响)。修补 pipeline.toManifestRoute:exact_key 归一为 URL 线形(可打印 ASCII 原样、其余 UTF-8 百分号编码大写 hex), target_file 仍盘上裸名、DB/渲染快照域不变。 回归三层:build-pipeline 注入裸全角资产行(红验证);preview-server 2c 非 ASCII redirect → Location → 资产终端 200;e2e 断言 ④c(7 条全查 跟随到终端 200)+ ⑦c(manifest 2772 键全 ASCII 不变量)。spec 复审复跑 (新库全链 job e059bf41)23/23 PASS,浏览器实测 3 张原破图全部加载。 证据文档:§5.2 失败计数修正(5×500+2×mojibake=7 例)、§2 ①c 措辞对齐 断言实义(抽前 3 标题)、§5.5 修补记录、计时/产物/job 面更新至复跑值。 验证:vitest 382 + 主题 26 全绿(408);typecheck 过;lint 裸跑零告警。 Co-Authored-By: Claude Code <noreply@anthropic.com>
This commit is contained in:
@@ -193,9 +193,26 @@ describe('预览服务器(真实构建产物 + 手构 manifest 扩展)', ()
|
||||
indexable: false,
|
||||
location: '/wp-content/uploads/2024/10/HDPE-carat-pipe-(1)·.jpg',
|
||||
}
|
||||
// Task 8 spec 复审修补回归:非 ASCII 文件名资产——盘上/dist 是裸形文件名(文件系统
|
||||
// 域不解码),manifest exact_key 须是编码线形(管线生成侧归一——浏览器对裸非 ASCII
|
||||
// 一律百分号编码后发请求)。与上一行 redirect 构成真实链:跟随 Location 的编码形
|
||||
// 请求必须命中本资产行(2c 断言全链 200)。
|
||||
const nonAsciiAssetFile = 'wp-content/uploads/2024/10/HDPE-carat-pipe-(1)·.jpg'
|
||||
await mkdir(dirname(join(distDir, nonAsciiAssetFile)), { recursive: true })
|
||||
await writeFile(join(distDir, nonAsciiAssetFile), 'nonascii-asset-body\n', 'utf8')
|
||||
const nonAsciiAssetRow: RouteManifestRoute = {
|
||||
exact_key: '/wp-content/uploads/2024/10/HDPE-carat-pipe-%EF%BC%881%EF%BC%89%C2%B7.jpg',
|
||||
query_policy: 'ignore',
|
||||
query_key: null,
|
||||
kind: 'asset',
|
||||
target_file: `/${nonAsciiAssetFile}`,
|
||||
status: 200,
|
||||
content_type: 'image/jpeg',
|
||||
indexable: false,
|
||||
}
|
||||
const extended: RouteManifest = {
|
||||
...manifest!,
|
||||
routes: [...manifest!.routes, ...probeRows, goneRow, nonAsciiRedirectRow],
|
||||
routes: [...manifest!.routes, ...probeRows, goneRow, nonAsciiRedirectRow, nonAsciiAssetRow],
|
||||
}
|
||||
|
||||
preview = createPreviewServer({ distDir, routeManifest: extended, host: '127.0.0.1', port: 0 })
|
||||
@@ -255,6 +272,24 @@ describe('预览服务器(真实构建产物 + 手构 manifest 扩展)', ()
|
||||
expect(text).toContain('/wp-content/uploads/2024/10/HDPE-carat-pipe-(1)·.jpg')
|
||||
})
|
||||
|
||||
it('2c. 非 ASCII redirect → Location 编码形 → 资产终端 200(spec 复审修补回归:exact_key 编码线形命中)', async () => {
|
||||
const res = await fetch(new URL('/nonascii-redirect/', `http://127.0.0.1:${port}/`), {
|
||||
redirect: 'manual',
|
||||
})
|
||||
expect(res.status).toBe(301)
|
||||
const location = res.headers.get('location')
|
||||
expect(location).toBe('/wp-content/uploads/2024/10/HDPE-carat-pipe-%EF%BC%881%EF%BC%89%C2%B7.jpg')
|
||||
// 跟随 Location(浏览器/重定向客户端的真实路径——发编码形请求)→ 命中资产行 → 200
|
||||
const terminal = await get(location!)
|
||||
expect(terminal.status).toBe(200)
|
||||
expect(terminal.headers.get('content-type')).toBe('image/jpeg')
|
||||
expect(terminal.bytes).toStrictEqual(Buffer.from('nonascii-asset-body\n', 'utf8'))
|
||||
// 裸形直接请求(未编码客户端形态):URL 解析器编码后同键命中——不可 404
|
||||
const rawForm = await get('/wp-content/uploads/2024/10/HDPE-carat-pipe-(1)·.jpg')
|
||||
expect(rawForm.status).toBe(200)
|
||||
expect(rawForm.bytes).toStrictEqual(Buffer.from('nonascii-asset-body\n', 'utf8'))
|
||||
})
|
||||
|
||||
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)
|
||||
|
||||
Reference in New Issue
Block a user