Backend: tabla CapturaDepartamento, 5 departamentos, 7 desarrollos, service CRUD + importador Excel + 6 endpoints. Frontend: tabla editable inline, tabs por dept, resumen avance, paginación, filtro estatus, permisos por rol, dark mode. 746 API tests + 42 landing tests.
224 lines
8.1 KiB
TypeScript
224 lines
8.1 KiB
TypeScript
import { beforeEach, describe, expect, it, vi } from 'vitest'
|
|
import type { Request, Response } from 'express'
|
|
|
|
// ---------- Prisma mockeado ----------
|
|
|
|
const prismaMock = vi.hoisted(() => ({
|
|
capturaDepartamento: {
|
|
findMany: vi.fn(),
|
|
findUnique: vi.fn(),
|
|
count: vi.fn(),
|
|
upsert: vi.fn(),
|
|
},
|
|
$transaction: vi.fn(),
|
|
}))
|
|
|
|
vi.mock('../services/prisma.js', () => ({ prisma: prismaMock }))
|
|
|
|
// Mock del service para no depender de Prisma real
|
|
vi.mock('../services/captura-departamento.service.js', () => ({
|
|
obtenerUnidades: vi.fn(),
|
|
obtenerCapturas: vi.fn(),
|
|
obtenerCapturaUnidad: vi.fn(),
|
|
guardarCampo: vi.fn(),
|
|
guardarBulk: vi.fn(),
|
|
resumenAvance: vi.fn(),
|
|
importarDesdeExcel: vi.fn(),
|
|
}))
|
|
|
|
import {
|
|
listarEndpoint,
|
|
unidadesEndpoint,
|
|
resumenEndpoint,
|
|
guardarEndpoint,
|
|
bulkEndpoint,
|
|
importarEndpoint,
|
|
requireAdminCaptura,
|
|
} from './captura-departamento.controller.js'
|
|
import * as capturaService from '../services/captura-departamento.service.js'
|
|
|
|
function reqFake(opts: { query?: Record<string, string>; body?: unknown; params?: Record<string, string>; token?: { sub: string; rol: string } }): Request {
|
|
return {
|
|
query: opts.query ?? {},
|
|
body: opts.body ?? {},
|
|
params: opts.params ?? {},
|
|
usuarioToken: opts.token,
|
|
} as unknown as Request
|
|
}
|
|
|
|
function resFake() {
|
|
const json = vi.fn()
|
|
const end = vi.fn()
|
|
const statusFn = vi.fn().mockReturnValue({ json, end })
|
|
return { res: { json, end, status: statusFn } as unknown as Response, status: statusFn, json, end }
|
|
}
|
|
|
|
beforeEach(() => {
|
|
vi.clearAllMocks()
|
|
})
|
|
|
|
// ── requireAdminCaptura ─────────────────────────────────────────────
|
|
|
|
describe('requireAdminCaptura', () => {
|
|
it('pasa si rol es admin', () => {
|
|
const next = vi.fn()
|
|
const req = reqFake({ token: { sub: 'u1', rol: 'admin' } })
|
|
requireAdminCaptura(req, {} as Response, next)
|
|
expect(next).toHaveBeenCalled()
|
|
})
|
|
|
|
it('lanza 403 si rol no es admin', () => {
|
|
const req = reqFake({ token: { sub: 'u1', rol: 'vendedor' } })
|
|
expect(() => requireAdminCaptura(req, {} as Response, vi.fn())).toThrow('Se requiere rol admin')
|
|
})
|
|
|
|
it('lanza 403 si no hay token', () => {
|
|
const req = reqFake({})
|
|
expect(() => requireAdminCaptura(req, {} as Response, vi.fn())).toThrow('Se requiere rol admin')
|
|
})
|
|
})
|
|
|
|
// ── listarEndpoint ──────────────────────────────────────────────────
|
|
|
|
describe('listarEndpoint (GET /api/captura)', () => {
|
|
it('retorna capturas agrupadas', async () => {
|
|
const fake = { U101: { estatus: 'VENDIDO' } }
|
|
vi.mocked(capturaService.obtenerCapturas).mockResolvedValue(fake)
|
|
const { res, json } = resFake()
|
|
|
|
await listarEndpoint(reqFake({ query: { desarrollo: 'SENDA' }, token: { sub: 'u1', rol: 'admin' } }), res)
|
|
expect(json).toHaveBeenCalledWith(fake)
|
|
expect(capturaService.obtenerCapturas).toHaveBeenCalledWith('SENDA', undefined)
|
|
})
|
|
|
|
it('pasa departamento cuando se provee', async () => {
|
|
vi.mocked(capturaService.obtenerCapturas).mockResolvedValue({})
|
|
const { res } = resFake()
|
|
|
|
await listarEndpoint(
|
|
reqFake({ query: { desarrollo: 'SENDA', departamento: 'OBRA' }, token: { sub: 'u1', rol: 'admin' } }),
|
|
res,
|
|
)
|
|
expect(capturaService.obtenerCapturas).toHaveBeenCalledWith('SENDA', 'OBRA')
|
|
})
|
|
|
|
it('rechaza sin desarrollo', async () => {
|
|
const { res } = resFake()
|
|
await expect(listarEndpoint(reqFake({ query: {}, token: { sub: 'u1', rol: 'admin' } }), res)).rejects.toThrow()
|
|
})
|
|
})
|
|
|
|
// ── unidadesEndpoint ────────────────────────────────────────────────
|
|
|
|
describe('unidadesEndpoint (GET /api/captura/unidades)', () => {
|
|
it('retorna lista de unidades', async () => {
|
|
vi.mocked(capturaService.obtenerUnidades).mockResolvedValue(['U-101', 'U-102'])
|
|
const { res, json } = resFake()
|
|
|
|
await unidadesEndpoint(reqFake({ query: { desarrollo: 'SENDA' }, token: { sub: 'u1', rol: 'admin' } }), res)
|
|
expect(json).toHaveBeenCalledWith(['U-101', 'U-102'])
|
|
})
|
|
})
|
|
|
|
// ── resumenEndpoint ─────────────────────────────────────────────────
|
|
|
|
describe('resumenEndpoint (GET /api/captura/resumen)', () => {
|
|
it('retorna resumen de avance', async () => {
|
|
const fake = [{ departamento: 'OBRA', camposTotales: 8, camposLlenos: 4, porcentaje: 50 }]
|
|
vi.mocked(capturaService.resumenAvance).mockResolvedValue(fake)
|
|
const { res, json } = resFake()
|
|
|
|
await resumenEndpoint(reqFake({ query: { desarrollo: 'SENDA' }, token: { sub: 'u1', rol: 'admin' } }), res)
|
|
expect(json).toHaveBeenCalledWith(fake)
|
|
})
|
|
})
|
|
|
|
// ── guardarEndpoint ─────────────────────────────────────────────────
|
|
|
|
describe('guardarEndpoint (POST /api/captura)', () => {
|
|
it('guarda campo y retorna 204', async () => {
|
|
vi.mocked(capturaService.guardarCampo).mockResolvedValue(undefined)
|
|
const { res, status } = resFake()
|
|
|
|
await guardarEndpoint(
|
|
reqFake({
|
|
body: { desarrollo: 'SENDA', unidadCodigo: 'U-101', departamento: 'VENTAS', campo: 'estatus', valor: 'VENDIDO' },
|
|
token: { sub: 'u1', rol: 'admin' },
|
|
}),
|
|
res,
|
|
)
|
|
expect(status).toHaveBeenCalledWith(204)
|
|
expect(capturaService.guardarCampo).toHaveBeenCalledWith('SENDA', 'U-101', 'VENTAS', 'estatus', 'VENDIDO', 'u1')
|
|
})
|
|
|
|
it('rechaza departamento inválido', async () => {
|
|
const { res } = resFake()
|
|
await expect(
|
|
guardarEndpoint(
|
|
reqFake({
|
|
body: { desarrollo: 'SENDA', unidadCodigo: 'U-101', departamento: 'INVALIDO', campo: 'x', valor: 'y' },
|
|
token: { sub: 'u1', rol: 'admin' },
|
|
}),
|
|
res,
|
|
),
|
|
).rejects.toThrow('Departamento inválido')
|
|
})
|
|
})
|
|
|
|
// ── bulkEndpoint ────────────────────────────────────────────────────
|
|
|
|
describe('bulkEndpoint (POST /api/captura/bulk)', () => {
|
|
it('guarda múltiple y retorna 204', async () => {
|
|
vi.mocked(capturaService.guardarBulk).mockResolvedValue(undefined)
|
|
const { res, status } = resFake()
|
|
|
|
await bulkEndpoint(
|
|
reqFake({
|
|
body: {
|
|
desarrollo: 'SENDA',
|
|
departamento: 'VENTAS',
|
|
registros: [
|
|
{ unidad: 'U-101', campo: 'estatus', valor: 'VENDIDO' },
|
|
{ unidad: 'U-102', campo: 'estatus', valor: 'DISPONIBLE' },
|
|
],
|
|
},
|
|
token: { sub: 'u1', rol: 'admin' },
|
|
}),
|
|
res,
|
|
)
|
|
expect(status).toHaveBeenCalledWith(204)
|
|
expect(capturaService.guardarBulk).toHaveBeenCalledWith('SENDA', 'VENTAS', [
|
|
{ unidad: 'U-101', campo: 'estatus', valor: 'VENDIDO' },
|
|
{ unidad: 'U-102', campo: 'estatus', valor: 'DISPONIBLE' },
|
|
], 'u1')
|
|
})
|
|
})
|
|
|
|
// ── importarEndpoint ────────────────────────────────────────────────
|
|
|
|
describe('importarEndpoint (POST /api/captura/importar)', () => {
|
|
it('importa datos y retorna resultado (admin)', async () => {
|
|
const fake = { registrosCreados: 10, registrosActualizados: 5 }
|
|
vi.mocked(capturaService.importarDesdeExcel).mockResolvedValue(fake)
|
|
const { res, json } = resFake()
|
|
|
|
await importarEndpoint(
|
|
reqFake({
|
|
body: { desarrollo: 'SENDA', datos: [['CLIENTE', 'ESTATUS'], ['U-101', 'VENDIDO']] },
|
|
token: { sub: 'u1', rol: 'admin' },
|
|
}),
|
|
res,
|
|
)
|
|
expect(json).toHaveBeenCalledWith(fake)
|
|
expect(capturaService.importarDesdeExcel).toHaveBeenCalledWith('SENDA', [['CLIENTE', 'ESTATUS'], ['U-101', 'VENDIDO']], 'u1')
|
|
})
|
|
|
|
it('lanza 403 si no es admin', async () => {
|
|
const req = reqFake({
|
|
body: { desarrollo: 'SENDA', datos: [] },
|
|
token: { sub: 'u1', rol: 'vendedor' },
|
|
})
|
|
expect(() => requireAdminCaptura(req, {} as Response, vi.fn())).toThrow('Se requiere rol admin')
|
|
})
|
|
})
|