import { beforeEach, describe, expect, it, vi } from 'vitest' import type { Request, Response } from 'express' import type { Usuario } from '@prisma/client' import { actualizarRolEndpoint, actualizarVinculoMondayEndpoint, listarUsuariosMondayEndpoint, } from './usuarios.controller.js' import { AppError } from '../utils/errors.js' // ---------- Monday y Prisma mockeados (los tests no tocan red ni BD) ---------- const mondayMock = vi.hoisted(() => ({ getUsuariosWorkspace: vi.fn() })) const prismaMock = vi.hoisted(() => ({ usuario: { update: vi.fn() } })) vi.mock('../services/monday.service.js', () => ({ mondayService: mondayMock })) vi.mock('../services/prisma.js', () => ({ prisma: prismaMock })) const USUARIOS_WORKSPACE = [ { id: 13676, nombre: 'Erick Peña', email: 'erick@frasa.mx' }, { id: 20400, nombre: 'Cristina Barrera', email: 'cristina@frasa.mx' }, ] function usuarioFake(overrides: Partial = {}): Usuario { return { id: 'u-1', username: 'erick israel pena ramos', nombre: 'ERICK ISRAEL PEÑA RAMOS', passwordHash: 'hash', rol: 'vendedor', asignacion: 'MONTEBELLO', empleadoId: 13676, mondayUserId: null, activo: true, createdAt: new Date(), updatedAt: new Date(), ...overrides, } } function reqFake(overrides: Partial = {}): Request { return { body: {}, usuarioToken: { sub: 'u-1', username: 'erick israel pena ramos', rol: 'vendedor' }, ...overrides, } as Request } function resFake() { const json = vi.fn() const status = vi.fn().mockReturnValue({ json }) return { res: { json, status } as unknown as Response, status, json } } beforeEach(() => { vi.clearAllMocks() mondayMock.getUsuariosWorkspace.mockResolvedValue(USUARIOS_WORKSPACE) }) describe('listarUsuariosMondayEndpoint (GET /api/monday/usuarios)', () => { it('devuelve los usuarios del workspace con id numérico', async () => { const { res, json } = resFake() await listarUsuariosMondayEndpoint(reqFake(), res) expect(json).toHaveBeenCalledWith([ { id: 13676, nombre: 'Erick Peña', email: 'erick@frasa.mx' }, { id: 20400, nombre: 'Cristina Barrera', email: 'cristina@frasa.mx' }, ]) }) it('propaga el error si Monday falla (el error handler responde 5xx)', async () => { mondayMock.getUsuariosWorkspace.mockRejectedValue(new Error('Monday caído')) const { res } = resFake() await expect(listarUsuariosMondayEndpoint(reqFake(), res)).rejects.toThrow('Monday caído') }) }) describe('actualizarVinculoMondayEndpoint (PATCH /api/usuarios/me/monday)', () => { it('con id válido guarda el vínculo y devuelve el perfil actualizado', async () => { prismaMock.usuario.update.mockResolvedValue(usuarioFake({ mondayUserId: 13676 })) const { res, json } = resFake() await actualizarVinculoMondayEndpoint(reqFake({ body: { mondayUserId: 13676 } }), res) expect(prismaMock.usuario.update).toHaveBeenCalledWith({ where: { id: 'u-1' }, data: { mondayUserId: 13676 }, }) expect(json).toHaveBeenCalledWith({ nombre: 'ERICK ISRAEL PEÑA RAMOS', rol: 'vendedor', asignacion: 'MONTEBELLO', mondayUserId: 13676, modulos: ['mi-dia', 'alta-cliente', 'alta-afluencia', 'cotizador', 'mi-tablero', 'contactos', 'chat'], acciones: [], }) }) it('con id inexistente en el workspace responde 400 y no toca la BD', async () => { const { res } = resFake() const error = await actualizarVinculoMondayEndpoint( reqFake({ body: { mondayUserId: 99999 } }), res, ).catch((e: unknown) => e) expect(error).toBeInstanceOf(AppError) expect(error).toMatchObject({ status: 400 }) expect(prismaMock.usuario.update).not.toHaveBeenCalled() }) it('con null desvincula sin consultar Monday', async () => { prismaMock.usuario.update.mockResolvedValue(usuarioFake({ mondayUserId: null })) const { res, json } = resFake() await actualizarVinculoMondayEndpoint(reqFake({ body: { mondayUserId: null } }), res) expect(mondayMock.getUsuariosWorkspace).not.toHaveBeenCalled() expect(prismaMock.usuario.update).toHaveBeenCalledWith({ where: { id: 'u-1' }, data: { mondayUserId: null }, }) expect(json).toHaveBeenCalledWith(expect.objectContaining({ mondayUserId: null })) }) it('sin token verificado: 400', async () => { const { res } = resFake() await expect( actualizarVinculoMondayEndpoint(reqFake({ usuarioToken: undefined }), res), ).rejects.toThrow('Falta el token verificado') expect(prismaMock.usuario.update).not.toHaveBeenCalled() }) it('body inválido (string): ZodError (el error handler lo traduce a 400)', async () => { const { res } = resFake() await expect( actualizarVinculoMondayEndpoint(reqFake({ body: { mondayUserId: '13676' } }), res), ).rejects.toThrow() expect(prismaMock.usuario.update).not.toHaveBeenCalled() }) }) describe('actualizarRolEndpoint (PATCH /api/usuarios/:id/rol)', () => { function reqAdmin(overrides: Partial = {}): Request { return { body: { rol: 'escrituracion' }, params: { id: 'u-9' }, usuarioToken: { sub: 'u-1', username: 'admin', rol: 'admin' }, ...overrides, } as Request } it('admin con rol válido: actualiza y devuelve el perfil con sus módulos', async () => { prismaMock.usuario.update.mockResolvedValue(usuarioFake({ id: 'u-9', rol: 'escrituracion' })) const { res, json } = resFake() await actualizarRolEndpoint(reqAdmin(), res) expect(prismaMock.usuario.update).toHaveBeenCalledWith({ where: { id: 'u-9' }, data: { rol: 'escrituracion' }, }) expect(json).toHaveBeenCalledWith( expect.objectContaining({ rol: 'escrituracion', modulos: expect.arrayContaining(['cobranza']) }), ) }) it('sin rol admin: 403 y no toca la BD', async () => { const { res } = resFake() const error = await actualizarRolEndpoint( reqAdmin({ usuarioToken: { sub: 'u-2', username: 'x', rol: 'vendedor' } }), res, ).catch((e: unknown) => e) expect(error).toMatchObject({ status: 403 }) expect(prismaMock.usuario.update).not.toHaveBeenCalled() }) it('rol inválido: 400 y no toca la BD', async () => { const { res } = resFake() const error = await actualizarRolEndpoint(reqAdmin({ body: { rol: 'coordinador' } }), res).catch( (e: unknown) => e, ) expect(error).toMatchObject({ status: 400 }) expect(prismaMock.usuario.update).not.toHaveBeenCalled() }) it('sin token verificado: 400', async () => { const { res } = resFake() await expect(actualizarRolEndpoint(reqAdmin({ usuarioToken: undefined }), res)).rejects.toThrow( 'Falta el token verificado', ) expect(prismaMock.usuario.update).not.toHaveBeenCalled() }) })