import assert from 'node:assert/strict'; import { EventEmitter } from 'node:events'; import express from 'express'; import test from 'node:test'; import type { RequestHandler } from 'express'; import { createRequest, createResponse } from 'node-mocks-http'; import type { Body, Headers, RequestMethod, RequestOptions } from 'node-mocks-http'; import { createEntityRouter } from '../../src/factories/router.factory.ts'; import { commonErrorHandler } from '../../src/helpers.ts'; import type { EntityRouterDbApi, EntityRouterService, } from '../../src/types/index.ts'; import { setCurrentUser, setRuntimeContext, } from '../../src/utils/request-context.ts'; const USER_ID = '0f5cb907-73d3-45da-9e42-bf3e4b499efb'; const ENTITY_ID = 'aa0b89ab-b0db-4d6c-a59c-c7785fc3eae5'; interface RecordedCall { name: string; payload: unknown; } function contextMiddleware(): RequestHandler { return (req, _res, next) => { setCurrentUser(req, { id: USER_ID, app_role: { name: 'Test Role', permissions: [ { name: 'READ_TEST_ENTITIES' }, { name: 'CREATE_TEST_ENTITIES' }, { name: 'UPDATE_TEST_ENTITIES' }, { name: 'DELETE_TEST_ENTITIES' }, ], }, }); setRuntimeContext(req, { mode: 'admin', projectSlug: null, headerEnvironment: 'stage', headerProjectSlug: 'demo-tour', }); next(); }; } function buildHarness() { const calls: RecordedCall[] = []; const service: EntityRouterService, Record> = { create: (options) => { calls.push({ name: 'create', payload: options }); return Promise.resolve({ id: ENTITY_ID, ...options.data }); }, update: (options) => { calls.push({ name: 'update', payload: options }); return Promise.resolve(true); }, remove: (options) => { calls.push({ name: 'remove', payload: options }); return Promise.resolve(true); }, deleteByIds: (options) => { calls.push({ name: 'deleteByIds', payload: options }); return Promise.resolve(true); }, bulkImport: () => { calls.push({ name: 'bulkImport', payload: null }); return Promise.resolve(); }, }; const dbApi: EntityRouterDbApi = { MODEL: { rawAttributes: { id: {}, name: {}, createdAt: {}, }, }, findAll: (query, options) => { calls.push({ name: 'findAll', payload: { query, options } }); return Promise.resolve({ rows: [{ id: ENTITY_ID, name: 'Lobby' }], count: 1, }); }, findAllAutocomplete: (options) => { calls.push({ name: 'findAllAutocomplete', payload: options }); return Promise.resolve([{ id: ENTITY_ID, label: 'Lobby' }]); }, findBy: (options) => { calls.push({ name: 'findBy', payload: options }); return Promise.resolve({ id: ENTITY_ID, name: 'Lobby' }); }, }; const app = express(); app.use(contextMiddleware()); app.use('/entities', createEntityRouter('test_entities', service, dbApi)); app.use(commonErrorHandler); return { app, calls }; } interface AppRequestOptions { method: RequestMethod; url: string; headers?: Headers; body?: Body; } function dispatchApp(app: express.Express, options: AppRequestOptions) { const requestOptions: RequestOptions = { method: options.method, url: options.url, }; if (options.headers !== undefined) { requestOptions.headers = options.headers; } if (options.body !== undefined) { requestOptions.body = options.body; } const req = createRequest(requestOptions); const res = createResponse({ eventEmitter: EventEmitter }); return new Promise((resolve, reject) => { res.on('end', () => resolve(res)); app(req, res, (error?: unknown) => { if (error) { reject( error instanceof Error ? error : new Error('Express app returned a non-Error callback value.'), ); return; } resolve(res); }); }); } function isRecord(value: unknown): value is Record { return typeof value === 'object' && value !== null; } function asRecord(value: unknown): Record { if (isRecord(value)) return value; throw new TypeError('Expected value to be an object record.'); } void test('createEntityRouter passes current user, runtime context, and host into create service calls', async () => { const { app, calls } = buildHarness(); const response = await dispatchApp(app, { method: 'POST', url: '/entities', headers: { referer: 'https://builder.example.test/projects', }, body: { data: { name: 'Lobby' } }, }); assert.equal(response.statusCode, 200); assert.deepEqual(response._getData(), { id: ENTITY_ID, name: 'Lobby' }); const call = calls.find((item) => item.name === 'create'); assert.ok(call); const payload = asRecord(call.payload); assert.equal(payload.host, 'https://builder.example.test'); assert.equal(asRecord(payload.currentUser).id, USER_ID); assert.equal( asRecord(payload.runtimeContext).headerProjectSlug, 'demo-tour', ); }); void test('createEntityRouter normalizes list query controls and preserves filters', async () => { const { app, calls } = buildHarness(); const response = await dispatchApp(app, { method: 'GET', url: '/entities?limit=1000&page=2&sort=asc&field=name&unknown=keep', }); assert.equal(response.statusCode, 200); assert.deepEqual(response._getData(), { rows: [{ id: ENTITY_ID, name: 'Lobby' }], count: 1, }); const call = calls.find((item) => item.name === 'findAll'); assert.ok(call); const payload = asRecord(call.payload); assert.deepEqual(payload.query, { limit: 1000, page: 2, unknown: 'keep', sort: 'ASC', field: 'name', }); assert.equal(asRecord(payload.options).currentUser !== undefined, true); }); void test('createEntityRouter rejects mismatched body ids before update service call', async () => { const { app, calls } = buildHarness(); const response = await dispatchApp(app, { method: 'PUT', url: `/entities/${ENTITY_ID}`, body: { data: { id: '6a373ead-7ff4-4f1c-9a69-29ff1e97420d', name: 'Mismatch', }, }, }); assert.equal(response.statusCode, 400); assert.equal(response._getData(), 'Request body id does not match route id'); assert.equal(calls.some((item) => item.name === 'update'), false); }); void test('createEntityRouter emits CSV exports with configured fields', async () => { const { app } = buildHarness(); const response = await dispatchApp(app, { method: 'GET', url: '/entities?filetype=csv', }); assert.equal(response.statusCode, 200); const disposition: unknown = response.getHeader('content-disposition'); if (typeof disposition !== 'string') { throw new TypeError('Expected content-disposition response header.'); } assert.match(disposition, /export\.csv/); const csvPayload: unknown = response._getData(); if (typeof csvPayload !== 'string') { throw new TypeError('Expected CSV response body.'); } assert.equal( csvPayload.trim(), '"id","createdAt"\n"aa0b89ab-b0db-4d6c-a59c-c7785fc3eae5",', ); });