import { describe, it, expect } from 'vitest'; import express from 'express'; import request from 'supertest'; import { errorMiddleware, NotFoundError, ValidationError, ConflictError, ForbiddenError, asyncWrap, ApiError } from '../../lib/api/errors.js'; function buildApp(handler) { const app = express(); app.use(express.json()); app.get('/test', asyncWrap(handler)); app.use(errorMiddleware); return app; } describe('api/errors', () => { it('NotFoundError → 404 with code "not_found"', async () => { const res = await request(buildApp(() => { throw new NotFoundError('missing'); })) .get('/test'); expect(res.status).toBe(404); expect(res.body).toEqual({ error: { code: 'not_found', message: 'missing' } }); }); it('ValidationError → 400 with details', async () => { const res = await request(buildApp(() => { throw new ValidationError('bad', [{ path: 'name' }]); })) .get('/test'); expect(res.status).toBe(400); expect(res.body.error.code).toBe('validation_failed'); expect(res.body.error.details).toEqual([{ path: 'name' }]); }); it('ConflictError → 409', async () => { const res = await request(buildApp(() => { throw new ConflictError(); })).get('/test'); expect(res.status).toBe(409); expect(res.body.error.code).toBe('conflict'); }); it('ForbiddenError → 403', async () => { const res = await request(buildApp(() => { throw new ForbiddenError(); })).get('/test'); expect(res.status).toBe(403); expect(res.body.error.code).toBe('forbidden'); }); it('unknown error → 500 with generic shape', async () => { const res = await request(buildApp(() => { throw new Error('boom'); })).get('/test'); expect(res.status).toBe(500); expect(res.body.error.code).toBe('internal'); }); it('asyncWrap forwards rejected promises', async () => { const res = await request(buildApp(async () => { throw new NotFoundError(); })) .get('/test'); expect(res.status).toBe(404); }); it('ApiError exposes code, status, and details', () => { const e = new ApiError('x', 'y', 418, { a: 1 }); expect(e.code).toBe('x'); expect(e.status).toBe(418); expect(e.details).toEqual({ a: 1 }); }); });