import { beforeEach, describe, expect, it, vi } from 'vitest' import type { Request, Response } from 'express' import { AppError, NotFoundError } from '../utils/errors.js' // ---------- Prisma y Monday mockeados (los tests no tocan BD ni red) ---------- const prismaMock = vi.hoisted(() => ({ unidad: { findMany: vi.fn(), findUnique: vi.fn(), }, })) const mondayMock = vi.hoisted(() => ({ getUnidades: vi.fn(), getUnidad: vi.fn(), })) vi.mock('../services/prisma.js', () => ({ prisma: prismaMock })) vi.mock('../services/monday.service.js', () => ({ mondayService: mondayMock })) import { listarUnidades, obtenerUnidad } from './unidades.controller.js' import { saludMonday } from '../services/salud-monday.service.js' const filaLocal = { codigo: 'U-101', desarrollo: 'DIAGONAL', modelo: '2RA', supConstruccion: 70, supExcedente: 0, supEstacionamiento: 12.5, precioLista: 2_000_000, precioAcabados: 2_100_000, status: 'DISPONIBLE', etapa: 'Entrega inmediata', } function resFake() { const json = vi.fn() return { res: { json } as unknown as Response, json } } beforeEach(() => { vi.clearAllMocks() vi.restoreAllMocks() // restaura los spyOn(saludMonday, 'estaDisponible') saludMonday.restablecer() prismaMock.unidad.findMany.mockResolvedValue([filaLocal]) prismaMock.unidad.findUnique.mockResolvedValue(filaLocal) }) describe('unidades.controller (fallback local)', () => { it('Monday vivo: devuelve Monday y no toca la tabla local', async () => { mondayMock.getUnidades.mockResolvedValue([{ codigo: 'M-1', status: 'DISPONIBLE', desarrollo: 'SENDA' }]) const { res, json } = resFake() await listarUnidades({ query: {} } as unknown as Request, res) expect(json).toHaveBeenCalledWith([{ codigo: 'M-1', status: 'DISPONIBLE', desarrollo: 'SENDA' }]) expect(prismaMock.unidad.findMany).not.toHaveBeenCalled() }) it('Monday caído (circuit breaker): lista desde la tabla Unidad con la misma forma de DTO', async () => { vi.spyOn(saludMonday, 'estaDisponible').mockReturnValue(false) const { res, json } = resFake() await listarUnidades({ query: {} } as unknown as Request, res) expect(mondayMock.getUnidades).not.toHaveBeenCalled() const lista = json.mock.calls[0]?.[0] as Record[] expect(lista).toHaveLength(1) expect(lista[0]).toMatchObject({ codigo: 'U-101', modelo: '2RA', precioSinAcabados: 2_000_000, precioConAcabados: 2_100_000, status: 'DISPONIBLE', desarrollo: 'DIAGONAL', etapa: 'Entrega inmediata', }) }) it('Monday caído: los filtros ?disponibles y ?desarrollo se aplican igual', async () => { vi.spyOn(saludMonday, 'estaDisponible').mockReturnValue(false) prismaMock.unidad.findMany.mockResolvedValue([ filaLocal, { ...filaLocal, codigo: 'U-102', status: 'APARTADO' }, { ...filaLocal, codigo: 'S-1', desarrollo: 'SENDA' }, ]) const { res, json } = resFake() await listarUnidades({ query: { disponibles: 'true', desarrollo: 'senda' } } as unknown as Request, res) const lista = json.mock.calls[0]?.[0] as Record[] expect(lista.map((u) => u['codigo'])).toEqual(['S-1']) }) it('Monday lanza 502: fallback local; otro error se propaga', async () => { mondayMock.getUnidades.mockRejectedValue(new AppError(502, 'Monday no respondió')) const { res, json } = resFake() await listarUnidades({ query: {} } as unknown as Request, res) expect((json.mock.calls[0]?.[0] as unknown[]).length).toBe(1) mondayMock.getUnidades.mockRejectedValue(new AppError(500, 'bug')) await expect(listarUnidades({ query: {} } as unknown as Request, resFake().res)).rejects.toThrow('bug') }) it('obtenerUnidad: Monday caído → local; inexistente en ambos → 404', async () => { vi.spyOn(saludMonday, 'estaDisponible').mockReturnValue(false) const { res, json } = resFake() await obtenerUnidad({ params: { codigo: 'U-101' } } as unknown as Request, res) expect((json.mock.calls[0]?.[0] as Record)['codigo']).toBe('U-101') prismaMock.unidad.findUnique.mockResolvedValue(null) await expect( obtenerUnidad({ params: { codigo: 'U-X' } } as unknown as Request, resFake().res), ).rejects.toThrow(NotFoundError) }) })