feat(plan4): API CRUD /api/admin/users (TDD)
This commit is contained in:
@@ -0,0 +1,47 @@
|
||||
import { NextRequest, NextResponse } from 'next/server'
|
||||
import { getServerSession } from 'next-auth'
|
||||
import { authOptions } from '@/lib/auth'
|
||||
import { prisma } from '@/lib/prisma'
|
||||
import bcrypt from 'bcryptjs'
|
||||
|
||||
async function requireAdmin() {
|
||||
const session = await getServerSession(authOptions)
|
||||
if (!session || session.user.role !== 'admin') return null
|
||||
return session
|
||||
}
|
||||
|
||||
export async function PUT(req: NextRequest, { params }: { params: { id: string } }) {
|
||||
const session = await requireAdmin()
|
||||
if (!session) return NextResponse.json({ error: 'Forbidden' }, { status: 403 })
|
||||
|
||||
const body = await req.json().catch(() => ({}))
|
||||
const { name, role, password } = body as Record<string, string>
|
||||
|
||||
const data: Record<string, string> = {}
|
||||
if (name) data.name = name
|
||||
if (role) data.role = role
|
||||
if (password) data.password = await bcrypt.hash(password, 12)
|
||||
|
||||
if (Object.keys(data).length === 0) {
|
||||
return NextResponse.json({ error: 'Aucun champ à mettre à jour' }, { status: 400 })
|
||||
}
|
||||
|
||||
const user = await prisma.user.update({
|
||||
where: { id: params.id },
|
||||
data,
|
||||
select: { id: true, email: true, name: true, role: true, createdAt: true },
|
||||
})
|
||||
return NextResponse.json(user)
|
||||
}
|
||||
|
||||
export async function DELETE(req: NextRequest, { params }: { params: { id: string } }) {
|
||||
const session = await requireAdmin()
|
||||
if (!session) return NextResponse.json({ error: 'Forbidden' }, { status: 403 })
|
||||
|
||||
if (params.id === session.user.id) {
|
||||
return NextResponse.json({ error: 'Impossible de se supprimer soi-même' }, { status: 400 })
|
||||
}
|
||||
|
||||
await prisma.user.delete({ where: { id: params.id } })
|
||||
return NextResponse.json({ ok: true })
|
||||
}
|
||||
@@ -0,0 +1,61 @@
|
||||
import { GET, POST } from './route'
|
||||
import { getServerSession } from 'next-auth'
|
||||
import { prisma } from '@/lib/prisma'
|
||||
import bcrypt from 'bcryptjs'
|
||||
|
||||
jest.mock('next-auth')
|
||||
jest.mock('@/lib/prisma', () => ({ prisma: { user: { findMany: jest.fn(), create: jest.fn() } } }))
|
||||
jest.mock('bcryptjs')
|
||||
|
||||
const mockSession = getServerSession as jest.MockedFunction<typeof getServerSession>
|
||||
const mockFindMany = prisma.user.findMany as jest.MockedFunction<typeof prisma.user.findMany>
|
||||
const mockCreate = prisma.user.create as jest.MockedFunction<typeof prisma.user.create>
|
||||
const mockHash = bcrypt.hash as jest.MockedFunction<typeof bcrypt.hash>
|
||||
|
||||
const adminSession = { user: { id: 'admin-id', role: 'admin' } }
|
||||
const gestSession = { user: { id: 'gest-id', role: 'gestionnaire' } }
|
||||
|
||||
describe('GET /api/admin/users', () => {
|
||||
it('retourne 403 si non admin', async () => {
|
||||
mockSession.mockResolvedValue(gestSession as never)
|
||||
const res = await GET(new Request('http://localhost') as never)
|
||||
expect(res.status).toBe(403)
|
||||
})
|
||||
|
||||
it('retourne la liste des utilisateurs', async () => {
|
||||
mockSession.mockResolvedValue(adminSession as never)
|
||||
mockFindMany.mockResolvedValue([
|
||||
{ id: '1', email: 'a@b.com', name: 'Alice', role: 'admin', createdAt: new Date() } as never,
|
||||
])
|
||||
const res = await GET(new Request('http://localhost') as never)
|
||||
const body = await res.json()
|
||||
expect(body).toHaveLength(1)
|
||||
expect(body[0].email).toBe('a@b.com')
|
||||
expect(body[0].password).toBeUndefined()
|
||||
})
|
||||
})
|
||||
|
||||
describe('POST /api/admin/users', () => {
|
||||
it('retourne 403 si non admin', async () => {
|
||||
mockSession.mockResolvedValue(gestSession as never)
|
||||
const res = await POST(new Request('http://localhost', { method: 'POST', body: '{}' }) as never)
|
||||
expect(res.status).toBe(403)
|
||||
})
|
||||
|
||||
it('crée un utilisateur avec mot de passe hashé', async () => {
|
||||
mockSession.mockResolvedValue(adminSession as never)
|
||||
mockHash.mockResolvedValue('hashed' as never)
|
||||
mockCreate.mockResolvedValue({ id: 'new', email: 'b@c.com', name: 'Bob', role: 'gestionnaire', createdAt: new Date() } as never)
|
||||
const body = JSON.stringify({ email: 'b@c.com', name: 'Bob', password: 'pass123', role: 'gestionnaire' })
|
||||
const res = await POST(new Request('http://localhost', { method: 'POST', body, headers: { 'Content-Type': 'application/json' } }) as never)
|
||||
expect(res.status).toBe(201)
|
||||
expect(mockHash).toHaveBeenCalledWith('pass123', 12)
|
||||
})
|
||||
|
||||
it('retourne 400 si champs manquants', async () => {
|
||||
mockSession.mockResolvedValue(adminSession as never)
|
||||
const body = JSON.stringify({ email: 'b@c.com' })
|
||||
const res = await POST(new Request('http://localhost', { method: 'POST', body, headers: { 'Content-Type': 'application/json' } }) as never)
|
||||
expect(res.status).toBe(400)
|
||||
})
|
||||
})
|
||||
@@ -0,0 +1,42 @@
|
||||
import { NextRequest, NextResponse } from 'next/server'
|
||||
import { getServerSession } from 'next-auth'
|
||||
import { authOptions } from '@/lib/auth'
|
||||
import { prisma } from '@/lib/prisma'
|
||||
import bcrypt from 'bcryptjs'
|
||||
|
||||
async function requireAdmin() {
|
||||
const session = await getServerSession(authOptions)
|
||||
if (!session || session.user.role !== 'admin') return null
|
||||
return session
|
||||
}
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
||||
export async function GET(_req: NextRequest) {
|
||||
const session = await requireAdmin()
|
||||
if (!session) return NextResponse.json({ error: 'Forbidden' }, { status: 403 })
|
||||
|
||||
const users = await prisma.user.findMany({
|
||||
orderBy: { createdAt: 'asc' },
|
||||
select: { id: true, email: true, name: true, role: true, createdAt: true },
|
||||
})
|
||||
return NextResponse.json(users)
|
||||
}
|
||||
|
||||
export async function POST(req: NextRequest) {
|
||||
const session = await requireAdmin()
|
||||
if (!session) return NextResponse.json({ error: 'Forbidden' }, { status: 403 })
|
||||
|
||||
const body = await req.json().catch(() => ({}))
|
||||
const { email, name, password, role } = body as Record<string, string>
|
||||
|
||||
if (!email || !name || !password || !role) {
|
||||
return NextResponse.json({ error: 'email, name, password et role requis' }, { status: 400 })
|
||||
}
|
||||
|
||||
const hashed = await bcrypt.hash(password, 12)
|
||||
const user = await prisma.user.create({
|
||||
data: { email, name, password: hashed, role },
|
||||
select: { id: true, email: true, name: true, role: true, createdAt: true },
|
||||
})
|
||||
return NextResponse.json(user, { status: 201 })
|
||||
}
|
||||
Reference in New Issue
Block a user