Document and verify the high-quality preview HTTP contract
studio CI and deployment / verify-and-deploy (pull_request) Successful in 1m8s
waterfall-wiki CI and deployment / verify-and-deploy (pull_request) Successful in 18m56s

This commit is contained in:
2026-10-29 00:09:22 +08:00
parent 0360a5165b
commit 4b24745eb9
2 changed files with 3 additions and 1 deletions
@@ -68,7 +68,7 @@ export const apiDocuments:Record<string,readonly string[]>={
"/api/preview": [ "/api/preview": [
"轻量图片预览", "轻量图片预览",
"GET, HEAD", "GET, HEAD",
"查询参数:source(必填,本站静态栅格图片路径,或 /api/media/<UUID>,可带 ?variant=poster;作为查询值时必须 URL 编码);width(可选,240 / 480 / 960 / 1280,默认 480)。不接受任意远程图片地址。支持 If-None-Match。", "查询参数:source(必填,本站静态栅格图片路径,或 /api/media/<UUID>,可带 ?variant=poster;作为查询值时必须 URL 编码);width(可选,240 / 480 / 960 / 1280,默认 480)。高清导出使用 profile=export 且 width=2160:WebP 质量95,不按常规缩略图预算缩小,不放大小原件;其他 profile 或尺寸组合返回400。不接受任意远程图片地址。支持 If-None-Match。",
"WebP 图片;包含 ETag;缓存命中可返回 304;HEAD 无正文。", "WebP 图片;包含 ETag;缓存命中可返回 304;HEAD 无正文。",
"GET /api/preview?source=%2Fapi%2Fmedia%2F<UUID>&width=480", "GET /api/preview?source=%2Fapi%2Fmedia%2F<UUID>&width=480",
"400:参数不合法;404:图片不存在;503:预览暂不可用。" "400:参数不合法;404:图片不存在;503:预览暂不可用。"
@@ -42,6 +42,8 @@ test('real PostgreSQL + S3 notes, flat tags, resource sharing and video processi
await db().query("INSERT INTO contribution_events(id,kind,subject_type,amount) VALUES($1,'test.import','asset',2),($2,'test.edit','post',3)",[randomUUID(),randomUUID()]);assert.equal((await request('/api/feed')).contributions.reduce((n:number,c:{total:number})=>n+c.total,0),5); await db().query("INSERT INTO contribution_events(id,kind,subject_type,amount) VALUES($1,'test.import','asset',2),($2,'test.edit','post',3)",[randomUUID(),randomUUID()]);assert.equal((await request('/api/feed')).contributions.reduce((n:number,c:{total:number})=>n+c.total,0),5);
const img=await fetch(base+'/api/media/'+first.assets[0],{headers:{Range:'bytes=0-9'}});assert.equal(img.status,206);assert.equal((await img.arrayBuffer()).byteLength,10); const img=await fetch(base+'/api/media/'+first.assets[0],{headers:{Range:'bytes=0-9'}});assert.equal(img.status,206);assert.equal((await img.arrayBuffer()).byteLength,10);
const lightweight=await fetch(base+'/api/preview?source='+encodeURIComponent('/api/media/'+first.assets[0])+'&width=480');assert.equal(lightweight.status,200);assert.match(lightweight.headers.get('content-type')||'',/image\/webp/);assert.ok((await lightweight.arrayBuffer()).byteLength<=100000); const lightweight=await fetch(base+'/api/preview?source='+encodeURIComponent('/api/media/'+first.assets[0])+'&width=480');assert.equal(lightweight.status,200);assert.match(lightweight.headers.get('content-type')||'',/image\/webp/);assert.ok((await lightweight.arrayBuffer()).byteLength<=100000);
const highQuality=await fetch(base+'/api/preview?source='+encodeURIComponent('/api/media/'+first.assets[0])+'&width=2160&profile=export');assert.equal(highQuality.status,200);assert.match(highQuality.headers.get('content-type')||'',/image\/webp/);const exportMeta=await sharp(Buffer.from(await highQuality.arrayBuffer())).metadata();assert.equal(exportMeta.width,160);assert.equal(exportMeta.height,220);
assert.equal((await fetch(base+'/api/preview?source='+encodeURIComponent('/api/media/'+first.assets[0])+'&width=2160')).status,400);
const textEntry=await fetch(base+'/explore/'+reused.id+'?agent_mode=True');assert.equal(textEntry.status,200);assert.match(textEntry.headers.get('content-type')||'',/text\/plain/);assert.ok((await textEntry.text()).includes(first.assets[0])); const textEntry=await fetch(base+'/explore/'+reused.id+'?agent_mode=True');assert.equal(textEntry.status,200);assert.match(textEntry.headers.get('content-type')||'',/text\/plain/);assert.ok((await textEntry.text()).includes(first.assets[0]));
assert.equal((await fetch(base+'/images/lines/missing')).status,404);assert.equal((await fetch(base+'/p/'+reused.slug)).status,200); assert.equal((await fetch(base+'/images/lines/missing')).status,404);assert.equal((await fetch(base+'/p/'+reused.slug)).status,200);
for(const sound of [true,false]){ for(const sound of [true,false]){