2026-05-28 15:11:36 +08:00

172 lines
5.4 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: {
findUnique: jest.fn(),
},
invitation: {
count: jest.fn(),
findMany: jest.fn(),
create: jest.fn(),
update: jest.fn(),
delete: jest.fn(),
},
},
}))
jest.mock('@/lib/config', () => ({
resolveBaseUrl: jest.fn(() => 'http://localhost:3000'),
}))
const { getServerSession } = require('next-auth')
const { prisma } = require('@/lib/prisma')
const { resolveBaseUrl } = require('@/lib/config')
import { GET, POST } from '../route'
import { PATCH, DELETE } from '../[id]/route'
function jsonRequest(url: string, body: unknown, method = 'POST') {
return new NextRequest(url, {
method,
body: JSON.stringify(body),
})
}
describe('邀请链接管理权限', () => {
beforeEach(() => {
jest.resetAllMocks()
resolveBaseUrl.mockReturnValue('http://localhost:3000')
getServerSession.mockResolvedValue({
user: { id: 'admin-1', role: 'ADMIN', isSuperAdmin: false },
})
prisma.user.findUnique.mockResolvedValue({ isSuperAdmin: false })
})
it('普通管理员不能查看或创建邀请链接', async () => {
const listResponse = await GET(new NextRequest('http://localhost/api/invitations'))
const createResponse = await POST(
jsonRequest('http://localhost/api/invitations', { maxUses: 1, expiresInDays: null })
)
expect(listResponse.status).toBe(403)
expect(createResponse.status).toBe(403)
expect(prisma.invitation.findMany).not.toHaveBeenCalled()
expect(prisma.invitation.create).not.toHaveBeenCalled()
})
it('旧会话带最高权限标记但数据库已撤销时不能创建邀请链接', async () => {
getServerSession.mockResolvedValue({
user: { id: 'admin-1', role: 'ADMIN', isSuperAdmin: true },
})
prisma.user.findUnique.mockResolvedValue({ isSuperAdmin: false })
const response = await POST(
jsonRequest('http://localhost/api/invitations', { maxUses: 1, expiresInDays: null })
)
expect(response.status).toBe(403)
expect(prisma.invitation.create).not.toHaveBeenCalled()
})
it('最高权限账号可以创建并查看邀请链接', async () => {
getServerSession.mockResolvedValue({
user: { id: 'super-1', role: 'ADMIN', isSuperAdmin: true },
})
prisma.user.findUnique.mockResolvedValue({ isSuperAdmin: true })
prisma.invitation.create.mockResolvedValue({
id: 'invite-1',
code: 'ABCD1234',
createdBy: 'super-1',
maxUses: 3,
currentUses: 0,
expiresAt: null,
isActive: true,
createdAt: new Date('2026-05-28T00:00:00.000Z'),
})
prisma.invitation.count.mockResolvedValue(1)
prisma.invitation.findMany.mockResolvedValue([
{
id: 'invite-1',
code: 'ABCD1234',
createdBy: 'super-1',
maxUses: 3,
currentUses: 0,
expiresAt: null,
isActive: true,
createdAt: new Date('2026-05-28T00:00:00.000Z'),
},
])
const createResponse = await POST(
jsonRequest('http://localhost/api/invitations', { maxUses: 3, expiresInDays: null })
)
const listResponse = await GET(new NextRequest('http://localhost/api/invitations'))
const listData = await listResponse.json()
expect(createResponse.status).toBe(200)
expect(listResponse.status).toBe(200)
expect(prisma.invitation.create).toHaveBeenCalledWith(
expect.objectContaining({
data: expect.objectContaining({
createdBy: 'super-1',
maxUses: 3,
}),
})
)
expect(listData.invitations[0].inviteUrl).toBe('http://localhost:3000/register?invite=ABCD1234')
})
it('普通管理员不能启停或删除邀请链接', async () => {
const patchResponse = await PATCH(
jsonRequest('http://localhost/api/invitations/invite-1', { isActive: false }, 'PATCH'),
{ params: { id: 'invite-1' } }
)
const deleteResponse = await DELETE(
new NextRequest('http://localhost/api/invitations/invite-1', { method: 'DELETE' }),
{ params: { id: 'invite-1' } }
)
expect(patchResponse.status).toBe(403)
expect(deleteResponse.status).toBe(403)
expect(prisma.invitation.update).not.toHaveBeenCalled()
expect(prisma.invitation.delete).not.toHaveBeenCalled()
})
it('最高权限账号可以启停和删除邀请链接', async () => {
getServerSession.mockResolvedValue({
user: { id: 'super-1', role: 'ADMIN', isSuperAdmin: true },
})
prisma.user.findUnique.mockResolvedValue({ isSuperAdmin: true })
prisma.invitation.update.mockResolvedValue({ id: 'invite-1', isActive: false })
prisma.invitation.delete.mockResolvedValue({ id: 'invite-1' })
const patchResponse = await PATCH(
jsonRequest('http://localhost/api/invitations/invite-1', { isActive: false }, 'PATCH'),
{ params: { id: 'invite-1' } }
)
const deleteResponse = await DELETE(
new NextRequest('http://localhost/api/invitations/invite-1', { method: 'DELETE' }),
{ params: { id: 'invite-1' } }
)
expect(patchResponse.status).toBe(200)
expect(deleteResponse.status).toBe(200)
expect(prisma.invitation.update).toHaveBeenCalledWith({
where: { id: 'invite-1' },
data: { isActive: false },
})
expect(prisma.invitation.delete).toHaveBeenCalledWith({
where: { id: 'invite-1' },
})
})
})