330 lines
12 KiB
TypeScript
330 lines
12 KiB
TypeScript
import { NextRequest } from 'next/server'
|
|
|
|
jest.mock('@/lib/auth', () => ({
|
|
authOptions: {},
|
|
}))
|
|
|
|
jest.mock('next-auth', () => ({
|
|
getServerSession: jest.fn(),
|
|
}))
|
|
|
|
jest.mock('@/lib/admin-permissions', () => ({
|
|
isCurrentSuperAdmin: jest.fn(),
|
|
}))
|
|
|
|
jest.mock('@/lib/field-pool', () => ({
|
|
getFieldPoolRecords: jest.fn(),
|
|
normalizeFieldOptionValue: jest.fn((value: string) => value.trim()),
|
|
syncFieldPoolFromTemplates: jest.fn(),
|
|
}))
|
|
|
|
jest.mock('@/lib/prisma', () => ({
|
|
prisma: {
|
|
category: {
|
|
findFirst: jest.fn(),
|
|
findUnique: jest.fn(),
|
|
create: jest.fn(),
|
|
update: jest.fn(),
|
|
delete: jest.fn(),
|
|
},
|
|
scene: {
|
|
findFirst: jest.fn(),
|
|
findUnique: jest.fn(),
|
|
create: jest.fn(),
|
|
update: jest.fn(),
|
|
delete: jest.fn(),
|
|
},
|
|
platform: {
|
|
findFirst: jest.fn(),
|
|
findUnique: jest.fn(),
|
|
update: jest.fn(),
|
|
delete: jest.fn(),
|
|
},
|
|
material: {
|
|
count: jest.fn(),
|
|
updateMany: jest.fn(),
|
|
},
|
|
fieldDefinition: {
|
|
findUnique: jest.fn(),
|
|
update: jest.fn(),
|
|
},
|
|
$transaction: jest.fn(),
|
|
},
|
|
}))
|
|
|
|
const { getServerSession } = require('next-auth')
|
|
const { isCurrentSuperAdmin } = require('@/lib/admin-permissions')
|
|
const { prisma } = require('@/lib/prisma')
|
|
const { normalizeFieldOptionValue } = require('@/lib/field-pool')
|
|
|
|
import { POST as createCategory, PATCH as updateCategory, DELETE as deleteCategory } from '../../categories/route'
|
|
import { POST as createScene, PATCH as updateScene, DELETE as deleteScene } from '../../scenes/route'
|
|
import { PUT as updatePlatform, DELETE as deletePlatform } from '../../platforms/route'
|
|
import { PATCH as toggleCategoryField, PUT as saveCategoryFields } from '../../category-fields/route'
|
|
import { PUT as updateTemplateCategoryOptions } from '../../template-categories/[fieldKey]/route'
|
|
|
|
function jsonRequest(url: string, body: unknown, method = 'PUT') {
|
|
return new NextRequest(url, {
|
|
method,
|
|
body: JSON.stringify(body),
|
|
})
|
|
}
|
|
|
|
describe('全局字典和分类字段写操作权限', () => {
|
|
beforeEach(() => {
|
|
jest.resetAllMocks()
|
|
normalizeFieldOptionValue.mockImplementation((value: string) => value.trim())
|
|
getServerSession.mockResolvedValue({
|
|
user: { id: 'admin-1', role: 'ADMIN' },
|
|
})
|
|
isCurrentSuperAdmin.mockResolvedValue(false)
|
|
})
|
|
|
|
it('普通管理员不能修改或删除店铺分类', async () => {
|
|
const updateResponse = await updateCategory(
|
|
jsonRequest('http://localhost/api/categories', { id: 'cat-1', name: '新店铺' }, 'PATCH')
|
|
)
|
|
const deleteResponse = await deleteCategory(
|
|
new NextRequest('http://localhost/api/categories?id=cat-1', { method: 'DELETE' })
|
|
)
|
|
|
|
expect(updateResponse.status).toBe(403)
|
|
expect(deleteResponse.status).toBe(403)
|
|
expect(prisma.category.update).not.toHaveBeenCalled()
|
|
expect(prisma.category.delete).not.toHaveBeenCalled()
|
|
expect(prisma.material.updateMany).not.toHaveBeenCalled()
|
|
})
|
|
|
|
it('普通管理员不能新增店铺分类', async () => {
|
|
const response = await createCategory(
|
|
jsonRequest('http://localhost/api/categories', { name: '新店铺' }, 'POST')
|
|
)
|
|
const data = await response.json()
|
|
|
|
expect(response.status).toBe(403)
|
|
expect(data.error).toBe('仅最高权限账号可新增店铺分类')
|
|
expect(prisma.category.create).not.toHaveBeenCalled()
|
|
})
|
|
|
|
it('最高权限账号可以新增店铺分类', async () => {
|
|
isCurrentSuperAdmin.mockResolvedValue(true)
|
|
prisma.category.findUnique.mockResolvedValue(null)
|
|
prisma.category.create.mockResolvedValue({ id: 'cat-1', name: '新店铺' })
|
|
|
|
const response = await createCategory(
|
|
jsonRequest('http://localhost/api/categories', { name: '新店铺' }, 'POST')
|
|
)
|
|
const data = await response.json()
|
|
|
|
expect(response.status).toBe(201)
|
|
expect(data.name).toBe('新店铺')
|
|
expect(prisma.category.create).toHaveBeenCalledWith({
|
|
data: { name: '新店铺' },
|
|
})
|
|
})
|
|
|
|
it('最高权限账号可以修改店铺分类并同步素材', async () => {
|
|
isCurrentSuperAdmin.mockResolvedValue(true)
|
|
prisma.category.findFirst.mockResolvedValue(null)
|
|
prisma.category.findUnique.mockResolvedValue({ id: 'cat-1', name: '旧店铺' })
|
|
prisma.category.update.mockResolvedValue({ id: 'cat-1', name: '新店铺' })
|
|
prisma.material.updateMany.mockResolvedValue({ count: 3 })
|
|
|
|
const response = await updateCategory(
|
|
jsonRequest('http://localhost/api/categories', { id: 'cat-1', name: '新店铺' }, 'PATCH')
|
|
)
|
|
const data = await response.json()
|
|
|
|
expect(response.status).toBe(200)
|
|
expect(data.name).toBe('新店铺')
|
|
expect(prisma.material.updateMany).toHaveBeenCalledWith({
|
|
where: { categoryName: '旧店铺' },
|
|
data: { categoryName: '新店铺' },
|
|
})
|
|
})
|
|
|
|
it('普通管理员不能修改或删除场景分类', async () => {
|
|
const updateResponse = await updateScene(
|
|
jsonRequest('http://localhost/api/scenes', { id: 'scene-1', name: '新场景' }, 'PATCH')
|
|
)
|
|
const deleteResponse = await deleteScene(
|
|
new NextRequest('http://localhost/api/scenes?id=scene-1', { method: 'DELETE' })
|
|
)
|
|
|
|
expect(updateResponse.status).toBe(403)
|
|
expect(deleteResponse.status).toBe(403)
|
|
expect(prisma.scene.update).not.toHaveBeenCalled()
|
|
expect(prisma.scene.delete).not.toHaveBeenCalled()
|
|
expect(prisma.material.updateMany).not.toHaveBeenCalled()
|
|
})
|
|
|
|
it('普通管理员不能新增场景分类', async () => {
|
|
const response = await createScene(
|
|
jsonRequest('http://localhost/api/scenes', { name: '新场景' }, 'POST')
|
|
)
|
|
const data = await response.json()
|
|
|
|
expect(response.status).toBe(403)
|
|
expect(data.error).toBe('仅最高权限账号可新增场景分类')
|
|
expect(prisma.scene.create).not.toHaveBeenCalled()
|
|
})
|
|
|
|
it('最高权限账号可以新增场景分类', async () => {
|
|
isCurrentSuperAdmin.mockResolvedValue(true)
|
|
prisma.scene.findUnique.mockResolvedValue(null)
|
|
prisma.scene.create.mockResolvedValue({ id: 'scene-1', name: '新场景' })
|
|
|
|
const response = await createScene(
|
|
jsonRequest('http://localhost/api/scenes', { name: '新场景' }, 'POST')
|
|
)
|
|
const data = await response.json()
|
|
|
|
expect(response.status).toBe(201)
|
|
expect(data.name).toBe('新场景')
|
|
expect(prisma.scene.create).toHaveBeenCalledWith({
|
|
data: { name: '新场景' },
|
|
})
|
|
})
|
|
|
|
it('最高权限账号可以修改场景分类并同步素材', async () => {
|
|
isCurrentSuperAdmin.mockResolvedValue(true)
|
|
prisma.scene.findFirst.mockResolvedValue(null)
|
|
prisma.scene.findUnique.mockResolvedValue({ id: 'scene-1', name: '旧场景' })
|
|
prisma.scene.update.mockResolvedValue({ id: 'scene-1', name: '新场景' })
|
|
prisma.material.updateMany.mockResolvedValue({ count: 2 })
|
|
|
|
const response = await updateScene(
|
|
jsonRequest('http://localhost/api/scenes', { id: 'scene-1', name: '新场景' }, 'PATCH')
|
|
)
|
|
const data = await response.json()
|
|
|
|
expect(response.status).toBe(200)
|
|
expect(data.name).toBe('新场景')
|
|
expect(prisma.material.updateMany).toHaveBeenCalledWith({
|
|
where: { sceneName: '旧场景' },
|
|
data: { sceneName: '新场景' },
|
|
})
|
|
})
|
|
|
|
it('普通管理员不能修改或删除平台分类', async () => {
|
|
const updateResponse = await updatePlatform(
|
|
jsonRequest('http://localhost/api/platforms', { id: 'platform-1', name: '新平台' })
|
|
)
|
|
const deleteResponse = await deletePlatform(
|
|
new NextRequest('http://localhost/api/platforms?id=platform-1', { method: 'DELETE' })
|
|
)
|
|
|
|
expect(updateResponse.status).toBe(403)
|
|
expect(deleteResponse.status).toBe(403)
|
|
expect(prisma.platform.update).not.toHaveBeenCalled()
|
|
expect(prisma.platform.delete).not.toHaveBeenCalled()
|
|
expect(prisma.material.updateMany).not.toHaveBeenCalled()
|
|
})
|
|
|
|
it('最高权限账号可以修改平台分类并同步素材', async () => {
|
|
isCurrentSuperAdmin.mockResolvedValue(true)
|
|
prisma.platform.findFirst.mockResolvedValue(null)
|
|
prisma.platform.findUnique.mockResolvedValue({ id: 'platform-1', name: '旧平台' })
|
|
prisma.platform.update.mockResolvedValue({ id: 'platform-1', name: '新平台' })
|
|
prisma.material.updateMany.mockResolvedValue({ count: 1 })
|
|
|
|
const response = await updatePlatform(
|
|
jsonRequest('http://localhost/api/platforms', { id: 'platform-1', name: '新平台' })
|
|
)
|
|
const data = await response.json()
|
|
|
|
expect(response.status).toBe(200)
|
|
expect(data.name).toBe('新平台')
|
|
expect(prisma.material.updateMany).toHaveBeenCalledWith({
|
|
where: { platformName: '旧平台' },
|
|
data: { platformName: '新平台' },
|
|
})
|
|
})
|
|
|
|
it('普通管理员不能修改分类字段启用和排序', async () => {
|
|
const toggleResponse = await toggleCategoryField(
|
|
jsonRequest('http://localhost/api/category-fields', { fieldKey: 'brandName', enabled: true }, 'PATCH')
|
|
)
|
|
const saveResponse = await saveCategoryFields(
|
|
jsonRequest('http://localhost/api/category-fields', {
|
|
updates: [{ fieldKey: 'brandName', filterDisplayName: '品牌', filterOrder: 1 }],
|
|
})
|
|
)
|
|
|
|
expect(toggleResponse.status).toBe(403)
|
|
expect(saveResponse.status).toBe(403)
|
|
expect(prisma.fieldDefinition.update).not.toHaveBeenCalled()
|
|
expect(prisma.$transaction).not.toHaveBeenCalled()
|
|
})
|
|
|
|
it('最高权限账号可以修改分类字段配置', async () => {
|
|
isCurrentSuperAdmin.mockResolvedValue(true)
|
|
prisma.fieldDefinition.findUnique.mockResolvedValue({ id: 'field-1', key: 'brandName' })
|
|
prisma.fieldDefinition.update.mockResolvedValue({ id: 'field-1', isFilterEnabled: true })
|
|
|
|
const response = await toggleCategoryField(
|
|
jsonRequest('http://localhost/api/category-fields', { fieldKey: 'brandName', enabled: true }, 'PATCH')
|
|
)
|
|
const data = await response.json()
|
|
|
|
expect(response.status).toBe(200)
|
|
expect(data.success).toBe(true)
|
|
expect(prisma.fieldDefinition.update).toHaveBeenCalledWith({
|
|
where: { id: 'field-1' },
|
|
data: { isFilterEnabled: true },
|
|
})
|
|
})
|
|
|
|
it('普通管理员不能批量修改模板分类选项', async () => {
|
|
const response = await updateTemplateCategoryOptions(
|
|
jsonRequest('http://localhost/api/template-categories/brandName', { options: ['A', 'B'] }),
|
|
{ params: { fieldKey: 'brandName' } }
|
|
)
|
|
|
|
expect(response.status).toBe(403)
|
|
expect(prisma.$transaction).not.toHaveBeenCalled()
|
|
})
|
|
|
|
it('最高权限账号可以批量修改模板分类选项', async () => {
|
|
isCurrentSuperAdmin.mockResolvedValue(true)
|
|
const tx = {
|
|
fieldDefinition: {
|
|
findUnique: jest.fn().mockResolvedValue({
|
|
id: 'field-1',
|
|
key: 'brandName',
|
|
options: [],
|
|
}),
|
|
},
|
|
fieldOption: {
|
|
update: jest.fn(),
|
|
create: jest.fn().mockResolvedValue({}),
|
|
deleteMany: jest.fn().mockResolvedValue({ count: 0 }),
|
|
},
|
|
uploadField: {
|
|
updateMany: jest.fn().mockResolvedValue({ count: 2 }),
|
|
},
|
|
}
|
|
prisma.$transaction.mockImplementation((callback: any) => callback(tx))
|
|
|
|
const response = await updateTemplateCategoryOptions(
|
|
jsonRequest('http://localhost/api/template-categories/brandName', { options: ['A', 'B'] }),
|
|
{ params: { fieldKey: 'brandName' } }
|
|
)
|
|
const data = await response.json()
|
|
|
|
expect(response.status).toBe(200)
|
|
expect(data.options).toEqual(['A', 'B'])
|
|
expect(tx.fieldOption.create).toHaveBeenCalledTimes(2)
|
|
expect(tx.uploadField.updateMany).toHaveBeenCalledWith({
|
|
where: {
|
|
key: 'brandName',
|
|
type: 'select',
|
|
addToCategory: true,
|
|
},
|
|
data: {
|
|
optionsStatic: JSON.stringify(['A', 'B']),
|
|
},
|
|
})
|
|
})
|
|
})
|