Files
FRASA-CAS/api-gateway/src/controllers/borradores.controller.test.ts
Consult AS Technology 59944668a8 feat(alta): borrador, pre-llenado desde cotización y liga remota de firma
- Borradores por vendedor (guardar/continuar/eliminar), se limpia al registrar
- Pre-llenado con ?cotizacion=<id> (cliente, contacto, proyecto, montos);
  botón 'Iniciar alta' en Mis cotizaciones del cotizador
- Liga remota pública /client/firma/<token> (JWT scope firma-remota, 72h,
  rate limit, solo datos mínimos sin PLD): firma digital + subida de
  documentos que prende checkboxes en Monday Apartado
- Tokens de liga no sirven como sesión y viceversa
- Tests: api 228, alta 82, cotizador 82
2026-08-28 07:29:22 +00:00

181 lines
6.0 KiB
TypeScript

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> = {}): 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<string, unknown> = {}) => ({
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()
})
})