import { NextRequest } from 'next/server' const VALID_RESTORE_TOKEN = 'restore-token-12345678901234567890' jest.mock('@/lib/prisma', () => ({ prisma: { user: { create: jest.fn(), findUnique: jest.fn(), }, category: { create: jest.fn(), }, scene: { create: jest.fn(), }, material: { create: jest.fn(), }, }, })) function createRequest( token?: string, ip = '127.0.0.1', confirmPhrase?: string ) { return new NextRequest('http://localhost/api/restore-data', { method: 'POST', headers: { ...(token ? { 'x-restore-data-token': token } : {}), ...(confirmPhrase ? { 'x-restore-data-confirm': confirmPhrase } : {}), 'x-forwarded-for': ip, }, }) } async function loadPost() { const route = await import('../route') return route.POST } describe('POST /api/restore-data', () => { const originalEnv = process.env beforeEach(() => { jest.resetModules() process.env = { ...originalEnv } delete process.env.RESTORE_DATA_ENABLED delete process.env.RESTORE_DATA_TOKEN delete process.env.RESTORE_DATA_ALLOWED_IPS delete process.env.RESTORE_DATA_TRUST_PROXY delete process.env.RESTORE_ADMIN_PASSWORD }) afterAll(() => { process.env = originalEnv }) it('默认未显式启用时拒绝执行', async () => { process.env.RESTORE_DATA_TOKEN = VALID_RESTORE_TOKEN process.env.RESTORE_ADMIN_PASSWORD = 'configured-password' const POST = await loadPost() const response = await POST(createRequest(VALID_RESTORE_TOKEN)) const data = await response.json() expect(response.status).toBe(503) expect(data.success).toBe(false) expect(data.message).toContain('RESTORE_DATA_ENABLED=1') }) it('启用后仍要求配置恢复令牌', async () => { process.env.RESTORE_DATA_ENABLED = '1' process.env.RESTORE_ADMIN_PASSWORD = 'configured-password' const POST = await loadPost() const response = await POST(createRequest()) const data = await response.json() expect(response.status).toBe(503) expect(data.success).toBe(false) expect(data.message).toContain('RESTORE_DATA_TOKEN') }) it('启用后会拒绝过短恢复令牌', async () => { process.env.RESTORE_DATA_ENABLED = '1' process.env.RESTORE_DATA_TOKEN = 'restore-token' process.env.RESTORE_DATA_ALLOWED_IPS = '127.0.0.1' process.env.RESTORE_ADMIN_PASSWORD = 'configured-password' const POST = await loadPost() const response = await POST(createRequest('restore-token')) const data = await response.json() expect(response.status).toBe(503) expect(data.success).toBe(false) expect(data.message).toContain('RESTORE_DATA_TOKEN 长度不足') }) it('启用且配置令牌后会拒绝错误令牌', async () => { process.env.RESTORE_DATA_ENABLED = '1' process.env.RESTORE_DATA_TOKEN = VALID_RESTORE_TOKEN process.env.RESTORE_DATA_ALLOWED_IPS = '127.0.0.1' process.env.RESTORE_DATA_TRUST_PROXY = '1' process.env.RESTORE_ADMIN_PASSWORD = 'configured-password' const POST = await loadPost() const response = await POST(createRequest('wrong-token')) const data = await response.json() expect(response.status).toBe(401) expect(data.success).toBe(false) expect(data.message).toBe('恢复令牌无效') }) it('启用且配置令牌后仍要求配置 IP 白名单', async () => { process.env.RESTORE_DATA_ENABLED = '1' process.env.RESTORE_DATA_TOKEN = VALID_RESTORE_TOKEN process.env.RESTORE_ADMIN_PASSWORD = 'configured-password' const POST = await loadPost() const response = await POST(createRequest(VALID_RESTORE_TOKEN)) const data = await response.json() expect(response.status).toBe(503) expect(data.success).toBe(false) expect(data.message).toContain('RESTORE_DATA_ALLOWED_IPS') }) it('启用且配置令牌后会拒绝非白名单 IP', async () => { process.env.RESTORE_DATA_ENABLED = '1' process.env.RESTORE_DATA_TOKEN = VALID_RESTORE_TOKEN process.env.RESTORE_DATA_ALLOWED_IPS = '127.0.0.1' process.env.RESTORE_DATA_TRUST_PROXY = '1' process.env.RESTORE_ADMIN_PASSWORD = 'configured-password' const POST = await loadPost() const response = await POST(createRequest(VALID_RESTORE_TOKEN, '10.0.0.8')) const data = await response.json() expect(response.status).toBe(403) expect(data.success).toBe(false) expect(data.message).toBe('当前来源 IP 不允许执行恢复') }) it('默认不信任可伪造的代理 IP 头', async () => { process.env.RESTORE_DATA_ENABLED = '1' process.env.RESTORE_DATA_TOKEN = VALID_RESTORE_TOKEN process.env.RESTORE_DATA_ALLOWED_IPS = '127.0.0.1' process.env.RESTORE_ADMIN_PASSWORD = 'configured-password' const POST = await loadPost() const response = await POST(createRequest(VALID_RESTORE_TOKEN, '127.0.0.1')) const data = await response.json() expect(response.status).toBe(403) expect(data.success).toBe(false) expect(data.message).toBe('无法确认可信来源 IP,恢复接口已拒绝') }) it('通过令牌和白名单后仍要求恢复确认头', async () => { process.env.RESTORE_DATA_ENABLED = '1' process.env.RESTORE_DATA_TOKEN = VALID_RESTORE_TOKEN process.env.RESTORE_DATA_ALLOWED_IPS = '127.0.0.1' process.env.RESTORE_DATA_TRUST_PROXY = '1' process.env.RESTORE_ADMIN_PASSWORD = 'configured-password' const POST = await loadPost() const response = await POST(createRequest(VALID_RESTORE_TOKEN, '127.0.0.1')) const data = await response.json() expect(response.status).toBe(400) expect(data.success).toBe(false) expect(data.message).toBe('恢复确认头无效') expect(data.requiredConfirmHeader).toBe('x-restore-data-confirm') }) })