106 lines
3.4 KiB
TypeScript
106 lines
3.4 KiB
TypeScript
import { describe, expect, it, vi } from 'vitest';
|
|
import { DatabaseError } from 'pg';
|
|
import type { Request, Response } from 'express';
|
|
import { errorMiddleware } from '@/api/middlewares/error.js';
|
|
import { AppError } from '@/utils/errors.js';
|
|
import { ERROR_CODES } from '@insforge/shared-schemas';
|
|
|
|
vi.mock('@/utils/logger.js', () => ({
|
|
default: { info: vi.fn(), error: vi.fn(), warn: vi.fn(), debug: vi.fn() },
|
|
}));
|
|
|
|
function makeMockRes() {
|
|
const res = {} as Response & {
|
|
status: ReturnType<typeof vi.fn>;
|
|
json: ReturnType<typeof vi.fn>;
|
|
statusCode?: number;
|
|
body?: unknown;
|
|
};
|
|
res.status = vi.fn((code: number) => {
|
|
res.statusCode = code;
|
|
return res;
|
|
});
|
|
res.json = vi.fn((body: unknown) => {
|
|
res.body = body;
|
|
return res;
|
|
});
|
|
return res;
|
|
}
|
|
|
|
function makePgError(code: string, message: string): DatabaseError {
|
|
// pg's DatabaseError ctor isn't ergonomic; build a shaped instance.
|
|
const err = new DatabaseError(message, message.length, 'error');
|
|
Object.assign(err, { code });
|
|
return err;
|
|
}
|
|
|
|
describe('errorMiddleware', () => {
|
|
it('translates Postgres 42501 (RLS WITH CHECK denial) into 403 with FORBIDDEN', () => {
|
|
const err = makePgError(
|
|
'42501',
|
|
'new row violates row-level security policy for table "objects"'
|
|
);
|
|
const req = {} as Request;
|
|
const res = makeMockRes();
|
|
const next = vi.fn();
|
|
|
|
errorMiddleware(err, req, res, next);
|
|
|
|
expect(res.statusCode).toBe(403);
|
|
const body = res.body as { error: string; message: string; nextActions?: string };
|
|
expect(body.error).toBe(ERROR_CODES.FORBIDDEN);
|
|
expect(body.message).toContain('row-level security');
|
|
// The hint should mention RLS so a developer can self-diagnose.
|
|
expect(body.nextActions).toMatch(/row-level security|RLS|policy/i);
|
|
});
|
|
|
|
it('still maps 23505 (unique violation) to 409 — regression check', () => {
|
|
const err = makePgError('23505', 'duplicate key value violates unique constraint');
|
|
err.detail = 'Key (key)=(note.txt) already exists.';
|
|
const req = {} as Request;
|
|
const res = makeMockRes();
|
|
const next = vi.fn();
|
|
|
|
errorMiddleware(err, req, res, next);
|
|
|
|
expect(res.statusCode).toBe(409);
|
|
});
|
|
|
|
it('passes AppError through with its declared status', () => {
|
|
const err = new AppError('bad input', 400, ERROR_CODES.INVALID_INPUT);
|
|
const req = {} as Request;
|
|
const res = makeMockRes();
|
|
const next = vi.fn();
|
|
|
|
errorMiddleware(err, req, res, next);
|
|
|
|
expect(res.statusCode).toBe(400);
|
|
const body = res.body as { error: string };
|
|
expect(body.error).toBe(ERROR_CODES.INVALID_INPUT);
|
|
});
|
|
|
|
it('falls back to 500 for unknown errors even when they carry a 4xx status', () => {
|
|
const req = {} as Request;
|
|
const res = makeMockRes();
|
|
const next = vi.fn();
|
|
|
|
errorMiddleware({ status: 404, message: 'not found from an unknown source' }, req, res, next);
|
|
|
|
expect(res.statusCode).toBe(500);
|
|
const body = res.body as { error: string; message: string };
|
|
expect(body.error).toBe(ERROR_CODES.INTERNAL_ERROR);
|
|
expect(body.message).toBe('not found from an unknown source');
|
|
});
|
|
|
|
it('falls back to 500 for unrecognized pg codes', () => {
|
|
const err = makePgError('XX000', 'internal error');
|
|
const req = {} as Request;
|
|
const res = makeMockRes();
|
|
const next = vi.fn();
|
|
|
|
errorMiddleware(err, req, res, next);
|
|
|
|
expect(res.statusCode).toBe(500);
|
|
});
|
|
});
|