import { beforeEach, describe, expect, it, vi } from 'vitest' import type { Request, Response } from 'express' import { eliminarBorrador, guardarBorrador, listarBorradores, obtenerBorrador, } from './borradores.controller.js' // ---------- Prisma mockeado (los tests no tocan la BD) ---------- const prismaMock = vi.hoisted(() => ({ borradorAlta: { findMany: vi.fn(), findUnique: vi.fn(), create: vi.fn(), update: vi.fn(), delete: vi.fn(), }, })) vi.mock('../services/prisma.js', () => ({ prisma: prismaMock })) function reqFake(overrides: Partial = {}): Request { return { params: {}, body: {}, usuarioToken: { sub: 'u-1', username: 'asesor uno', rol: 'vendedor' }, ...overrides, } as Request } function resFake() { const json = vi.fn() const end = vi.fn() const status = vi.fn().mockReturnValue({ json, end }) return { res: { json, status } as unknown as Response, status, json, end } } const borradorFake = (overrides: Record = {}) => ({ id: 'b-1', usuarioId: 'u-1', nombre: 'Sin nombre', datosJson: '{"datosGenerales":{}}', paso: 0, createdAt: new Date(), updatedAt: new Date(), ...overrides, }) beforeEach(() => { vi.clearAllMocks() }) describe('listarBorradores', () => { it('devuelve solo los del usuario del token y SIN datosJson', async () => { prismaMock.borradorAlta.findMany.mockResolvedValue([{ id: 'b-1', nombre: 'X' }]) const { res, json } = resFake() await listarBorradores(reqFake(), res) const args = prismaMock.borradorAlta.findMany.mock.calls[0]![0] expect(args.where).toEqual({ usuarioId: 'u-1' }) expect(args.select).not.toHaveProperty('datosJson') expect(json).toHaveBeenCalledWith([{ id: 'b-1', nombre: 'X' }]) }) }) describe('guardarBorrador', () => { it('crea un borrador nuevo (201) con etiqueta del nombre del cliente', async () => { prismaMock.borradorAlta.create.mockResolvedValue(borradorFake()) const { res, status } = resFake() const datos = { datosGenerales: { nombreCompleto: 'JUAN PEREZ' } } await guardarBorrador(reqFake({ body: { paso: 2, datos } }), res) const creado = prismaMock.borradorAlta.create.mock.calls[0]![0].data expect(creado.usuarioId).toBe('u-1') expect(creado.nombre).toBe('JUAN PEREZ') expect(creado.paso).toBe(2) expect(JSON.parse(creado.datosJson)).toEqual(datos) expect(status).toHaveBeenCalledWith(201) }) it('usa "Sin nombre" cuando el cliente aún no tiene nombre', async () => { prismaMock.borradorAlta.create.mockResolvedValue(borradorFake()) const { res } = resFake() await guardarBorrador(reqFake({ body: { paso: 0, datos: {} } }), res) expect(prismaMock.borradorAlta.create.mock.calls[0]![0].data.nombre).toBe('Sin nombre') }) it('con id actualiza el borrador propio (upsert)', async () => { prismaMock.borradorAlta.findUnique.mockResolvedValue(borradorFake()) prismaMock.borradorAlta.update.mockResolvedValue(borradorFake({ paso: 5 })) const { res, json } = resFake() await guardarBorrador(reqFake({ body: { id: 'b-1', paso: 5, datos: {} } }), res) expect(prismaMock.borradorAlta.update).toHaveBeenCalledWith({ where: { id: 'b-1' }, data: expect.objectContaining({ paso: 5 }), }) expect(prismaMock.borradorAlta.create).not.toHaveBeenCalled() expect(json).toHaveBeenCalled() }) it('rechaza actualizar un borrador de otro usuario (403)', async () => { prismaMock.borradorAlta.findUnique.mockResolvedValue(borradorFake({ usuarioId: 'otro' })) await expect( guardarBorrador(reqFake({ body: { id: 'b-1', paso: 1, datos: {} } }), resFake().res), ).rejects.toMatchObject({ status: 403 }) expect(prismaMock.borradorAlta.update).not.toHaveBeenCalled() }) it('un admin sí puede actualizar el borrador de otro usuario', async () => { prismaMock.borradorAlta.findUnique.mockResolvedValue(borradorFake({ usuarioId: 'otro' })) prismaMock.borradorAlta.update.mockResolvedValue(borradorFake()) const req = reqFake({ body: { id: 'b-1', paso: 1, datos: {} }, usuarioToken: { sub: 'admin-1', username: 'admin', rol: 'admin' }, }) await guardarBorrador(req, resFake().res) expect(prismaMock.borradorAlta.update).toHaveBeenCalled() }) it('404 si el id a actualizar no existe', async () => { prismaMock.borradorAlta.findUnique.mockResolvedValue(null) await expect( guardarBorrador(reqFake({ body: { id: 'b-x', paso: 1, datos: {} } }), resFake().res), ).rejects.toMatchObject({ status: 404 }) }) }) describe('obtenerBorrador', () => { it('devuelve el borrador completo al dueño', async () => { prismaMock.borradorAlta.findUnique.mockResolvedValue(borradorFake()) const { res, json } = resFake() await obtenerBorrador(reqFake({ params: { id: 'b-1' } }), res) expect(json).toHaveBeenCalledWith(expect.objectContaining({ id: 'b-1', datosJson: expect.any(String) })) }) it('403 al abrir el borrador de otro usuario', async () => { prismaMock.borradorAlta.findUnique.mockResolvedValue(borradorFake({ usuarioId: 'otro' })) await expect(obtenerBorrador(reqFake({ params: { id: 'b-1' } }), resFake().res)).rejects.toMatchObject({ status: 403, }) }) }) describe('eliminarBorrador', () => { it('borra el borrador propio (204)', async () => { prismaMock.borradorAlta.findUnique.mockResolvedValue(borradorFake()) prismaMock.borradorAlta.delete.mockResolvedValue(borradorFake()) const { res, status, end } = resFake() await eliminarBorrador(reqFake({ params: { id: 'b-1' } }), res) expect(prismaMock.borradorAlta.delete).toHaveBeenCalledWith({ where: { id: 'b-1' } }) expect(status).toHaveBeenCalledWith(204) expect(end).toHaveBeenCalled() }) it('no borra el borrador de otro usuario (403)', async () => { prismaMock.borradorAlta.findUnique.mockResolvedValue(borradorFake({ usuarioId: 'otro' })) await expect(eliminarBorrador(reqFake({ params: { id: 'b-1' } }), resFake().res)).rejects.toMatchObject({ status: 403, }) expect(prismaMock.borradorAlta.delete).not.toHaveBeenCalled() }) })