diff --git a/apps/waterfall-wiki/src/lib/api-documentation.ts b/apps/waterfall-wiki/src/lib/api-documentation.ts index e5096d4..342bf7e 100644 --- a/apps/waterfall-wiki/src/lib/api-documentation.ts +++ b/apps/waterfall-wiki/src/lib/api-documentation.ts @@ -68,7 +68,7 @@ export const apiDocuments:Record={ "/api/preview": [ "轻量图片预览", "GET, HEAD", - "查询参数:source(必填,本站静态栅格图片路径,或 /api/media/,可带 ?variant=poster;作为查询值时必须 URL 编码);width(可选,240 / 480 / 960 / 1280,默认 480)。不接受任意远程图片地址。支持 If-None-Match。", + "查询参数:source(必填,本站静态栅格图片路径,或 /api/media/,可带 ?variant=poster;作为查询值时必须 URL 编码);width(可选,240 / 480 / 960 / 1280,默认 480)。高清导出使用 profile=export 且 width=2160:WebP 质量95,不按常规缩略图预算缩小,不放大小原件;其他 profile 或尺寸组合返回400。不接受任意远程图片地址。支持 If-None-Match。", "WebP 图片;包含 ETag;缓存命中可返回 304;HEAD 无正文。", "GET /api/preview?source=%2Fapi%2Fmedia%2F&width=480", "400:参数不合法;404:图片不存在;503:预览暂不可用。" diff --git a/apps/waterfall-wiki/tests/integration.test.ts b/apps/waterfall-wiki/tests/integration.test.ts index 9e63fd9..717abf7 100644 --- a/apps/waterfall-wiki/tests/integration.test.ts +++ b/apps/waterfall-wiki/tests/integration.test.ts @@ -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); 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 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])); 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]){