ai-chatbot/tests/routes/retired-storage.test.ts

79 lines
4.3 KiB
TypeScript

import { test, expect } from '@playwright/test';
import { NextRequest, NextResponse } from 'next/server';
import { serverLoader } from '../helpers/canvas-download-server-loader';
import { isRetiredStorageUrl, RETIRED_STORAGE_MESSAGE } from '@/lib/storage/retired-storage';
import { resolveSiteBranding, DEFAULT_SITE_LOGO, DEFAULT_SITE_LOGO_DARK } from '@/lib/site-branding';
const retired = 'https://s3.1080p.hk/jsxiangche/2025/01/image.png';
const current = 'https://scqnycdn.jiansun.vip/uploads/current.png';
test('retired URLs include stored proxy URLs but do not include live storage', () => {
for (const url of [retired, retired.replace('https:', 'http:'), '/api/image-proxy?url=' + encodeURIComponent(retired)]) {
expect(isRetiredStorageUrl(url)).toBe(true);
}
for (const url of [current, '/brand/logo-black.png', 'https://s3.jiansun.vip/gongxiangpan/a.png', 'https://example.com/s3.1080p.hk/a.png']) {
expect(isRetiredStorageUrl(url)).toBe(false);
}
});
test('original image proxy returns permanent failure without redirecting or fetching', async () => {
let fetches = 0;
const route = serverLoader({ 'next/server': { NextResponse }, fetch: () => { fetches++; throw new Error('Unexpected network'); } })('app/api/image-proxy/route.ts');
const response = await route.GET(new NextRequest('https://ai.yydd.ai/api/image-proxy?original=1&url=' + encodeURIComponent(retired), {headers:{'sec-fetch-dest':'image'}}));
expect(response.status).toBe(410);
const data = await response.json();
expect(data.failure).toMatchObject({code:'STORAGE_RETIRED',retryable:false});
expect(data.error).toContain(data.requestId);
expect(fetches).toBe(0);
});
test('byte cache never fetches retired resources', async () => {
let fetches = 0;
const module = serverLoader({ fetch: () => { fetches++; throw new Error('Unexpected network'); } })('lib/storage/image-byte-cache.ts');
await expect(module.fetchImageBytes(retired)).rejects.toThrow('HTTP 410');
expect(fetches).toBe(0);
});
test('retired sources cannot enter the archive queue', async () => {
let inserts = 0;
const module = serverLoader({
'@/lib/db': {db:{insert:()=>{inserts++;throw new Error('Unexpected insert');}}},
'@/lib/db/schema': {},
})('lib/storage/image-archive-db.ts');
expect(await module.enqueueImageArchiveTask({sourceUrl:retired})).toBeNull();
expect(inserts).toBe(0);
});
test('single downloads reject retired sources before async preparation, while live candidates remain usable', async () => {
const fetched: string[] = [];
const route = serverLoader({
'@/app/(auth)/auth':{auth:async()=>({user:{id:'owner'}})},
'next/server':{NextResponse,after:()=>{}},
'@/lib/storage/canvas-cdn-download':{},
'@/lib/storage/image-download-preparation':{},
'@/lib/canvas/photoshop-safe-download':{
fetchCanvasDownloadAsset:async({file}:any)=>{fetched.push(file.url);return {ok:true,bytes:Buffer.from('original'),filename:'original.png',contentType:'image/png'};},
sanitizeCanvasDownloadFileName:()=> 'original', buildAttachmentContentDisposition:()=> 'attachment',
},
})('app/api/canvas/download-image/route.ts');
const request = (body:unknown) => new NextRequest('https://ai.yydd.ai/api/canvas/download-image',{method:'POST',body:JSON.stringify(body)});
const failure = await route.POST(request({url:retired,preparation:'async',preferDirect:true}));
expect(failure.status).toBe(410);
expect(fetched).toEqual([]);
const success = await route.POST(request({urls:[retired,current]}));
expect(success.status).toBe(200);
expect(fetched).toEqual([current]);
expect(await success.text()).toBe('original');
});
test('ZIP asset preparation reports permanent failure without retrying the source', async () => {
const module = serverLoader({})( 'lib/canvas/photoshop-safe-download.ts');
const result = await module.fetchCanvasDownloadAsset({file:{url:retired},index:0,requestOrigin:'https://ai.yydd.ai',cookie:''});
expect(result).toMatchObject({ok:false,error:RETIRED_STORAGE_MESSAGE});
});
test('branding replaces unavailable storage with theme variants and preserves custom live branding', () => {
expect(resolveSiteBranding({siteLogo:retired,siteLogoDark:retired,favicon:retired})).toEqual({siteLogo:DEFAULT_SITE_LOGO,siteLogoDark:DEFAULT_SITE_LOGO_DARK,favicon:DEFAULT_SITE_LOGO});
expect(resolveSiteBranding({siteLogo:current,siteLogoDark:current}).siteLogo).toBe(current);
});