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 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); expect(await response.json()).toMatchObject({code:'storage_retired',error:RETIRED_STORAGE_MESSAGE}); expect(fetches).toBe(0); }); test('byte cache never fetches retired resources', async () => { let fetches = 0; const module = serverLoader({ fetch: () => { fetches++; throw 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 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); });