148 lines
4.1 KiB
TypeScript
148 lines
4.1 KiB
TypeScript
import assert from 'node:assert/strict';
|
|
import test from 'node:test';
|
|
import type { NextFunction, Request, RequestHandler } from 'express';
|
|
import Joi from 'joi';
|
|
import { createRequest, createResponse } from 'node-mocks-http';
|
|
|
|
import { commonErrorHandler } from '../src/helpers.ts';
|
|
import { validateRequest } from '../src/middlewares/validate-request.ts';
|
|
import {
|
|
crud,
|
|
projects,
|
|
tourPages,
|
|
users,
|
|
} from '../src/validators/request-schemas.ts';
|
|
import type { RouteError } from '../src/types/index.ts';
|
|
|
|
interface UserUpdateTestBody {
|
|
data: {
|
|
firstName: string;
|
|
custom_permissions?: unknown;
|
|
allowed_private_production_project_ids?: unknown;
|
|
};
|
|
}
|
|
|
|
function runMiddleware(
|
|
middleware: RequestHandler,
|
|
req: Request,
|
|
): Promise<RouteError | null> {
|
|
const res = createResponse();
|
|
|
|
return new Promise((resolve) => {
|
|
const next: NextFunction = (error?: unknown) => {
|
|
resolve(error instanceof Error ? error : null);
|
|
};
|
|
|
|
middleware(req, res, next);
|
|
});
|
|
}
|
|
|
|
void test('validateRequest applies converted sanitized values to request parts', async () => {
|
|
const req = createRequest({
|
|
query: {
|
|
limit: '25',
|
|
page: '2',
|
|
field: 'createdAt',
|
|
sort: 'DESC',
|
|
name: 'Lobby',
|
|
},
|
|
});
|
|
|
|
const error = await runMiddleware(validateRequest(crud.list), req);
|
|
|
|
assert.equal(error, null);
|
|
assert.equal(req.query.limit, 25);
|
|
assert.equal(req.query.page, 2);
|
|
assert.equal(req.query.name, 'Lobby');
|
|
});
|
|
|
|
void test('validateRequest returns structured request validation error details', async () => {
|
|
const req = createRequest({
|
|
body: {
|
|
data: ['not-object'],
|
|
},
|
|
});
|
|
|
|
const error = await runMiddleware(validateRequest(crud.create), req);
|
|
|
|
assert.equal(error?.code, 400);
|
|
assert.equal(error?.isRequestValidation, true);
|
|
assert.equal(error?.details?.[0]?.path, 'body.data');
|
|
});
|
|
|
|
void test('commonErrorHandler sends request validation errors as JSON', () => {
|
|
const error: RouteError = new Error('Invalid request');
|
|
error.code = 400;
|
|
error.isRequestValidation = true;
|
|
error.details = [{ path: 'body.email', message: 'email is required' }];
|
|
|
|
const req = createRequest();
|
|
const res = createResponse();
|
|
|
|
commonErrorHandler(error, req, res, () => {});
|
|
|
|
assert.equal(res.statusCode, 400);
|
|
assert.deepEqual(res._getData(), {
|
|
error: 'Invalid request',
|
|
details: [{ path: 'body.email', message: 'email is required' }],
|
|
});
|
|
});
|
|
|
|
void test('user update schema preserves omitted permissions fields', async () => {
|
|
const req = createRequest<Request<{ id: string }, unknown, UserUpdateTestBody>>({
|
|
params: { id: '094121b9-c567-469a-b256-ba221b7fd5d6' },
|
|
body: {
|
|
data: {
|
|
firstName: 'Admin',
|
|
},
|
|
},
|
|
});
|
|
|
|
const error = await runMiddleware(validateRequest(users.update), req);
|
|
|
|
assert.equal(error, null);
|
|
assert.equal(
|
|
Object.prototype.hasOwnProperty.call(req.body.data, 'custom_permissions'),
|
|
false,
|
|
);
|
|
assert.equal(
|
|
Object.prototype.hasOwnProperty.call(
|
|
req.body.data,
|
|
'allowed_private_production_project_ids',
|
|
),
|
|
false,
|
|
);
|
|
});
|
|
|
|
void test('create schemas reject missing fields required before service layer', async () => {
|
|
const userError = await runMiddleware(
|
|
validateRequest(users.create),
|
|
createRequest({ body: { data: { firstName: 'No Email' } } }),
|
|
);
|
|
const projectError = await runMiddleware(
|
|
validateRequest(projects.create),
|
|
createRequest({ body: { data: { name: 'No Slug' } } }),
|
|
);
|
|
const tourPageError = await runMiddleware(
|
|
validateRequest(tourPages.create),
|
|
createRequest({ body: { data: { name: 'No Project', slug: 'no-project' } } }),
|
|
);
|
|
|
|
assert.equal(userError?.isRequestValidation, true);
|
|
assert.equal(projectError?.isRequestValidation, true);
|
|
assert.equal(tourPageError?.isRequestValidation, true);
|
|
});
|
|
|
|
void test('validateRequest rejects invalid schema maps during route setup', () => {
|
|
const invalidSchemas = { body: Joi.object() };
|
|
Object.defineProperty(invalidSchemas, 'cookies', {
|
|
enumerable: true,
|
|
value: Joi.object(),
|
|
});
|
|
|
|
assert.throws(
|
|
() => validateRequest(invalidSchemas),
|
|
/Unsupported request validation part/,
|
|
);
|
|
});
|