Files
insforge--insforge/backend/tests/unit/error-middleware-rls.test.ts
wehub-resource-sync 3a28426bf4
Lint and Format Check / lint-and-format (push) Failing after 0s
Check Migrations / Check for duplicate migration numbers (push) Failing after 1s
CI Pre-merge Check / CI Pre-merge Check (push) Failing after 2m17s
chore: import upstream snapshot with attribution
2026-07-13 12:23:40 +08:00

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);
});
});