744 lines
26 KiB
TypeScript
744 lines
26 KiB
TypeScript
import { NextRequest } from 'next/server'
|
|
|
|
jest.mock('@/lib/auth', () => ({
|
|
authOptions: {},
|
|
}))
|
|
|
|
jest.mock('next-auth', () => ({
|
|
getServerSession: jest.fn(),
|
|
}))
|
|
|
|
jest.mock('@/lib/prisma', () => ({
|
|
prisma: {
|
|
user: {
|
|
count: jest.fn(),
|
|
findMany: jest.fn(),
|
|
findUnique: jest.fn(),
|
|
findFirst: jest.fn(),
|
|
delete: jest.fn(),
|
|
update: jest.fn(),
|
|
updateMany: jest.fn(),
|
|
},
|
|
department: {
|
|
findMany: jest.fn(),
|
|
},
|
|
$transaction: jest.fn(),
|
|
},
|
|
}))
|
|
|
|
jest.mock('@/lib/license', () => ({
|
|
planDepartmentScopeWithPaidCore: jest.fn(),
|
|
}))
|
|
|
|
const { getServerSession } = require('next-auth')
|
|
const { prisma } = require('@/lib/prisma')
|
|
const { planDepartmentScopeWithPaidCore } = require('@/lib/license')
|
|
|
|
import { GET as listUsers, DELETE as deleteUser } from '../route'
|
|
import { POST as updateUser } from '../update/route'
|
|
import { POST as updateDepartment } from '../update-department/route'
|
|
import { POST as setRole } from '../set-role/route'
|
|
import { POST as toggleActive } from '../toggle-active/route'
|
|
import { POST as toggleDownload } from '../toggle-download/route'
|
|
import { POST as updateHierarchy } from '../update-hierarchy/route'
|
|
import {
|
|
POST as setDepartmentManager,
|
|
DELETE as removeDepartmentManager,
|
|
} from '../set-department-manager/route'
|
|
|
|
describe('admin user organization permissions', () => {
|
|
beforeEach(() => {
|
|
jest.resetAllMocks()
|
|
planDepartmentScopeWithPaidCore.mockResolvedValue({
|
|
valid: true,
|
|
scopedDepartments: ['设计部', '设计部-建模组'],
|
|
withinScope: true,
|
|
})
|
|
})
|
|
|
|
it('最高权限账号可以查看全量用户列表', async () => {
|
|
getServerSession.mockResolvedValue({ user: { id: 'super-1', role: 'ADMIN' } })
|
|
prisma.user.findUnique.mockResolvedValue({
|
|
id: 'super-1',
|
|
role: 'ADMIN',
|
|
department: '管理部',
|
|
managedDepartment: null,
|
|
isSuperAdmin: true,
|
|
})
|
|
prisma.user.count.mockResolvedValue(2)
|
|
prisma.user.findMany.mockResolvedValue([
|
|
{ id: 'super-1', username: '最高权限账号' },
|
|
{ id: 'user-2', username: '员工A' },
|
|
])
|
|
|
|
const response = await listUsers(
|
|
new NextRequest('http://localhost/api/admin/users?page=1&pageSize=20', { method: 'GET' })
|
|
)
|
|
const data = await response.json()
|
|
|
|
expect(response.status).toBe(200)
|
|
expect(data.pagination.total).toBe(2)
|
|
expect(prisma.department.findMany).not.toHaveBeenCalled()
|
|
expect(planDepartmentScopeWithPaidCore).not.toHaveBeenCalled()
|
|
expect(prisma.user.count).toHaveBeenCalledWith({ where: {} })
|
|
})
|
|
|
|
it('部门管理员只能查看管理部门树范围内的用户列表', async () => {
|
|
getServerSession.mockResolvedValue({ user: { id: 'manager-1', role: 'ADMIN' } })
|
|
prisma.user.findUnique.mockResolvedValue({
|
|
id: 'manager-1',
|
|
role: 'ADMIN',
|
|
department: '设计部',
|
|
managedDepartment: '设计部',
|
|
isSuperAdmin: false,
|
|
})
|
|
prisma.department.findMany.mockResolvedValue([
|
|
{ name: '设计部' },
|
|
{ name: '设计部-建模组' },
|
|
{ name: '财务部' },
|
|
])
|
|
prisma.user.count.mockResolvedValue(2)
|
|
prisma.user.findMany.mockResolvedValue([
|
|
{ id: 'manager-1', username: '设计主管' },
|
|
{ id: 'user-2', username: '建模员工' },
|
|
])
|
|
|
|
const response = await listUsers(
|
|
new NextRequest('http://localhost/api/admin/users?search=建模', { method: 'GET' })
|
|
)
|
|
const data = await response.json()
|
|
|
|
expect(response.status).toBe(200)
|
|
expect(data.users).toHaveLength(2)
|
|
expect(planDepartmentScopeWithPaidCore).toHaveBeenCalledWith({
|
|
scopeDepartment: '设计部',
|
|
departments: [
|
|
{ name: '设计部' },
|
|
{ name: '设计部-建模组' },
|
|
{ name: '财务部' },
|
|
],
|
|
})
|
|
expect(prisma.user.count).toHaveBeenCalledWith({
|
|
where: {
|
|
AND: [
|
|
{
|
|
OR: [
|
|
{ id: 'manager-1' },
|
|
{ department: { in: ['设计部', '设计部-建模组'] } },
|
|
{ department: { startsWith: '设计部-' } },
|
|
],
|
|
},
|
|
{
|
|
OR: [
|
|
{ username: { contains: '建模', mode: 'insensitive' } },
|
|
{ email: { contains: '建模', mode: 'insensitive' } },
|
|
{ department: { contains: '建模', mode: 'insensitive' } },
|
|
],
|
|
},
|
|
],
|
|
},
|
|
})
|
|
})
|
|
|
|
it('没有管理部门的普通管理员只能在用户列表中看到自己', async () => {
|
|
getServerSession.mockResolvedValue({ user: { id: 'admin-1', role: 'ADMIN' } })
|
|
prisma.user.findUnique.mockResolvedValue({
|
|
id: 'admin-1',
|
|
role: 'ADMIN',
|
|
department: '',
|
|
managedDepartment: null,
|
|
isSuperAdmin: false,
|
|
})
|
|
prisma.user.count.mockResolvedValue(1)
|
|
prisma.user.findMany.mockResolvedValue([{ id: 'admin-1', username: '普通管理员' }])
|
|
|
|
const response = await listUsers(
|
|
new NextRequest('http://localhost/api/admin/users', { method: 'GET' })
|
|
)
|
|
const data = await response.json()
|
|
|
|
expect(response.status).toBe(200)
|
|
expect(data.users).toHaveLength(1)
|
|
expect(prisma.user.count).toHaveBeenCalledWith({
|
|
where: { AND: [{ id: 'admin-1' }] },
|
|
})
|
|
})
|
|
|
|
it('普通管理员不能删除用户', async () => {
|
|
getServerSession.mockResolvedValue({ user: { id: 'admin-1', role: 'ADMIN' } })
|
|
prisma.user.findUnique.mockResolvedValue({ isSuperAdmin: false })
|
|
|
|
const response = await deleteUser(
|
|
new NextRequest('http://localhost/api/admin/users?id=user-2', { method: 'DELETE' })
|
|
)
|
|
const data = await response.json()
|
|
|
|
expect(response.status).toBe(403)
|
|
expect(data.error).toContain('最高权限')
|
|
expect(prisma.user.delete).not.toHaveBeenCalled()
|
|
})
|
|
|
|
it('最高权限账号不能删除另一个最高权限账号', async () => {
|
|
getServerSession.mockResolvedValue({ user: { id: 'super-1', role: 'ADMIN' } })
|
|
prisma.user.findUnique
|
|
.mockResolvedValueOnce({ isSuperAdmin: true })
|
|
.mockResolvedValueOnce({ isSuperAdmin: true })
|
|
|
|
const response = await deleteUser(
|
|
new NextRequest('http://localhost/api/admin/users?id=super-2', { method: 'DELETE' })
|
|
)
|
|
const data = await response.json()
|
|
|
|
expect(response.status).toBe(403)
|
|
expect(data.error).toContain('最高权限账号')
|
|
expect(prisma.user.delete).not.toHaveBeenCalled()
|
|
})
|
|
|
|
it('最高权限账号可以删除普通用户', async () => {
|
|
getServerSession.mockResolvedValue({ user: { id: 'super-1', role: 'ADMIN' } })
|
|
prisma.user.findUnique
|
|
.mockResolvedValueOnce({ isSuperAdmin: true })
|
|
.mockResolvedValueOnce({ isSuperAdmin: false })
|
|
prisma.user.delete.mockResolvedValue({ id: 'user-2' })
|
|
|
|
const response = await deleteUser(
|
|
new NextRequest('http://localhost/api/admin/users?id=user-2', { method: 'DELETE' })
|
|
)
|
|
const data = await response.json()
|
|
|
|
expect(response.status).toBe(200)
|
|
expect(data.message).toBe('删除成功')
|
|
expect(prisma.user.delete).toHaveBeenCalledWith({ where: { id: 'user-2' } })
|
|
})
|
|
|
|
it('普通管理员不能调整用户部门', async () => {
|
|
getServerSession.mockResolvedValue({ user: { id: 'admin-1', role: 'ADMIN' } })
|
|
prisma.user.findUnique.mockResolvedValue({ isSuperAdmin: false })
|
|
|
|
const response = await updateDepartment(
|
|
new NextRequest('http://localhost/api/admin/users/update-department', {
|
|
method: 'POST',
|
|
body: JSON.stringify({ userId: 'user-2', department: '设计部' }),
|
|
})
|
|
)
|
|
const data = await response.json()
|
|
|
|
expect(response.status).toBe(403)
|
|
expect(data.error).toContain('最高权限')
|
|
expect(prisma.user.update).not.toHaveBeenCalled()
|
|
})
|
|
|
|
it('部门管理员不能通过统一更新接口提升用户角色', async () => {
|
|
getServerSession.mockResolvedValue({ user: { id: 'manager-1', role: 'ADMIN' } })
|
|
prisma.user.findUnique
|
|
.mockResolvedValueOnce({ id: 'manager-1', managedDepartment: '设计部' })
|
|
.mockResolvedValueOnce({ id: 'manager-1', isSuperAdmin: false })
|
|
.mockResolvedValueOnce({ id: 'user-2', username: '员工A', department: '设计部' })
|
|
.mockResolvedValueOnce({ id: 'user-2', isSuperAdmin: false })
|
|
prisma.department.findMany.mockResolvedValue([{ name: '设计部' }])
|
|
|
|
const response = await updateUser(
|
|
new NextRequest('http://localhost/api/admin/users/update', {
|
|
method: 'POST',
|
|
body: JSON.stringify({ userId: 'user-2', username: '员工A', role: 'ADMIN' }),
|
|
})
|
|
)
|
|
const data = await response.json()
|
|
|
|
expect(response.status).toBe(403)
|
|
expect(planDepartmentScopeWithPaidCore).toHaveBeenCalledWith({
|
|
scopeDepartment: '设计部',
|
|
targetDepartment: '设计部',
|
|
departments: [{ name: '设计部' }],
|
|
})
|
|
expect(data.error).toContain('最高权限')
|
|
expect(prisma.$transaction).not.toHaveBeenCalled()
|
|
})
|
|
|
|
it('部门管理员不能通过统一更新接口调整部门和下载权限', async () => {
|
|
getServerSession.mockResolvedValue({ user: { id: 'manager-1', role: 'ADMIN' } })
|
|
prisma.user.findUnique
|
|
.mockResolvedValueOnce({ id: 'manager-1', managedDepartment: '设计部' })
|
|
.mockResolvedValueOnce({ id: 'manager-1', isSuperAdmin: false })
|
|
.mockResolvedValueOnce({ id: 'user-2', username: '员工A', department: '设计部' })
|
|
.mockResolvedValueOnce({ id: 'user-2', isSuperAdmin: false })
|
|
prisma.department.findMany.mockResolvedValue([{ name: '设计部' }])
|
|
|
|
const response = await updateUser(
|
|
new NextRequest('http://localhost/api/admin/users/update', {
|
|
method: 'POST',
|
|
body: JSON.stringify({
|
|
userId: 'user-2',
|
|
department: '财务部',
|
|
canDownload: false,
|
|
}),
|
|
})
|
|
)
|
|
const data = await response.json()
|
|
|
|
expect(response.status).toBe(403)
|
|
expect(planDepartmentScopeWithPaidCore).toHaveBeenCalledWith({
|
|
scopeDepartment: '设计部',
|
|
targetDepartment: '设计部',
|
|
departments: [{ name: '设计部' }],
|
|
})
|
|
expect(data.error).toContain('最高权限')
|
|
expect(prisma.$transaction).not.toHaveBeenCalled()
|
|
})
|
|
|
|
it('部门管理员可以在部门不变时修改成员姓名但不会批量改历史素材作者名', async () => {
|
|
getServerSession.mockResolvedValue({ user: { id: 'manager-1', role: 'ADMIN' } })
|
|
prisma.user.findUnique
|
|
.mockResolvedValueOnce({ id: 'manager-1', managedDepartment: '设计部' })
|
|
.mockResolvedValueOnce({ id: 'manager-1', isSuperAdmin: false })
|
|
.mockResolvedValueOnce({ id: 'user-2', username: '旧姓名', department: '设计部' })
|
|
.mockResolvedValueOnce({ id: 'user-2', isSuperAdmin: false })
|
|
prisma.department.findMany.mockResolvedValue([{ name: '设计部' }])
|
|
|
|
const tx = {
|
|
user: {
|
|
update: jest.fn().mockResolvedValue({
|
|
id: 'user-2',
|
|
username: '新姓名',
|
|
department: '设计部',
|
|
}),
|
|
},
|
|
material: {
|
|
updateMany: jest.fn().mockResolvedValue({ count: 2 }),
|
|
},
|
|
}
|
|
prisma.$transaction.mockImplementation((callback: any) => callback(tx))
|
|
|
|
const response = await updateUser(
|
|
new NextRequest('http://localhost/api/admin/users/update', {
|
|
method: 'POST',
|
|
body: JSON.stringify({
|
|
userId: 'user-2',
|
|
username: '新姓名',
|
|
department: '设计部',
|
|
}),
|
|
})
|
|
)
|
|
const data = await response.json()
|
|
|
|
expect(response.status).toBe(200)
|
|
expect(planDepartmentScopeWithPaidCore).toHaveBeenCalledWith({
|
|
scopeDepartment: '设计部',
|
|
targetDepartment: '设计部',
|
|
departments: [{ name: '设计部' }],
|
|
})
|
|
expect(data.materialsUpdated).toBe(false)
|
|
expect(tx.user.update).toHaveBeenCalledWith({
|
|
where: { id: 'user-2' },
|
|
data: {
|
|
username: '新姓名',
|
|
department: '设计部',
|
|
},
|
|
})
|
|
expect(tx.material.updateMany).not.toHaveBeenCalled()
|
|
})
|
|
|
|
it('最高权限账号显式要求时才会同步历史素材作者名', async () => {
|
|
getServerSession.mockResolvedValue({ user: { id: 'super-1', role: 'ADMIN' } })
|
|
prisma.user.findUnique
|
|
.mockResolvedValueOnce({ id: 'super-1', managedDepartment: null })
|
|
.mockResolvedValueOnce({ id: 'super-1', isSuperAdmin: true })
|
|
.mockResolvedValueOnce({ id: 'user-2', username: '旧姓名', department: '设计部' })
|
|
.mockResolvedValueOnce({ id: 'user-2', isSuperAdmin: false })
|
|
|
|
const tx = {
|
|
user: {
|
|
update: jest.fn().mockResolvedValue({
|
|
id: 'user-2',
|
|
username: '新姓名',
|
|
department: '设计部',
|
|
}),
|
|
},
|
|
material: {
|
|
updateMany: jest.fn().mockResolvedValue({ count: 2 }),
|
|
},
|
|
}
|
|
prisma.$transaction.mockImplementation((callback: any) => callback(tx))
|
|
|
|
const response = await updateUser(
|
|
new NextRequest('http://localhost/api/admin/users/update', {
|
|
method: 'POST',
|
|
body: JSON.stringify({
|
|
userId: 'user-2',
|
|
username: '新姓名',
|
|
syncMaterialAuthorName: true,
|
|
}),
|
|
})
|
|
)
|
|
const data = await response.json()
|
|
|
|
expect(response.status).toBe(200)
|
|
expect(data.materialsUpdated).toBe(true)
|
|
expect(tx.user.update).toHaveBeenCalledWith({
|
|
where: { id: 'user-2' },
|
|
data: {
|
|
username: '新姓名',
|
|
},
|
|
})
|
|
expect(tx.material.updateMany).toHaveBeenCalledWith({
|
|
where: { userId: 'user-2' },
|
|
data: { authorName: '新姓名' },
|
|
})
|
|
})
|
|
|
|
it('普通管理员不能通过旧接口修改用户角色', async () => {
|
|
getServerSession.mockResolvedValue({ user: { id: 'admin-1', role: 'ADMIN' } })
|
|
prisma.user.findUnique.mockResolvedValue({ isSuperAdmin: false })
|
|
|
|
const response = await setRole(
|
|
new NextRequest('http://localhost/api/admin/users/set-role', {
|
|
method: 'POST',
|
|
body: JSON.stringify({ userId: 'user-2', role: 'ADMIN' }),
|
|
})
|
|
)
|
|
const data = await response.json()
|
|
|
|
expect(response.status).toBe(403)
|
|
expect(data.error).toContain('最高权限')
|
|
expect(prisma.user.update).not.toHaveBeenCalled()
|
|
})
|
|
|
|
it('最高权限账号可以通过旧接口修改普通用户角色', async () => {
|
|
getServerSession.mockResolvedValue({ user: { id: 'super-1', role: 'ADMIN', email: 'super@example.com' } })
|
|
prisma.user.findUnique
|
|
.mockResolvedValueOnce({ isSuperAdmin: true })
|
|
.mockResolvedValueOnce({ id: 'user-2', email: 'user2@example.com', department: '设计部' })
|
|
.mockResolvedValueOnce({ isSuperAdmin: false })
|
|
prisma.user.update.mockResolvedValue({
|
|
id: 'user-2',
|
|
email: 'user2@example.com',
|
|
username: 'user2',
|
|
role: 'ADMIN',
|
|
})
|
|
|
|
const response = await setRole(
|
|
new NextRequest('http://localhost/api/admin/users/set-role', {
|
|
method: 'POST',
|
|
body: JSON.stringify({ userId: 'user-2', role: 'ADMIN' }),
|
|
})
|
|
)
|
|
const data = await response.json()
|
|
|
|
expect(response.status).toBe(200)
|
|
expect(data.user.role).toBe('ADMIN')
|
|
expect(prisma.user.update).toHaveBeenCalledWith({
|
|
where: { id: 'user-2' },
|
|
data: { role: 'ADMIN' },
|
|
})
|
|
})
|
|
|
|
it('最高权限账号把用户降为普通用户时会清空管理部门', async () => {
|
|
getServerSession.mockResolvedValue({ user: { id: 'super-1', role: 'ADMIN', email: 'super@example.com' } })
|
|
prisma.user.findUnique
|
|
.mockResolvedValueOnce({ isSuperAdmin: true })
|
|
.mockResolvedValueOnce({ id: 'user-2', email: 'user2@example.com', department: '设计部' })
|
|
.mockResolvedValueOnce({ isSuperAdmin: false })
|
|
prisma.user.update.mockResolvedValue({
|
|
id: 'user-2',
|
|
email: 'user2@example.com',
|
|
username: 'user2',
|
|
role: 'USER',
|
|
})
|
|
|
|
const response = await setRole(
|
|
new NextRequest('http://localhost/api/admin/users/set-role', {
|
|
method: 'POST',
|
|
body: JSON.stringify({ userId: 'user-2', role: 'USER' }),
|
|
})
|
|
)
|
|
|
|
expect(response.status).toBe(200)
|
|
expect(prisma.user.update).toHaveBeenCalledWith({
|
|
where: { id: 'user-2' },
|
|
data: { role: 'USER', managedDepartment: null },
|
|
})
|
|
})
|
|
|
|
it('普通管理员不能通过旧接口启用或禁用用户', async () => {
|
|
getServerSession.mockResolvedValue({ user: { id: 'admin-1', role: 'ADMIN' } })
|
|
prisma.user.findUnique.mockResolvedValue({ isSuperAdmin: false })
|
|
|
|
const response = await toggleActive(
|
|
new NextRequest('http://localhost/api/admin/users/toggle-active', {
|
|
method: 'POST',
|
|
body: JSON.stringify({ userId: 'user-2', isActive: false }),
|
|
})
|
|
)
|
|
const data = await response.json()
|
|
|
|
expect(response.status).toBe(403)
|
|
expect(data.error).toContain('最高权限')
|
|
expect(prisma.user.update).not.toHaveBeenCalled()
|
|
})
|
|
|
|
it('最高权限账号可以通过旧接口启用或禁用普通用户', async () => {
|
|
getServerSession.mockResolvedValue({ user: { id: 'super-1', role: 'ADMIN' } })
|
|
prisma.user.findUnique
|
|
.mockResolvedValueOnce({ isSuperAdmin: true })
|
|
.mockResolvedValueOnce({ id: 'user-2', department: '设计部', isSuperAdmin: false })
|
|
prisma.user.update.mockResolvedValue({ id: 'user-2', isActive: false })
|
|
|
|
const response = await toggleActive(
|
|
new NextRequest('http://localhost/api/admin/users/toggle-active', {
|
|
method: 'POST',
|
|
body: JSON.stringify({ userId: 'user-2', isActive: false }),
|
|
})
|
|
)
|
|
const data = await response.json()
|
|
|
|
expect(response.status).toBe(200)
|
|
expect(data.message).toContain('禁用')
|
|
expect(prisma.user.update).toHaveBeenCalledWith({
|
|
where: { id: 'user-2' },
|
|
data: { isActive: false },
|
|
})
|
|
})
|
|
|
|
it('普通管理员不能通过旧接口修改下载权限', async () => {
|
|
getServerSession.mockResolvedValue({ user: { id: 'admin-1', role: 'ADMIN' } })
|
|
prisma.user.findUnique.mockResolvedValue({ isSuperAdmin: false })
|
|
|
|
const response = await toggleDownload(
|
|
new NextRequest('http://localhost/api/admin/users/toggle-download', {
|
|
method: 'POST',
|
|
body: JSON.stringify({ userId: 'user-2' }),
|
|
})
|
|
)
|
|
const data = await response.json()
|
|
|
|
expect(response.status).toBe(403)
|
|
expect(data.error).toContain('最高权限')
|
|
expect(prisma.user.update).not.toHaveBeenCalled()
|
|
})
|
|
|
|
it('最高权限账号可以通过旧接口修改普通用户下载权限', async () => {
|
|
getServerSession.mockResolvedValue({ user: { id: 'super-1', role: 'ADMIN' } })
|
|
prisma.user.findUnique
|
|
.mockResolvedValueOnce({ isSuperAdmin: true })
|
|
.mockResolvedValueOnce({
|
|
id: 'user-2',
|
|
department: '设计部',
|
|
role: 'USER',
|
|
canDownload: true,
|
|
isSuperAdmin: false,
|
|
})
|
|
prisma.user.update.mockResolvedValue({ id: 'user-2', canDownload: false })
|
|
|
|
const response = await toggleDownload(
|
|
new NextRequest('http://localhost/api/admin/users/toggle-download', {
|
|
method: 'POST',
|
|
body: JSON.stringify({ userId: 'user-2' }),
|
|
})
|
|
)
|
|
const data = await response.json()
|
|
|
|
expect(response.status).toBe(200)
|
|
expect(data.canDownload).toBe(false)
|
|
expect(prisma.user.update).toHaveBeenCalledWith({
|
|
where: { id: 'user-2' },
|
|
data: { canDownload: false },
|
|
})
|
|
})
|
|
|
|
it('普通管理员不能通过旧接口调整用户层级', async () => {
|
|
getServerSession.mockResolvedValue({ user: { id: 'admin-1', role: 'ADMIN' } })
|
|
prisma.user.findUnique.mockResolvedValue({ isSuperAdmin: false })
|
|
|
|
const response = await updateHierarchy(
|
|
new NextRequest('http://localhost/api/admin/users/update-hierarchy', {
|
|
method: 'POST',
|
|
body: JSON.stringify({ userId: 'user-2', parentId: 'manager-1' }),
|
|
})
|
|
)
|
|
const data = await response.json()
|
|
|
|
expect(response.status).toBe(403)
|
|
expect(data.error).toContain('最高权限')
|
|
expect(prisma.user.update).not.toHaveBeenCalled()
|
|
})
|
|
|
|
it('最高权限账号可以通过旧接口调整普通用户层级', async () => {
|
|
getServerSession.mockResolvedValue({ user: { id: 'super-1', role: 'ADMIN' } })
|
|
prisma.user.findUnique
|
|
.mockResolvedValueOnce({ isSuperAdmin: true })
|
|
.mockResolvedValueOnce({ id: 'super-1', isSuperAdmin: true })
|
|
.mockResolvedValueOnce({ id: 'user-2', parentId: null, isSuperAdmin: false })
|
|
prisma.user.findMany.mockResolvedValue([
|
|
{ id: 'super-1', parentId: null },
|
|
{ id: 'user-2', parentId: null },
|
|
])
|
|
prisma.user.update.mockResolvedValue({ id: 'user-2', parentId: null })
|
|
|
|
const response = await updateHierarchy(
|
|
new NextRequest('http://localhost/api/admin/users/update-hierarchy', {
|
|
method: 'POST',
|
|
body: JSON.stringify({ userId: 'user-2', parentId: null }),
|
|
})
|
|
)
|
|
const data = await response.json()
|
|
|
|
expect(response.status).toBe(200)
|
|
expect(data.success).toBe(true)
|
|
expect(prisma.user.update).toHaveBeenCalledWith({
|
|
where: { id: 'user-2' },
|
|
data: { parentId: null },
|
|
select: { id: true, parentId: true },
|
|
})
|
|
})
|
|
|
|
it('最高权限账号不能调整最高权限账号的部门', async () => {
|
|
getServerSession.mockResolvedValue({ user: { id: 'super-1', role: 'ADMIN' } })
|
|
prisma.user.findUnique
|
|
.mockResolvedValueOnce({ isSuperAdmin: true })
|
|
.mockResolvedValueOnce({ isSuperAdmin: true })
|
|
|
|
const response = await updateDepartment(
|
|
new NextRequest('http://localhost/api/admin/users/update-department', {
|
|
method: 'POST',
|
|
body: JSON.stringify({ userId: 'super-2', department: '设计部' }),
|
|
})
|
|
)
|
|
const data = await response.json()
|
|
|
|
expect(response.status).toBe(403)
|
|
expect(data.error).toContain('最高权限账号')
|
|
expect(prisma.user.update).not.toHaveBeenCalled()
|
|
})
|
|
|
|
it('最高权限账号可以调整普通用户部门', async () => {
|
|
getServerSession.mockResolvedValue({ user: { id: 'super-1', role: 'ADMIN' } })
|
|
prisma.user.findUnique
|
|
.mockResolvedValueOnce({ isSuperAdmin: true })
|
|
.mockResolvedValueOnce({ isSuperAdmin: false })
|
|
prisma.user.update.mockResolvedValue({ id: 'user-2', username: 'user2', department: '设计部' })
|
|
|
|
const response = await updateDepartment(
|
|
new NextRequest('http://localhost/api/admin/users/update-department', {
|
|
method: 'POST',
|
|
body: JSON.stringify({ userId: 'user-2', department: '设计部' }),
|
|
})
|
|
)
|
|
const data = await response.json()
|
|
|
|
expect(response.status).toBe(200)
|
|
expect(data.department).toBe('设计部')
|
|
expect(prisma.user.update).toHaveBeenCalledWith({
|
|
where: { id: 'user-2' },
|
|
data: { department: '设计部' },
|
|
})
|
|
})
|
|
|
|
it('普通管理员不能设置部门管理员', async () => {
|
|
getServerSession.mockResolvedValue({ user: { id: 'admin-1', role: 'ADMIN' } })
|
|
prisma.user.findUnique.mockResolvedValue({ isSuperAdmin: false })
|
|
|
|
const response = await setDepartmentManager(
|
|
new NextRequest('http://localhost/api/admin/users/set-department-manager', {
|
|
method: 'POST',
|
|
body: JSON.stringify({ userId: 'user-2', departmentName: '设计部' }),
|
|
})
|
|
)
|
|
const data = await response.json()
|
|
|
|
expect(response.status).toBe(403)
|
|
expect(data.error).toContain('最高权限')
|
|
expect(prisma.$transaction).not.toHaveBeenCalled()
|
|
})
|
|
|
|
it('最高权限账号不能把最高权限账号设置为部门管理员', async () => {
|
|
getServerSession.mockResolvedValue({ user: { id: 'super-1', role: 'ADMIN' } })
|
|
prisma.user.findUnique
|
|
.mockResolvedValueOnce({ isSuperAdmin: true })
|
|
.mockResolvedValueOnce({ isSuperAdmin: true })
|
|
|
|
const response = await setDepartmentManager(
|
|
new NextRequest('http://localhost/api/admin/users/set-department-manager', {
|
|
method: 'POST',
|
|
body: JSON.stringify({ userId: 'super-2', departmentName: '设计部' }),
|
|
})
|
|
)
|
|
const data = await response.json()
|
|
|
|
expect(response.status).toBe(403)
|
|
expect(data.error).toContain('最高权限账号')
|
|
expect(prisma.$transaction).not.toHaveBeenCalled()
|
|
})
|
|
|
|
it('最高权限账号可以设置普通用户为部门管理员', async () => {
|
|
getServerSession.mockResolvedValue({ user: { id: 'super-1', role: 'ADMIN' } })
|
|
prisma.user.findUnique
|
|
.mockResolvedValueOnce({ isSuperAdmin: true })
|
|
.mockResolvedValueOnce({ isSuperAdmin: false })
|
|
prisma.user.findFirst.mockResolvedValue(null)
|
|
const tx = {
|
|
user: {
|
|
update: jest.fn().mockResolvedValue({ id: 'user-2', managedDepartment: '设计部' }),
|
|
updateMany: jest.fn().mockResolvedValue({ count: 2 }),
|
|
},
|
|
}
|
|
prisma.$transaction.mockImplementation((callback: any) => callback(tx))
|
|
|
|
const response = await setDepartmentManager(
|
|
new NextRequest('http://localhost/api/admin/users/set-department-manager', {
|
|
method: 'POST',
|
|
body: JSON.stringify({ userId: 'user-2', departmentName: '设计部' }),
|
|
})
|
|
)
|
|
const data = await response.json()
|
|
|
|
expect(response.status).toBe(200)
|
|
expect(data.message).toContain('设置成功')
|
|
expect(tx.user.update).toHaveBeenCalledWith({
|
|
where: { id: 'user-2' },
|
|
data: {
|
|
managedDepartment: '设计部',
|
|
department: '设计部',
|
|
role: 'ADMIN',
|
|
},
|
|
})
|
|
})
|
|
|
|
it('普通管理员不能取消部门管理员', async () => {
|
|
getServerSession.mockResolvedValue({ user: { id: 'admin-1', role: 'ADMIN' } })
|
|
prisma.user.findUnique.mockResolvedValue({ isSuperAdmin: false })
|
|
|
|
const response = await removeDepartmentManager(
|
|
new NextRequest('http://localhost/api/admin/users/set-department-manager?userId=user-2', {
|
|
method: 'DELETE',
|
|
})
|
|
)
|
|
const data = await response.json()
|
|
|
|
expect(response.status).toBe(403)
|
|
expect(data.error).toContain('最高权限')
|
|
expect(prisma.user.update).not.toHaveBeenCalled()
|
|
})
|
|
|
|
it('最高权限账号可以取消普通用户的部门管理员状态', async () => {
|
|
getServerSession.mockResolvedValue({ user: { id: 'super-1', role: 'ADMIN' } })
|
|
prisma.user.findUnique
|
|
.mockResolvedValueOnce({ isSuperAdmin: true })
|
|
.mockResolvedValueOnce({ isSuperAdmin: false })
|
|
prisma.user.update.mockResolvedValue({ id: 'user-2', managedDepartment: null })
|
|
|
|
const response = await removeDepartmentManager(
|
|
new NextRequest('http://localhost/api/admin/users/set-department-manager?userId=user-2', {
|
|
method: 'DELETE',
|
|
})
|
|
)
|
|
const data = await response.json()
|
|
|
|
expect(response.status).toBe(200)
|
|
expect(data.message).toContain('已取消')
|
|
expect(prisma.user.update).toHaveBeenCalledWith({
|
|
where: { id: 'user-2' },
|
|
data: { managedDepartment: null, role: 'USER' },
|
|
})
|
|
})
|
|
})
|