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 { 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>({ 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/, ); });