130 lines
3.6 KiB
TypeScript
130 lines
3.6 KiB
TypeScript
jest.mock('next/server', () => {
|
|
class MockNextResponse extends Response {
|
|
static next() {
|
|
return new MockNextResponse(null, { status: 200 })
|
|
}
|
|
|
|
static redirect(url: URL | string) {
|
|
return new MockNextResponse(null, {
|
|
status: 307,
|
|
headers: { Location: url.toString() },
|
|
})
|
|
}
|
|
}
|
|
|
|
return { NextResponse: MockNextResponse }
|
|
})
|
|
|
|
jest.mock('next-auth/jwt', () => ({
|
|
getToken: jest.fn(),
|
|
}))
|
|
|
|
function createRequest(url: string, options: { method?: string; origin?: string } = {}) {
|
|
const parsed = new URL(url)
|
|
const headers = new Headers()
|
|
if (options.origin) {
|
|
headers.set('origin', options.origin)
|
|
}
|
|
|
|
return {
|
|
method: options.method || 'GET',
|
|
url,
|
|
headers,
|
|
nextUrl: {
|
|
pathname: parsed.pathname,
|
|
origin: parsed.origin,
|
|
},
|
|
} as any
|
|
}
|
|
|
|
describe('middleware CORS', () => {
|
|
const originalEnv = process.env
|
|
|
|
beforeEach(() => {
|
|
jest.resetModules()
|
|
process.env = {
|
|
...originalEnv,
|
|
ALLOWED_ORIGINS: 'https://portal.example.com',
|
|
APP_PUBLIC_URL: 'https://app.example.com',
|
|
NEXTAUTH_URL: 'https://app.example.com',
|
|
}
|
|
})
|
|
|
|
afterEach(() => {
|
|
process.env = originalEnv
|
|
})
|
|
|
|
it('允许白名单来源携带凭据访问 API', async () => {
|
|
const { middleware } = await import('../middleware')
|
|
|
|
const response = await middleware(createRequest('https://app.example.com/api/materials', {
|
|
origin: 'https://portal.example.com',
|
|
}))
|
|
|
|
expect(response.headers.get('Access-Control-Allow-Origin')).toBe('https://portal.example.com')
|
|
expect(response.headers.get('Access-Control-Allow-Credentials')).toBe('true')
|
|
expect(response.headers.get('Vary')).toBe('Origin')
|
|
})
|
|
|
|
it('拒绝陌生来源的预检请求', async () => {
|
|
const { middleware } = await import('../middleware')
|
|
|
|
const response = await middleware(createRequest('https://app.example.com/api/materials', {
|
|
method: 'OPTIONS',
|
|
origin: 'https://evil.example.com',
|
|
}))
|
|
|
|
expect(response.status).toBe(403)
|
|
expect(response.headers.get('Access-Control-Allow-Origin')).toBeNull()
|
|
})
|
|
|
|
it('陌生来源的普通 API 响应不返回 CORS 凭据头', async () => {
|
|
const { middleware } = await import('../middleware')
|
|
|
|
const response = await middleware(createRequest('https://app.example.com/api/materials', {
|
|
origin: 'https://evil.example.com',
|
|
}))
|
|
|
|
expect(response.status).toBe(200)
|
|
expect(response.headers.get('Access-Control-Allow-Origin')).toBeNull()
|
|
expect(response.headers.get('Access-Control-Allow-Credentials')).toBeNull()
|
|
})
|
|
})
|
|
|
|
describe('middleware admin pages', () => {
|
|
const originalEnv = process.env
|
|
|
|
beforeEach(() => {
|
|
jest.resetModules()
|
|
process.env = {
|
|
...originalEnv,
|
|
NEXTAUTH_SECRET: 'test-secret',
|
|
}
|
|
})
|
|
|
|
afterEach(() => {
|
|
process.env = originalEnv
|
|
})
|
|
|
|
it('普通用户访问监控页会被带回首页', async () => {
|
|
const { getToken } = require('next-auth/jwt')
|
|
getToken.mockResolvedValue({ role: 'USER' })
|
|
|
|
const { middleware } = await import('../middleware')
|
|
const response = await middleware(createRequest('https://app.example.com/admin/monitor'))
|
|
|
|
expect(response.status).toBe(307)
|
|
expect(response.headers.get('Location')).toBe('https://app.example.com/')
|
|
})
|
|
|
|
it('管理员可以访问监控页', async () => {
|
|
const { getToken } = require('next-auth/jwt')
|
|
getToken.mockResolvedValue({ role: 'ADMIN' })
|
|
|
|
const { middleware } = await import('../middleware')
|
|
const response = await middleware(createRequest('https://app.example.com/admin/monitor'))
|
|
|
|
expect(response.status).toBe(200)
|
|
})
|
|
})
|