211 lines
6.9 KiB
TypeScript
211 lines
6.9 KiB
TypeScript
/** @jest-environment node */
|
|
|
|
import { GET, PUT } from '@/app/api/system-settings/route'
|
|
import { getServerSession } from 'next-auth'
|
|
import { prisma } from '@/lib/prisma'
|
|
import { isSystemAdmin } from '@/lib/department-permission'
|
|
|
|
jest.mock('next-auth', () => ({
|
|
getServerSession: jest.fn(),
|
|
}))
|
|
|
|
jest.mock('@/lib/auth', () => ({
|
|
authOptions: {},
|
|
}))
|
|
|
|
jest.mock('@/lib/prisma', () => ({
|
|
prisma: {
|
|
systemSettings: {
|
|
findFirst: jest.fn(),
|
|
count: jest.fn(),
|
|
create: jest.fn(),
|
|
update: jest.fn(),
|
|
},
|
|
},
|
|
}))
|
|
|
|
jest.mock('@/lib/department-permission', () => ({
|
|
isSystemAdmin: jest.fn((role: string | null | undefined) =>
|
|
role === 'ADMIN' || role === 'SYSTEM_ADMIN',
|
|
),
|
|
}))
|
|
|
|
jest.mock('@/lib/url-rewrite', () => ({
|
|
proxyHttpUrl: jest.fn((url: string) => url),
|
|
}))
|
|
|
|
const storedSettings = {
|
|
id: 'settings-1',
|
|
headerBgColor: '#123456',
|
|
siteName: '测试素材库',
|
|
headerLogoUrl: 'https://static.example.com/header.png',
|
|
loginLogoUrl: 'https://static.example.com/login.png',
|
|
s3Endpoint: 'https://storage.internal.example.com',
|
|
s3AccessKey: 'test-access-key',
|
|
s3SecretKey: 'test-secret-key',
|
|
s3Bucket: 'materials',
|
|
s3Region: 'us-east-1',
|
|
s3PublicUrl: 'https://cdn.example.com/materials',
|
|
maintenanceEnabled: true,
|
|
maintenanceMessage: '计划维护',
|
|
maintenanceStartTime: new Date('2026-07-30T10:00:00.000Z'),
|
|
maintenanceEndTime: new Date('2026-07-30T11:00:00.000Z'),
|
|
}
|
|
|
|
function createPutRequest(body: unknown) {
|
|
return {
|
|
json: jest.fn().mockResolvedValue(body),
|
|
} as any
|
|
}
|
|
|
|
describe('/api/system-settings 安全响应', () => {
|
|
beforeEach(() => {
|
|
jest.clearAllMocks()
|
|
;(isSystemAdmin as jest.Mock).mockImplementation(
|
|
(role: string | null | undefined) =>
|
|
role === 'ADMIN' || role === 'SYSTEM_ADMIN',
|
|
)
|
|
;(prisma.systemSettings.count as jest.Mock).mockResolvedValue(1)
|
|
})
|
|
|
|
test('匿名 GET 只返回公开白名单且禁止缓存', async () => {
|
|
;(getServerSession as jest.Mock).mockResolvedValue(null)
|
|
;(prisma.systemSettings.findFirst as jest.Mock).mockResolvedValue(storedSettings)
|
|
|
|
const response = await GET()
|
|
const body = await response.json()
|
|
|
|
expect(response.status).toBe(200)
|
|
expect(response.headers.get('cache-control')).toContain('no-store')
|
|
expect(body).toEqual({
|
|
headerBgColor: '#123456',
|
|
siteName: '测试素材库',
|
|
headerLogoUrl: 'https://static.example.com/header.png',
|
|
loginLogoUrl: 'https://static.example.com/login.png',
|
|
maintenanceEnabled: true,
|
|
maintenanceMessage: '计划维护',
|
|
maintenanceStartTime: '2026-07-30T10:00:00.000Z',
|
|
maintenanceEndTime: '2026-07-30T11:00:00.000Z',
|
|
})
|
|
expect(body).not.toHaveProperty('id')
|
|
expect(body).not.toHaveProperty('s3Endpoint')
|
|
expect(body).not.toHaveProperty('s3AccessKey')
|
|
expect(body).not.toHaveProperty('s3SecretKey')
|
|
expect(body).not.toHaveProperty('s3CredentialsConfigured')
|
|
expect(prisma.systemSettings.create).not.toHaveBeenCalled()
|
|
expect(prisma.systemSettings.count).not.toHaveBeenCalled()
|
|
})
|
|
|
|
test('系统管理员 GET 仅获得非敏感配置与 configured 状态', async () => {
|
|
;(getServerSession as jest.Mock).mockResolvedValue({
|
|
user: { id: 'admin-1', role: 'SYSTEM_ADMIN' },
|
|
})
|
|
;(prisma.systemSettings.findFirst as jest.Mock).mockResolvedValue(storedSettings)
|
|
|
|
const response = await GET()
|
|
const body = await response.json()
|
|
|
|
expect(response.status).toBe(200)
|
|
expect(body).toEqual(expect.objectContaining({
|
|
s3Endpoint: 'https://storage.internal.example.com',
|
|
s3Bucket: 'materials',
|
|
s3Region: 'us-east-1',
|
|
s3PublicUrl: 'https://cdn.example.com/materials',
|
|
s3CredentialsConfigured: true,
|
|
}))
|
|
expect(body).not.toHaveProperty('s3AccessKey')
|
|
expect(body).not.toHaveProperty('s3SecretKey')
|
|
expect(prisma.systemSettings.count).toHaveBeenCalledWith({
|
|
where: {
|
|
id: storedSettings.id,
|
|
s3AccessKey: { not: '' },
|
|
s3SecretKey: { not: '' },
|
|
},
|
|
})
|
|
})
|
|
|
|
test('普通用户不能更新系统设置', async () => {
|
|
;(getServerSession as jest.Mock).mockResolvedValue({
|
|
user: { id: 'user-1', role: 'USER' },
|
|
})
|
|
|
|
const response = await PUT(createPutRequest({ siteName: '无权修改' }))
|
|
const body = await response.json()
|
|
|
|
expect(response.status).toBe(403)
|
|
expect(body).toEqual({ error: '无权限' })
|
|
expect(response.headers.get('cache-control')).toContain('no-store')
|
|
expect(prisma.systemSettings.findFirst).not.toHaveBeenCalled()
|
|
expect(prisma.systemSettings.update).not.toHaveBeenCalled()
|
|
})
|
|
|
|
test('凭据必须成对覆盖写入', async () => {
|
|
;(getServerSession as jest.Mock).mockResolvedValue({
|
|
user: { id: 'admin-1', role: 'ADMIN' },
|
|
})
|
|
|
|
const response = await PUT(createPutRequest({
|
|
s3AccessKey: 'new-access-key',
|
|
s3SecretKey: '',
|
|
}))
|
|
const body = await response.json()
|
|
|
|
expect(response.status).toBe(400)
|
|
expect(body).toEqual({ error: 'Access Key 和 Secret Key 必须同时填写' })
|
|
expect(prisma.systemSettings.update).not.toHaveBeenCalled()
|
|
})
|
|
|
|
test('管理员可覆盖写入新凭据,但响应不返回任何凭据原文', async () => {
|
|
;(getServerSession as jest.Mock).mockResolvedValue({
|
|
user: { id: 'admin-1', role: 'ADMIN' },
|
|
})
|
|
;(prisma.systemSettings.findFirst as jest.Mock).mockResolvedValue({
|
|
id: storedSettings.id,
|
|
})
|
|
;(prisma.systemSettings.update as jest.Mock).mockResolvedValue({
|
|
...storedSettings,
|
|
s3AccessKey: 'new-access-key',
|
|
s3SecretKey: 'new-secret-key',
|
|
})
|
|
|
|
const response = await PUT(createPutRequest({
|
|
siteName: '更新后的素材库',
|
|
s3AccessKey: ' new-access-key ',
|
|
s3SecretKey: ' new-secret-key ',
|
|
}))
|
|
const body = await response.json()
|
|
|
|
expect(response.status).toBe(200)
|
|
expect(prisma.systemSettings.update).toHaveBeenCalledWith(
|
|
expect.objectContaining({
|
|
where: { id: storedSettings.id },
|
|
data: expect.objectContaining({
|
|
siteName: '更新后的素材库',
|
|
s3AccessKey: 'new-access-key',
|
|
s3SecretKey: 'new-secret-key',
|
|
}),
|
|
}),
|
|
)
|
|
expect(body.s3CredentialsConfigured).toBe(true)
|
|
expect(body).not.toHaveProperty('s3AccessKey')
|
|
expect(body).not.toHaveProperty('s3SecretKey')
|
|
expect(JSON.stringify(body)).not.toContain('new-secret-key')
|
|
expect(prisma.systemSettings.count).not.toHaveBeenCalled()
|
|
})
|
|
|
|
test('超级管理员标记可以更新设置', async () => {
|
|
;(getServerSession as jest.Mock).mockResolvedValue({
|
|
user: { id: 'super-1', role: 'USER', isSuperAdmin: true },
|
|
})
|
|
;(prisma.systemSettings.findFirst as jest.Mock).mockResolvedValue({
|
|
id: storedSettings.id,
|
|
})
|
|
;(prisma.systemSettings.update as jest.Mock).mockResolvedValue(storedSettings)
|
|
|
|
const response = await PUT(createPutRequest({ siteName: '超级管理员修改' }))
|
|
|
|
expect(response.status).toBe(200)
|
|
expect(prisma.systemSettings.update).toHaveBeenCalled()
|
|
})
|
|
})
|