- Tablas ConfiguracionSistema (7 configs) + RolConfiguracion (6 roles) - Endpoints: config/sistema GET/PATCH, config/roles GET/PATCH, config/usuarios GET/PATCH, config/reset-roles POST, config/acciones GET - modulosDeRol() async con fallback a MATRIZ estatica - auth/me devuelve acciones[] segun rol del usuario - saludMonday.estaDisponible() lee modo_local_forzado de DB (cache 30s) - Landing: vista Configuraciones con 4 tabs (Modo, Roles, Usuarios, Sistema) - Mi-dia: boton Descartar se deshabilita si !descartar_lead - 630 API tests + 24 landing tests + 20 mi-dia tests verdes Seed: npm run seed:config
197 lines
6.7 KiB
TypeScript
197 lines
6.7 KiB
TypeScript
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> = {}): 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> = {}): 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> = {}): 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()
|
|
})
|
|
})
|
|
|