179 lines
5.7 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

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')
})
})