/** @jest-environment node */ import { lookup } from 'node:dns/promises' import { NextRequest } from 'next/server' import { GET } from './route' jest.mock('node:dns/promises', () => ({ lookup: jest.fn(), })) jest.mock('@/lib/video-preview-access', () => ({ findBlockedVideoPreviewByUrl: jest.fn(), isVideoMediaPath: jest.fn(() => false), })) const mockedLookup = lookup as jest.MockedFunction function createRequest(targetUrl: string) { return new NextRequest( `https://sc.jiansun.vip/api/proxy-media?url=${encodeURIComponent(targetUrl)}`, ) } describe('/api/proxy-media', () => { beforeEach(() => { jest.clearAllMocks() mockedLookup.mockResolvedValue([ { address: '1.1.1.1', family: 4 }, ] as any) global.fetch = jest.fn() as jest.Mock }) test('回环地址在发起网络请求前就被拒绝', async () => { const response = await GET(createRequest('http://127.0.0.1:9000/a.jpg')) expect(response.status).toBe(403) expect(global.fetch).not.toHaveBeenCalled() }) test('域名解析到内网地址时拒绝回源', async () => { mockedLookup.mockResolvedValue([ { address: '192.168.58.20', family: 4 }, ] as any) const response = await GET(createRequest('https://s3.1080p.hk/a.jpg')) expect(response.status).toBe(403) expect(global.fetch).not.toHaveBeenCalled() }) test('公网 S3 使用手动跳转和流式响应', async () => { ;(global.fetch as jest.Mock).mockResolvedValue(new Response('image-data', { status: 200, headers: { 'content-type': 'image/jpeg', 'content-length': '10', }, })) const response = await GET(createRequest('https://s3.1080p.hk/a.jpg?token=secret')) expect(response.status).toBe(200) expect(await response.text()).toBe('image-data') expect(global.fetch).toHaveBeenCalledWith( 'https://s3.1080p.hk/a.jpg?token=secret', expect.objectContaining({ method: 'GET', redirect: 'manual', signal: expect.any(AbortSignal), }), ) }) test('每次上游跳转都会重新验证目标', async () => { ;(global.fetch as jest.Mock).mockResolvedValue(new Response(null, { status: 302, headers: { location: 'http://169.254.169.254/latest/meta-data' }, })) const response = await GET(createRequest('https://s3.1080p.hk/a.jpg')) expect(response.status).toBe(403) expect(global.fetch).toHaveBeenCalledTimes(1) }) })