From 36a030b37add7672d7a28e92b0ee5686b7ff4616 Mon Sep 17 00:00:00 2001 From: RiceWithMeat <47690223+RiceWithMeat@users.noreply.github.com> Date: Tue, 21 May 2024 03:02:30 +0700 Subject: [PATCH 1/3] =?UTF-8?q?#167=20=F0=9F=90=98=20call=20server=20respo?= =?UTF-8?q?nse=20interceptors=20for=20database=20requests,=20add=20detabas?= =?UTF-8?q?e=20api=20interceptors,=20add=20baseUrl=20field=20for=20databas?= =?UTF-8?q?e?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../databaseConfigSchema.ts | 6 +- .../createDatabaseRoutes.test.ts | 178 +++++++----- .../createDatabaseRoutes.ts | 71 ++++- .../createNestedDatabaseRoutes.test.ts | 8 +- .../createNestedDatabaseRoutes.ts | 270 +++++++++++------- .../createShallowDatabaseRoutes.test.ts | 8 +- .../createShallowDatabaseRoutes.ts | 211 +++++++++----- .../createDatabaseMockServer.ts | 8 +- .../createGraphQLMockServer.ts | 6 +- .../createMockServer/createMockServer.ts | 20 +- .../createRestMockServer.ts | 6 +- src/utils/types/server.ts | 2 + 12 files changed, 513 insertions(+), 281 deletions(-) diff --git a/bin/validateMockServerConfig/databaseConfigSchema/databaseConfigSchema.ts b/bin/validateMockServerConfig/databaseConfigSchema/databaseConfigSchema.ts index deef36a7..b1be460f 100644 --- a/bin/validateMockServerConfig/databaseConfigSchema/databaseConfigSchema.ts +++ b/bin/validateMockServerConfig/databaseConfigSchema/databaseConfigSchema.ts @@ -1,13 +1,17 @@ import { z } from 'zod'; +import { baseUrlSchema } from '../baseUrlSchema/baseUrlSchema'; +import { interceptorsSchema } from '../interceptorsSchema/interceptorsSchema'; import { plainObjectSchema, stringForwardSlashSchema, stringJsonFilenameSchema } from '../utils'; export const databaseConfigSchema = z.strictObject({ + baseUrl: baseUrlSchema.optional(), data: z.union([plainObjectSchema(z.record(z.unknown())), stringJsonFilenameSchema]), routes: z .union([ plainObjectSchema(z.record(stringForwardSlashSchema, stringForwardSlashSchema)), stringJsonFilenameSchema ]) - .optional() + .optional(), + interceptors: plainObjectSchema(interceptorsSchema).optional() }); diff --git a/src/core/database/createDatabaseRoutes/createDatabaseRoutes.test.ts b/src/core/database/createDatabaseRoutes/createDatabaseRoutes.test.ts index 06712ec4..70fdebd1 100644 --- a/src/core/database/createDatabaseRoutes/createDatabaseRoutes.test.ts +++ b/src/core/database/createDatabaseRoutes/createDatabaseRoutes.test.ts @@ -5,108 +5,142 @@ import path from 'path'; import request from 'supertest'; import { createDatabaseRoutes } from '@/core/database'; +import { urlJoin } from '@/utils/helpers'; import { createTmpDir } from '@/utils/helpers/tests'; import type { DatabaseConfig, MockServerConfig } from '@/utils/types'; import { findIndexById } from './helpers'; -describe('createDatabaseRoutes', () => { - const createServer = ( - mockServerConfig: Pick & { database: DatabaseConfig } - ) => { - const server = express(); - const routerBase = express.Router(); - const routesWithDatabaseRoutes = createDatabaseRoutes(routerBase, mockServerConfig.database); +const createServer = ( + mockServerConfig: Pick & { + database: DatabaseConfig; + } +) => { + const { baseUrl, database, interceptors } = mockServerConfig; + const server = express(); + const routerBase = express.Router(); + const routesWithDatabaseRoutes = createDatabaseRoutes({ + router: routerBase, + databaseConfig: database, + serverResponseInterceptor: interceptors?.response + }); - server.use(mockServerConfig.baseUrl ?? '/', routesWithDatabaseRoutes); - return server; - }; + const databaseBaseUrl = urlJoin(baseUrl ?? '/', database?.baseUrl ?? '/'); - describe('createDatabaseRoutes: routes and data successfully works when passing them by object', () => { - const data = { profile: { name: 'John' }, users: [{ id: 1 }, { id: 2 }] }; - const routes = { '/api/profile': '/profile' } as const; - const server = createServer({ database: { data, routes } }); + server.use(databaseBaseUrl, routesWithDatabaseRoutes); + return server; +}; - test('Should overwrite routes according to routes object (but default url should work too)', async () => { - const overwrittenUrlResponse = await request(server).get('/api/profile'); - expect(overwrittenUrlResponse.body).toStrictEqual(data.profile); +describe('createDatabaseRoutes: routes and data successfully works when passing them by object', () => { + const data = { profile: { name: 'John' }, users: [{ id: 1 }, { id: 2 }] }; + const routes = { '/api/profile': '/profile' } as const; + const server = createServer({ database: { data, routes } }); - const defaultUrlResponse = await request(server).get('/profile'); - expect(defaultUrlResponse.body).toStrictEqual(data.profile); - }); + test('Should overwrite routes according to routes object (but default url should work too)', async () => { + const overwrittenUrlResponse = await request(server).get('/api/profile'); + expect(overwrittenUrlResponse.body).toStrictEqual(data.profile); - test('Should successfully handle requests to shallow and nested database parts', async () => { - const shallowDatabaseResponse = await request(server).get('/profile'); - expect(shallowDatabaseResponse.body).toStrictEqual(data.profile); + const defaultUrlResponse = await request(server).get('/profile'); + expect(defaultUrlResponse.body).toStrictEqual(data.profile); + }); - const nestedDatabaseCollectionResponse = await request(server).get('/users'); - expect(nestedDatabaseCollectionResponse.body).toStrictEqual(data.users); + test('Should successfully handle requests to shallow and nested database parts', async () => { + const shallowDatabaseResponse = await request(server).get('/profile'); + expect(shallowDatabaseResponse.body).toStrictEqual(data.profile); - const nestedDatabaseItemResponse = await request(server).get('/users/1'); - expect(nestedDatabaseItemResponse.body).toStrictEqual( - data.users[findIndexById(data.users, 1)] - ); - }); + const nestedDatabaseCollectionResponse = await request(server).get('/users'); + expect(nestedDatabaseCollectionResponse.body).toStrictEqual(data.users); + + const nestedDatabaseItemResponse = await request(server).get('/users/1'); + expect(nestedDatabaseItemResponse.body).toStrictEqual(data.users[findIndexById(data.users, 1)]); }); +}); - describe('createDatabaseRoutes: routes and data successfully works when passing them by file', () => { - const data = { profile: { name: 'John' }, users: [{ id: 1 }, { id: 2 }] }; - const routes = { '/api/profile': '/profile' } as const; +describe('createDatabaseRoutes: routes and data successfully works when passing them by file', () => { + const data = { profile: { name: 'John' }, users: [{ id: 1 }, { id: 2 }] }; + const routes = { '/api/profile': '/profile' } as const; - let tmpDirPath: string; - let server: Express; + let tmpDirPath: string; + let server: Express; - beforeAll(() => { - tmpDirPath = createTmpDir(); + beforeAll(() => { + tmpDirPath = createTmpDir(); - const pathToData = path.join(tmpDirPath, './data.json') as `${string}.json`; - fs.writeFileSync(pathToData, JSON.stringify(data)); + const pathToData = path.join(tmpDirPath, './data.json') as `${string}.json`; + fs.writeFileSync(pathToData, JSON.stringify(data)); - const pathToRoutes = path.join(tmpDirPath, './routes.json') as `${string}.json`; - fs.writeFileSync(pathToRoutes, JSON.stringify(routes)); + const pathToRoutes = path.join(tmpDirPath, './routes.json') as `${string}.json`; + fs.writeFileSync(pathToRoutes, JSON.stringify(routes)); - server = createServer({ database: { data: pathToData, routes: pathToRoutes } }); - }); + server = createServer({ database: { data: pathToData, routes: pathToRoutes } }); + }); - afterAll(() => { - fs.rmSync(tmpDirPath, { recursive: true, force: true }); - }); + afterAll(() => { + fs.rmSync(tmpDirPath, { recursive: true, force: true }); + }); - test('Should overwrite routes according to routes object (but default url should work too)', async () => { - const overwrittenUrlResponse = await request(server).get('/api/profile'); - expect(overwrittenUrlResponse.body).toStrictEqual(data.profile); + test('Should overwrite routes according to routes object (but default url should work too)', async () => { + const overwrittenUrlResponse = await request(server).get('/api/profile'); + expect(overwrittenUrlResponse.body).toStrictEqual(data.profile); - const defaultUrlResponse = await request(server).get('/profile'); - expect(defaultUrlResponse.body).toStrictEqual(data.profile); - }); + const defaultUrlResponse = await request(server).get('/profile'); + expect(defaultUrlResponse.body).toStrictEqual(data.profile); + }); - test('Should successfully handle requests to shallow and nested database parts', async () => { - const shallowDatabaseResponse = await request(server).get('/profile'); - expect(shallowDatabaseResponse.body).toStrictEqual(data.profile); + test('Should successfully handle requests to shallow and nested database parts', async () => { + const shallowDatabaseResponse = await request(server).get('/profile'); + expect(shallowDatabaseResponse.body).toStrictEqual(data.profile); - const nestedDatabaseCollectionResponse = await request(server).get('/users'); - expect(nestedDatabaseCollectionResponse.body).toStrictEqual(data.users); + const nestedDatabaseCollectionResponse = await request(server).get('/users'); + expect(nestedDatabaseCollectionResponse.body).toStrictEqual(data.users); - const nestedDatabaseItemResponse = await request(server).get('/users/1'); - expect(nestedDatabaseItemResponse.body).toStrictEqual( - data.users[findIndexById(data.users, 1)] - ); - }); + const nestedDatabaseItemResponse = await request(server).get('/users/1'); + expect(nestedDatabaseItemResponse.body).toStrictEqual(data.users[findIndexById(data.users, 1)]); + }); +}); + +describe('createDatabaseRoutes: routes /__routes and /__db', () => { + const data = { profile: { name: 'John' }, users: [{ id: 1 }, { id: 2 }] }; + const routes = { '/api/profile': '/profile' } as const; + const server = createServer({ database: { data, routes } }); + + test('Should create /__db route that return data from databaseConfig', async () => { + const response = await request(server).get('/__db'); + expect(response.body).toStrictEqual(data); }); - describe('createDatabaseRoutes: routes /__routes and /__db', () => { + test('Should create /__routes route that return routes from databaseConfig', async () => { + const response = await request(server).get('/__routes'); + expect(response.body).toStrictEqual(routes); + }); +}); + +describe('createDatabaseRoutes: interceptors', () => { + test('Should call response interceptors in order: api -> server', async () => { + const apiInterceptor = jest.fn(); + const serverInterceptor = jest.fn(); + const data = { profile: { name: 'John' }, users: [{ id: 1 }, { id: 2 }] }; const routes = { '/api/profile': '/profile' } as const; - const server = createServer({ database: { data, routes } }); - - test('Should create /__db route that return data from databaseConfig', async () => { - const response = await request(server).get('/__db'); - expect(response.body).toStrictEqual(data); + const server = createServer({ + database: { + data, + routes, + interceptors: { + response: apiInterceptor + } + }, + interceptors: { + response: serverInterceptor + } }); - test('Should create /__routes route that return routes from databaseConfig', async () => { - const response = await request(server).get('/__routes'); - expect(response.body).toStrictEqual(routes); - }); + await request(server).get('/profile'); + + expect(apiInterceptor.mock.calls.length).toBe(1); + expect(serverInterceptor.mock.calls.length).toBe(1); + expect(apiInterceptor.mock.invocationCallOrder[0]).toBeLessThan( + serverInterceptor.mock.invocationCallOrder[0] + ); }); }); diff --git a/src/core/database/createDatabaseRoutes/createDatabaseRoutes.ts b/src/core/database/createDatabaseRoutes/createDatabaseRoutes.ts index e7245223..b5a2a6b2 100644 --- a/src/core/database/createDatabaseRoutes/createDatabaseRoutes.ts +++ b/src/core/database/createDatabaseRoutes/createDatabaseRoutes.ts @@ -1,6 +1,7 @@ import type { IRouter } from 'express'; -import type { DatabaseConfig, NestedDatabase, ShallowDatabase } from '@/utils/types'; +import { asyncHandler, callResponseInterceptors } from '@/utils/helpers'; +import type { DatabaseConfig, Interceptors, NestedDatabase, ShallowDatabase } from '@/utils/types'; import { createNestedDatabaseRoutes, @@ -13,26 +14,76 @@ import { FileStorage, MemoryStorage } from './storages'; const isVariableJsonFile = (variable: unknown): variable is `${string}.json` => typeof variable === 'string' && variable.endsWith('.json'); -export const createDatabaseRoutes = (router: IRouter, { data, routes }: DatabaseConfig) => { +interface CreateDatabaseRoutesParams { + router: IRouter; + databaseConfig: DatabaseConfig; + serverResponseInterceptor?: Interceptors['response']; +} + +export const createDatabaseRoutes = ({ + router, + databaseConfig, + serverResponseInterceptor +}: CreateDatabaseRoutesParams) => { + const { data, routes } = databaseConfig; + if (routes) { const storage = isVariableJsonFile(routes) ? new FileStorage(routes) : new MemoryStorage(routes); createRewrittenDatabaseRoutes(router, storage.read()); - router.route('/__routes').get((_request, response) => { - response.json(storage.read()); - }); + router.route('/__routes').get( + asyncHandler(async (request, response) => { + const data = await callResponseInterceptors({ + data: storage.read(), + request, + response, + interceptors: { + apiInterceptor: databaseConfig.interceptors?.response, + serverInterceptor: serverResponseInterceptor + } + }); + response.json(data); + }) + ); } const storage = isVariableJsonFile(data) ? new FileStorage(data) : new MemoryStorage(data); const { shallowDatabase, nestedDatabase } = splitDatabaseByNesting(storage.read()); - createShallowDatabaseRoutes(router, shallowDatabase, storage as MemoryStorage); - createNestedDatabaseRoutes(router, nestedDatabase, storage as MemoryStorage); - - router.route('/__db').get((_request, response) => { - response.json(storage.read()); + createShallowDatabaseRoutes({ + router, + database: shallowDatabase, + storage: storage as MemoryStorage, + responseInterceptors: { + apiInterceptor: databaseConfig.interceptors?.response, + serverInterceptor: serverResponseInterceptor + } }); + createNestedDatabaseRoutes({ + router, + database: nestedDatabase, + storage: storage as MemoryStorage, + responseInterceptors: { + apiInterceptor: databaseConfig.interceptors?.response, + serverInterceptor: serverResponseInterceptor + } + }); + + router.route('/__db').get( + asyncHandler(async (request, response) => { + const data = await callResponseInterceptors({ + data: storage.read(), + request, + response, + interceptors: { + apiInterceptor: databaseConfig.interceptors?.response, + serverInterceptor: serverResponseInterceptor + } + }); + response.json(data); + }) + ); return router; }; diff --git a/src/core/database/createDatabaseRoutes/helpers/createNestedDatabaseRoutes/createNestedDatabaseRoutes.test.ts b/src/core/database/createDatabaseRoutes/helpers/createNestedDatabaseRoutes/createNestedDatabaseRoutes.test.ts index f3fedb69..5b533e92 100644 --- a/src/core/database/createDatabaseRoutes/helpers/createNestedDatabaseRoutes/createNestedDatabaseRoutes.test.ts +++ b/src/core/database/createDatabaseRoutes/helpers/createNestedDatabaseRoutes/createNestedDatabaseRoutes.test.ts @@ -21,11 +21,11 @@ describe('CreateNestedDatabaseRoutes', () => { const routerBase = express.Router(); const storage = new MemoryStorage(nestedDatabase); - const routerWithNestedDatabaseRoutes = createNestedDatabaseRoutes( - routerBase, - nestedDatabase, + const routerWithNestedDatabaseRoutes = createNestedDatabaseRoutes({ + router: routerBase, + database: nestedDatabase, storage - ); + }); server.use(express.json()); server.use(express.text()); diff --git a/src/core/database/createDatabaseRoutes/helpers/createNestedDatabaseRoutes/createNestedDatabaseRoutes.ts b/src/core/database/createDatabaseRoutes/helpers/createNestedDatabaseRoutes/createNestedDatabaseRoutes.ts index 2644136e..e5231658 100644 --- a/src/core/database/createDatabaseRoutes/helpers/createNestedDatabaseRoutes/createNestedDatabaseRoutes.ts +++ b/src/core/database/createDatabaseRoutes/helpers/createNestedDatabaseRoutes/createNestedDatabaseRoutes.ts @@ -1,7 +1,8 @@ import type { IRouter } from 'express'; import type { ParsedUrlQuery } from 'node:querystring'; -import type { NestedDatabase } from '@/utils/types'; +import { asyncHandler, callResponseInterceptors } from '@/utils/helpers'; +import type { Interceptors, NestedDatabase } from '@/utils/types'; import type { MemoryStorage } from '../../storages'; import { createNewId, findIndexById } from '../array'; @@ -10,74 +11,97 @@ import { pagination } from '../pagination/pagination'; import { search } from '../search/search'; import { sort } from '../sort/sort'; -export const createNestedDatabaseRoutes = ( - router: IRouter, - database: NestedDatabase, - storage: MemoryStorage -) => { +interface CreateNestedDatabaseRoutesParams { + router: IRouter; + database: NestedDatabase; + storage: MemoryStorage; + responseInterceptors?: { + apiInterceptor?: Interceptors['response']; + serverInterceptor?: Interceptors['response']; + }; +} + +export const createNestedDatabaseRoutes = ({ + router, + database, + storage, + responseInterceptors +}: CreateNestedDatabaseRoutesParams) => { Object.keys(database).forEach((key) => { const collectionPath = `/${key}`; const itemPath = `/${key}/:id`; - router.route(collectionPath).get((request, response) => { - let data = storage.read(key); + router.route(collectionPath).get( + asyncHandler(async (request, response) => { + let data = storage.read(key); - if (!Array.isArray(data) || !request.query) { - // ✅ important: - // set 'Cache-Control' header for explicit browsers response revalidate - // https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Cache-Control - response.set('Cache-control', 'max-age=0, must-revalidate'); - return response.json(data); - } - - const { _page, _limit, _begin, _end, _sort, _order, _q, ...filters } = request.query; + if (!Array.isArray(data) || !request.query) { + // ✅ important: + // set 'Cache-Control' header for explicit browsers response revalidate + // https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Cache-Control + response.set('Cache-control', 'max-age=0, must-revalidate'); + return response.json(data); + } - if (Object.keys(filters).length) { - data = filter(data, filters as ParsedUrlQuery); - } + const { _page, _limit, _begin, _end, _sort, _order, _q, ...filters } = request.query; - if (_q) { - data = search(data, request.query._q as ParsedUrlQuery); - } + if (Object.keys(filters).length) { + data = filter(data, filters as ParsedUrlQuery); + } - if (_page) { - data = pagination(data, request.query as ParsedUrlQuery); - if (data._link) { - const links = {} as any; - const fullUrl = `${request.protocol}://${request.get('host')}${request.originalUrl}`; + if (_q) { + data = search(data, request.query._q as ParsedUrlQuery); + } - if (data._link.first) { - links.first = fullUrl.replace(`page=${data._link.current}`, `page=${data._link.first}`); - } - if (data._link.prev) { - links.prev = fullUrl.replace(`page=${data._link.current}`, `page=${data._link.prev}`); - } - if (data._link.next) { - links.next = fullUrl.replace(`page=${data._link.current}`, `page=${data._link.next}`); - } - if (data._link.last) { - links.last = fullUrl.replace(`page=${data._link.current}`, `page=${data._link.last}`); + if (_page) { + data = pagination(data, request.query as ParsedUrlQuery); + if (data._link) { + const links = {} as any; + const fullUrl = `${request.protocol}://${request.get('host')}${request.originalUrl}`; + + if (data._link.first) { + links.first = fullUrl.replace( + `page=${data._link.current}`, + `page=${data._link.first}` + ); + } + if (data._link.prev) { + links.prev = fullUrl.replace(`page=${data._link.current}`, `page=${data._link.prev}`); + } + if (data._link.next) { + links.next = fullUrl.replace(`page=${data._link.current}`, `page=${data._link.next}`); + } + if (data._link.last) { + links.last = fullUrl.replace(`page=${data._link.current}`, `page=${data._link.last}`); + } + + data._link = { ...data._link, ...links }; + response.links(links); } + } - data._link = { ...data._link, ...links }; - response.links(links); + if (_sort) { + data = sort(data, request.query as ParsedUrlQuery); } - } - - if (_sort) { - data = sort(data, request.query as ParsedUrlQuery); - } - - if (_begin || _end) { - data = data.slice(request.query._begin ?? 0, request.query._end); - response.set('X-Total-Count', data.length); - } - // ✅ important: - // set 'Cache-Control' header for explicit browsers response revalidate - // https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Cache-Control - response.set('Cache-control', 'no-cache'); - response.json(data); - }); + + if (_begin || _end) { + data = data.slice(request.query._begin ?? 0, request.query._end); + response.set('X-Total-Count', data.length); + } + // ✅ important: + // set 'Cache-Control' header for explicit browsers response revalidate + // https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Cache-Control + response.set('Cache-control', 'no-cache'); + + const updatedData = await callResponseInterceptors({ + data, + request, + response, + interceptors: responseInterceptors + }); + response.json(updatedData); + }) + ); router.route(collectionPath).post((request, response) => { const collection = storage.read(key); @@ -88,59 +112,91 @@ export const createNestedDatabaseRoutes = ( response.status(201).json(newResource); }); - router.route(itemPath).get((request, response) => { - const currentResourceCollection = storage.read(key); - const currentResourceIndex = findIndexById(currentResourceCollection, request.params.id); - if (currentResourceIndex === -1) { - response.status(404).end(); - return; - } - - // ✅ important: - // set 'Cache-Control' header for explicit browsers response revalidate - // https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Cache-Control - response.set('Cache-control', 'no-cache'); - response.json(storage.read([key, currentResourceIndex])); - }); + router.route(itemPath).get( + asyncHandler(async (request, response) => { + const currentResourceCollection = storage.read(key); + const currentResourceIndex = findIndexById(currentResourceCollection, request.params.id); + if (currentResourceIndex === -1) { + response.status(404).end(); + return; + } - router.route(itemPath).put((request, response) => { - const currentResourceCollection = storage.read(key); - const currentResourceIndex = findIndexById(currentResourceCollection, request.params.id); - if (currentResourceIndex === -1) { - response.status(404).end(); - return; - } - - const currentResource = storage.read([key, currentResourceIndex]); - const updatedResource = { ...request.body, id: currentResource.id }; - storage.write([key, currentResourceIndex], updatedResource); - response.json(updatedResource); - }); + // ✅ important: + // set 'Cache-Control' header for explicit browsers response revalidate + // https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Cache-Control + response.set('Cache-control', 'no-cache'); + const updatedData = await callResponseInterceptors({ + data: storage.read([key, currentResourceIndex]), + request, + response, + interceptors: responseInterceptors + }); + response.json(updatedData); + }) + ); + + router.route(itemPath).put( + asyncHandler(async (request, response) => { + const currentResourceCollection = storage.read(key); + const currentResourceIndex = findIndexById(currentResourceCollection, request.params.id); + if (currentResourceIndex === -1) { + response.status(404).end(); + return; + } - router.route(itemPath).patch((request, response) => { - const currentResourceCollection = storage.read(key); - const currentResourceIndex = findIndexById(currentResourceCollection, request.params.id); - if (currentResourceIndex === -1) { - response.status(404).end(); - return; - } - - const currentResource = storage.read([key, currentResourceIndex]); - const updatedResource = { ...currentResource, ...request.body, id: currentResource.id }; - storage.write([key, currentResourceIndex], updatedResource); - response.json(updatedResource); - }); + const currentResource = storage.read([key, currentResourceIndex]); + const updatedResource = { ...request.body, id: currentResource.id }; + storage.write([key, currentResourceIndex], updatedResource); + const updatedData = await callResponseInterceptors({ + data: updatedResource, + request, + response, + interceptors: responseInterceptors + }); + response.json(updatedData); + }) + ); + + router.route(itemPath).patch( + asyncHandler(async (request, response) => { + const currentResourceCollection = storage.read(key); + const currentResourceIndex = findIndexById(currentResourceCollection, request.params.id); + if (currentResourceIndex === -1) { + response.status(404).end(); + return; + } - router.route(itemPath).delete((request, response) => { - const currentResourceCollection = storage.read(key); - const currentResourceIndex = findIndexById(currentResourceCollection, request.params.id); - if (currentResourceIndex === -1) { - response.status(404).end(); - return; - } - storage.delete([key, currentResourceIndex]); - response.status(204).end(); - }); + const currentResource = storage.read([key, currentResourceIndex]); + const updatedResource = { ...currentResource, ...request.body, id: currentResource.id }; + storage.write([key, currentResourceIndex], updatedResource); + const updatedData = await callResponseInterceptors({ + data: updatedResource, + request, + response, + interceptors: responseInterceptors + }); + response.json(updatedData); + }) + ); + + router.route(itemPath).delete( + asyncHandler(async (request, response) => { + const currentResourceCollection = storage.read(key); + const currentResourceIndex = findIndexById(currentResourceCollection, request.params.id); + if (currentResourceIndex === -1) { + response.status(404).end(); + return; + } + storage.delete([key, currentResourceIndex]); + await callResponseInterceptors({ + data: {}, + request, + response, + interceptors: responseInterceptors + }); + response.status(204).end(); + }) + ); }); return router; diff --git a/src/core/database/createDatabaseRoutes/helpers/createShallowDatabaseRoutes/createShallowDatabaseRoutes.test.ts b/src/core/database/createDatabaseRoutes/helpers/createShallowDatabaseRoutes/createShallowDatabaseRoutes.test.ts index c9246081..76659b06 100644 --- a/src/core/database/createDatabaseRoutes/helpers/createShallowDatabaseRoutes/createShallowDatabaseRoutes.test.ts +++ b/src/core/database/createDatabaseRoutes/helpers/createShallowDatabaseRoutes/createShallowDatabaseRoutes.test.ts @@ -23,11 +23,11 @@ describe('createShallowDatabaseRoutes', () => { const routerBase = express.Router(); const storage = new MemoryStorage(shallowDatabase); - const routerWithRoutesForShallowDatabase = createShallowDatabaseRoutes( - routerBase, - shallowDatabase, + const routerWithRoutesForShallowDatabase = createShallowDatabaseRoutes({ + router: routerBase, + database: shallowDatabase, storage - ); + }); server.use(express.json()); server.use(express.text()); diff --git a/src/core/database/createDatabaseRoutes/helpers/createShallowDatabaseRoutes/createShallowDatabaseRoutes.ts b/src/core/database/createDatabaseRoutes/helpers/createShallowDatabaseRoutes/createShallowDatabaseRoutes.ts index 43271473..894a592f 100644 --- a/src/core/database/createDatabaseRoutes/helpers/createShallowDatabaseRoutes/createShallowDatabaseRoutes.ts +++ b/src/core/database/createDatabaseRoutes/helpers/createShallowDatabaseRoutes/createShallowDatabaseRoutes.ts @@ -1,7 +1,8 @@ import type { IRouter } from 'express'; import type { ParsedUrlQuery } from 'node:querystring'; -import type { ShallowDatabase } from '@/utils/types'; +import { asyncHandler, callResponseInterceptors } from '@/utils/helpers'; +import type { Interceptors, ShallowDatabase } from '@/utils/types'; import type { MemoryStorage } from '../../storages'; import { filter } from '../filter/filter'; @@ -9,93 +10,149 @@ import { pagination } from '../pagination/pagination'; import { search } from '../search/search'; import { sort } from '../sort/sort'; -export const createShallowDatabaseRoutes = ( - router: IRouter, - database: ShallowDatabase, - storage: MemoryStorage -) => { +interface CreateShallowDatabaseRoutesParams { + router: IRouter; + database: ShallowDatabase; + storage: MemoryStorage; + responseInterceptors?: { + apiInterceptor?: Interceptors['response']; + serverInterceptor?: Interceptors['response']; + }; +} + +export const createShallowDatabaseRoutes = ({ + router, + database, + storage, + responseInterceptors +}: CreateShallowDatabaseRoutesParams) => { Object.keys(database).forEach((key) => { const path = `/${key}`; - router.route(path).get((request, response) => { - let data = storage.read(key); - - if (!Array.isArray(data) || !request.query) { - // ✅ important: - // set 'Cache-Control' header for explicit browsers response revalidate - // https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Cache-Control - response.set('Cache-control', 'no-cache'); - return response.json(data); - } - - data = data.filter((element) => typeof element === 'object' && element !== null); + router.route(path).get( + asyncHandler(async (request, response) => { + let data = storage.read(key); + + if (!Array.isArray(data) || !request.query) { + // ✅ important: + // set 'Cache-Control' header for explicit browsers response revalidate + // https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Cache-Control + response.set('Cache-control', 'no-cache'); + + const updatedData = await callResponseInterceptors({ + data, + request, + response, + interceptors: responseInterceptors + }); + return response.json(updatedData); + } - const { _page, _limit, _begin, _end, _sort, _order, _q, ...filters } = request.query; + data = data.filter((element) => typeof element === 'object' && element !== null); - if (Object.keys(filters).length) { - data = filter(data, filters as ParsedUrlQuery); - } + const { _page, _limit, _begin, _end, _sort, _order, _q, ...filters } = request.query; - if (_q) { - data = search(data, request.query._q as ParsedUrlQuery); - } + if (Object.keys(filters).length) { + data = filter(data, filters as ParsedUrlQuery); + } - if (_page) { - data = pagination(data, request.query as ParsedUrlQuery); - if (data._link) { - const links = {} as any; - const fullUrl = `${request.protocol}://${request.get('host')}${request.originalUrl}`; + if (_q) { + data = search(data, request.query._q as ParsedUrlQuery); + } - if (data._link.first) { - links.first = fullUrl.replace(`page=${data._link.current}`, `page=${data._link.first}`); - } - if (data._link.prev) { - links.prev = fullUrl.replace(`page=${data._link.current}`, `page=${data._link.prev}`); - } - if (data._link.next) { - links.next = fullUrl.replace(`page=${data._link.current}`, `page=${data._link.next}`); - } - if (data._link.last) { - links.last = fullUrl.replace(`page=${data._link.current}`, `page=${data._link.last}`); + if (_page) { + data = pagination(data, request.query as ParsedUrlQuery); + if (data._link) { + const links = {} as any; + const fullUrl = `${request.protocol}://${request.get('host')}${request.originalUrl}`; + + if (data._link.first) { + links.first = fullUrl.replace( + `page=${data._link.current}`, + `page=${data._link.first}` + ); + } + if (data._link.prev) { + links.prev = fullUrl.replace(`page=${data._link.current}`, `page=${data._link.prev}`); + } + if (data._link.next) { + links.next = fullUrl.replace(`page=${data._link.current}`, `page=${data._link.next}`); + } + if (data._link.last) { + links.last = fullUrl.replace(`page=${data._link.current}`, `page=${data._link.last}`); + } + + data._link = { ...data._link, ...links }; + response.links(links); } + } + + if (_sort) { + data = sort(data, request.query as ParsedUrlQuery); + } - data._link = { ...data._link, ...links }; - response.links(links); + if (_begin || _end) { + data = data.slice(request.query._begin ?? 0, request.query._end); + response.set('X-Total-Count', data.length); } - } - - if (_sort) { - data = sort(data, request.query as ParsedUrlQuery); - } - - if (_begin || _end) { - data = data.slice(request.query._begin ?? 0, request.query._end); - response.set('X-Total-Count', data.length); - } - // ✅ important: - // set 'Cache-Control' header for explicit browsers response revalidate - // https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Cache-Control - response.set('Cache-control', 'no-cache'); - response.json(data); - }); - - router.route(path).post((request, response) => { - storage.write(key, request.body); - response.set('Location', request.url); - response.status(201).json(request.body); - }); - - router.route(path).put((request, response) => { - storage.write(key, request.body); - response.json(request.body); - }); - - router.route(path).patch((request, response) => { - const currentResource = storage.read(key); - const updatedResource = { ...currentResource, ...request.body }; - storage.write(key, updatedResource); - response.json(updatedResource); - }); + // ✅ important: + // set 'Cache-Control' header for explicit browsers response revalidate + // https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Cache-Control + response.set('Cache-control', 'no-cache'); + + const updatedData = await callResponseInterceptors({ + data, + request, + response, + interceptors: responseInterceptors + }); + + response.json(updatedData); + }) + ); + + router.route(path).post( + asyncHandler(async (request, response) => { + storage.write(key, request.body); + response.set('Location', request.url); + response.status(201); + const updatedData = await callResponseInterceptors({ + data: request.body, + request, + response, + interceptors: responseInterceptors + }); + response.json(updatedData); + }) + ); + + router.route(path).put( + asyncHandler(async (request, response) => { + storage.write(key, request.body); + const updatedData = await callResponseInterceptors({ + data: request.body, + request, + response, + interceptors: responseInterceptors + }); + response.json(updatedData); + }) + ); + + router.route(path).patch( + asyncHandler(async (request, response) => { + const currentResource = storage.read(key); + const updatedResource = { ...currentResource, ...request.body }; + storage.write(key, updatedResource); + const updatedData = await callResponseInterceptors({ + data: updatedResource, + request, + response, + interceptors: responseInterceptors + }); + response.json(updatedData); + }) + ); }); return router; diff --git a/src/server/createDatabaseMockServer/createDatabaseMockServer.ts b/src/server/createDatabaseMockServer/createDatabaseMockServer.ts index 0ffd7a19..b4fd1ce1 100644 --- a/src/server/createDatabaseMockServer/createDatabaseMockServer.ts +++ b/src/server/createDatabaseMockServer/createDatabaseMockServer.ts @@ -19,7 +19,7 @@ export const createDatabaseMockServer = ( databaseMockServerConfig: Omit, server: Express = express() ) => { - const { cors, staticPath, data, routes } = databaseMockServerConfig; + const { cors, staticPath, data, routes, interceptors } = databaseMockServerConfig; server.set('view engine', 'ejs'); server.set('views', urlJoin(__dirname, '../../static/views')); @@ -51,7 +51,11 @@ export const createDatabaseMockServer = ( staticMiddleware(server, baseUrl, staticPath); } - const routerWithDatabaseRoutes = createDatabaseRoutes(express.Router(), { data, routes }); + const routerWithDatabaseRoutes = createDatabaseRoutes({ + router: express.Router(), + databaseConfig: { data, routes }, + serverResponseInterceptor: interceptors?.response + }); server.use(baseUrl, routerWithDatabaseRoutes); notFoundMiddleware(server, databaseMockServerConfig); diff --git a/src/server/createGraphQLMockServer/createGraphQLMockServer.ts b/src/server/createGraphQLMockServer/createGraphQLMockServer.ts index 07ce2ac2..b20672e6 100644 --- a/src/server/createGraphQLMockServer/createGraphQLMockServer.ts +++ b/src/server/createGraphQLMockServer/createGraphQLMockServer.ts @@ -61,7 +61,11 @@ export const createGraphQLMockServer = ( server.use(baseUrl, routerWithGraphqlRoutes); if (database) { - const routerWithDatabaseRoutes = createDatabaseRoutes(express.Router(), database); + const routerWithDatabaseRoutes = createDatabaseRoutes({ + router: express.Router(), + databaseConfig: database, + serverResponseInterceptor: interceptors?.response + }); server.use(baseUrl, routerWithDatabaseRoutes); } diff --git a/src/server/createMockServer/createMockServer.ts b/src/server/createMockServer/createMockServer.ts index b1960a01..2f669567 100644 --- a/src/server/createMockServer/createMockServer.ts +++ b/src/server/createMockServer/createMockServer.ts @@ -96,8 +96,24 @@ export const createMockServer = ( } if (database) { - const routerWithDatabaseRoutes = createDatabaseRoutes(express.Router(), database); - server.use(baseUrl, routerWithDatabaseRoutes); + const routerWithDatabaseRoutes = createDatabaseRoutes({ + router: express.Router(), + databaseConfig: database, + serverResponseInterceptor: interceptors?.response + }); + + const databaseBaseUrl = urlJoin(baseUrl, database.baseUrl ?? '/'); + + const apiRequestInterceptor = database.interceptors?.request; + if (apiRequestInterceptor) { + requestInterceptorMiddleware({ + server, + path: databaseBaseUrl, + interceptor: apiRequestInterceptor + }); + } + + server.use(databaseBaseUrl, routerWithDatabaseRoutes); } notFoundMiddleware(server, mockServerConfig); diff --git a/src/server/createRestMockServer/createRestMockServer.ts b/src/server/createRestMockServer/createRestMockServer.ts index 32f30eb9..4bb288b9 100644 --- a/src/server/createRestMockServer/createRestMockServer.ts +++ b/src/server/createRestMockServer/createRestMockServer.ts @@ -61,7 +61,11 @@ export const createRestMockServer = ( server.use(baseUrl, routerWithRestRoutes); if (database) { - const routerWithDatabaseRoutes = createDatabaseRoutes(express.Router(), database); + const routerWithDatabaseRoutes = createDatabaseRoutes({ + router: express.Router(), + databaseConfig: database, + serverResponseInterceptor: interceptors?.response + }); server.use(baseUrl, routerWithDatabaseRoutes); } diff --git a/src/utils/types/server.ts b/src/utils/types/server.ts index 46f20afe..955c2f11 100644 --- a/src/utils/types/server.ts +++ b/src/utils/types/server.ts @@ -35,8 +35,10 @@ export interface GraphqlConfig { } export type DatabaseConfig = { + baseUrl?: BaseUrl; data: Record | `${string}.json`; routes?: Record<`/${string}`, `/${string}`> | `${string}.json`; + interceptors?: Interceptors; }; export interface BaseMockServerConfig { From 716dde88046a0bdb571629349d9a65a76905d922 Mon Sep 17 00:00:00 2001 From: RiceWithMeat <47690223+RiceWithMeat@users.noreply.github.com> Date: Mon, 6 Jan 2025 02:10:51 +0700 Subject: [PATCH 2/3] =?UTF-8?q?#167=20=F0=9F=90=98=20call=20database=20req?= =?UTF-8?q?uest=20interceptor=20in=20createRestMockServer=20and=20createGr?= =?UTF-8?q?aphQLMockServer,=20remove=20call=20response=20interceptors=20fo?= =?UTF-8?q?r=20=5F=5Fdb=20and=20=5F=5Froutes=20routes,=20add=20missing=20c?= =?UTF-8?q?all=20response=20interceptors=20for=20post=20routes,=20set=20da?= =?UTF-8?q?ta=20to=20null=20in=20delete=20route?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../createDatabaseRoutes.ts | 35 +++------------ .../createNestedDatabaseRoutes.ts | 45 ++++++++++++++----- .../createShallowDatabaseRoutes.ts | 8 ++++ .../createDatabaseMockServer.ts | 2 +- .../createGraphQLMockServer.ts | 14 +++++- .../createMockServer/createMockServer.ts | 2 +- .../createRestMockServer.ts | 14 +++++- 7 files changed, 77 insertions(+), 43 deletions(-) diff --git a/src/core/database/createDatabaseRoutes/createDatabaseRoutes.ts b/src/core/database/createDatabaseRoutes/createDatabaseRoutes.ts index b5a2a6b2..183dc5f3 100644 --- a/src/core/database/createDatabaseRoutes/createDatabaseRoutes.ts +++ b/src/core/database/createDatabaseRoutes/createDatabaseRoutes.ts @@ -1,6 +1,5 @@ import type { IRouter } from 'express'; -import { asyncHandler, callResponseInterceptors } from '@/utils/helpers'; import type { DatabaseConfig, Interceptors, NestedDatabase, ShallowDatabase } from '@/utils/types'; import { @@ -33,20 +32,9 @@ export const createDatabaseRoutes = ({ : new MemoryStorage(routes); createRewrittenDatabaseRoutes(router, storage.read()); - router.route('/__routes').get( - asyncHandler(async (request, response) => { - const data = await callResponseInterceptors({ - data: storage.read(), - request, - response, - interceptors: { - apiInterceptor: databaseConfig.interceptors?.response, - serverInterceptor: serverResponseInterceptor - } - }); - response.json(data); - }) - ); + router.route('/__routes').get((_request, response) => { + response.json(storage.read()); + }); } const storage = isVariableJsonFile(data) ? new FileStorage(data) : new MemoryStorage(data); @@ -70,20 +58,9 @@ export const createDatabaseRoutes = ({ } }); - router.route('/__db').get( - asyncHandler(async (request, response) => { - const data = await callResponseInterceptors({ - data: storage.read(), - request, - response, - interceptors: { - apiInterceptor: databaseConfig.interceptors?.response, - serverInterceptor: serverResponseInterceptor - } - }); - response.json(data); - }) - ); + router.route('/__db').get((_request, response) => { + response.json(storage.read()); + }); return router; }; diff --git a/src/core/database/createDatabaseRoutes/helpers/createNestedDatabaseRoutes/createNestedDatabaseRoutes.ts b/src/core/database/createDatabaseRoutes/helpers/createNestedDatabaseRoutes/createNestedDatabaseRoutes.ts index 015b6051..8a367808 100644 --- a/src/core/database/createDatabaseRoutes/helpers/createNestedDatabaseRoutes/createNestedDatabaseRoutes.ts +++ b/src/core/database/createDatabaseRoutes/helpers/createNestedDatabaseRoutes/createNestedDatabaseRoutes.ts @@ -1,7 +1,7 @@ import type { IRouter } from 'express'; import type { ParsedUrlQuery } from 'node:querystring'; -import { asyncHandler, callResponseInterceptors } from '@/utils/helpers'; +import { asyncHandler, callResponseInterceptors, isPlainObject } from '@/utils/helpers'; import type { Interceptors, NestedDatabase } from '@/utils/types'; import type { MemoryStorage } from '../../storages'; @@ -102,18 +102,34 @@ export const createNestedDatabaseRoutes = ({ response, interceptors: responseInterceptors }); + response.json(updatedData); }) ); - router.route(collectionPath).post((request, response) => { - const collection = storage.read(key); - const newResourceId = createNewId(collection); - const newResource = { ...request.body, id: newResourceId }; - storage.write([key, collection.length], newResource); - response.set('Location', `${request.url}/${newResourceId}`); - response.status(201).json(newResource); - }); + router.route(collectionPath).post( + asyncHandler(async (request, response) => { + const collection = storage.read(key); + const newResourceId = createNewId(collection); + const newResource = { ...request.body, id: newResourceId }; + + storage.write([key, collection.length], newResource); + + const updatedResource = await callResponseInterceptors({ + data: newResource, + request, + response, + interceptors: responseInterceptors + }); + + if (isPlainObject(updatedResource) && typeof updatedResource.id === 'number') { + response.set('Location', `${request.url}/${updatedResource.id}`); + response.status(201).json(updatedResource); + return; + } + response.status(400).end(); + }) + ); router.route(itemPath).get( asyncHandler(async (request, response) => { @@ -149,13 +165,16 @@ export const createNestedDatabaseRoutes = ({ const currentResource = storage.read([key, currentResourceIndex]); const updatedResource = { ...request.body, id: currentResource.id }; + storage.write([key, currentResourceIndex], updatedResource); + const updatedData = await callResponseInterceptors({ data: updatedResource, request, response, interceptors: responseInterceptors }); + response.json(updatedData); }) ); @@ -171,13 +190,16 @@ export const createNestedDatabaseRoutes = ({ const currentResource = storage.read([key, currentResourceIndex]); const updatedResource = { ...currentResource, ...request.body, id: currentResource.id }; + storage.write([key, currentResourceIndex], updatedResource); + const updatedData = await callResponseInterceptors({ data: updatedResource, request, response, interceptors: responseInterceptors }); + response.json(updatedData); }) ); @@ -190,13 +212,16 @@ export const createNestedDatabaseRoutes = ({ response.status(404).end(); return; } + storage.delete([key, currentResourceIndex]); + await callResponseInterceptors({ - data: {}, + data: null, request, response, interceptors: responseInterceptors }); + response.status(204).end(); }) ); diff --git a/src/core/database/createDatabaseRoutes/helpers/createShallowDatabaseRoutes/createShallowDatabaseRoutes.ts b/src/core/database/createDatabaseRoutes/helpers/createShallowDatabaseRoutes/createShallowDatabaseRoutes.ts index 4a7aa22f..22eeffda 100644 --- a/src/core/database/createDatabaseRoutes/helpers/createShallowDatabaseRoutes/createShallowDatabaseRoutes.ts +++ b/src/core/database/createDatabaseRoutes/helpers/createShallowDatabaseRoutes/createShallowDatabaseRoutes.ts @@ -117,14 +117,17 @@ export const createShallowDatabaseRoutes = ({ router.route(path).post( asyncHandler(async (request, response) => { storage.write(key, request.body); + response.set('Location', request.url); response.status(201); + const updatedData = await callResponseInterceptors({ data: request.body, request, response, interceptors: responseInterceptors }); + response.json(updatedData); }) ); @@ -132,12 +135,14 @@ export const createShallowDatabaseRoutes = ({ router.route(path).put( asyncHandler(async (request, response) => { storage.write(key, request.body); + const updatedData = await callResponseInterceptors({ data: request.body, request, response, interceptors: responseInterceptors }); + response.json(updatedData); }) ); @@ -146,13 +151,16 @@ export const createShallowDatabaseRoutes = ({ asyncHandler(async (request, response) => { const currentResource = storage.read(key); const updatedResource = { ...currentResource, ...request.body }; + storage.write(key, updatedResource); + const updatedData = await callResponseInterceptors({ data: updatedResource, request, response, interceptors: responseInterceptors }); + response.json(updatedData); }) ); diff --git a/src/server/createDatabaseMockServer/createDatabaseMockServer.ts b/src/server/createDatabaseMockServer/createDatabaseMockServer.ts index 311138ad..ae609ced 100644 --- a/src/server/createDatabaseMockServer/createDatabaseMockServer.ts +++ b/src/server/createDatabaseMockServer/createDatabaseMockServer.ts @@ -37,7 +37,7 @@ export const createDatabaseMockServer = ( cookieParseMiddleware(server); - const serverRequestInterceptor = databaseMockServerConfig.interceptors?.request; + const serverRequestInterceptor = interceptors?.request; if (serverRequestInterceptor) { requestInterceptorMiddleware({ server, interceptor: serverRequestInterceptor }); } diff --git a/src/server/createGraphQLMockServer/createGraphQLMockServer.ts b/src/server/createGraphQLMockServer/createGraphQLMockServer.ts index 9ff4e088..eb4e9914 100644 --- a/src/server/createGraphQLMockServer/createGraphQLMockServer.ts +++ b/src/server/createGraphQLMockServer/createGraphQLMockServer.ts @@ -38,7 +38,7 @@ export const createGraphQLMockServer = ( cookieParseMiddleware(server); - const serverRequestInterceptor = graphqlMockServerConfig.interceptors?.request; + const serverRequestInterceptor = interceptors?.request; if (serverRequestInterceptor) { requestInterceptorMiddleware({ server, interceptor: serverRequestInterceptor }); } @@ -69,6 +69,18 @@ export const createGraphQLMockServer = ( databaseConfig: database, serverResponseInterceptor: interceptors?.response }); + + const databaseBaseUrl = urlJoin(baseUrl, database.baseUrl ?? '/'); + + const apiRequestInterceptor = database.interceptors?.request; + if (apiRequestInterceptor) { + requestInterceptorMiddleware({ + server, + path: databaseBaseUrl, + interceptor: apiRequestInterceptor + }); + } + server.use(baseUrl, routerWithDatabaseRoutes); } diff --git a/src/server/createMockServer/createMockServer.ts b/src/server/createMockServer/createMockServer.ts index e7df6b77..ebaf1443 100644 --- a/src/server/createMockServer/createMockServer.ts +++ b/src/server/createMockServer/createMockServer.ts @@ -39,7 +39,7 @@ export const createMockServer = ( cookieParseMiddleware(server); - const serverRequestInterceptor = mockServerConfig.interceptors?.request; + const serverRequestInterceptor = interceptors?.request; if (serverRequestInterceptor) { requestInterceptorMiddleware({ server, interceptor: serverRequestInterceptor }); } diff --git a/src/server/createRestMockServer/createRestMockServer.ts b/src/server/createRestMockServer/createRestMockServer.ts index 8c94c7ae..9711c55a 100644 --- a/src/server/createRestMockServer/createRestMockServer.ts +++ b/src/server/createRestMockServer/createRestMockServer.ts @@ -38,7 +38,7 @@ export const createRestMockServer = ( cookieParseMiddleware(server); - const serverRequestInterceptor = restMockServerConfig.interceptors?.request; + const serverRequestInterceptor = interceptors?.request; if (serverRequestInterceptor) { requestInterceptorMiddleware({ server, interceptor: serverRequestInterceptor }); } @@ -69,6 +69,18 @@ export const createRestMockServer = ( databaseConfig: database, serverResponseInterceptor: interceptors?.response }); + + const databaseBaseUrl = urlJoin(baseUrl, database.baseUrl ?? '/'); + + const apiRequestInterceptor = database.interceptors?.request; + if (apiRequestInterceptor) { + requestInterceptorMiddleware({ + server, + path: databaseBaseUrl, + interceptor: apiRequestInterceptor + }); + } + server.use(baseUrl, routerWithDatabaseRoutes); } From cc535dbd1edc96d300a1d57e8199c27f4d658563 Mon Sep 17 00:00:00 2001 From: RiceWithMeat <47690223+RiceWithMeat@users.noreply.github.com> Date: Mon, 6 Jan 2025 19:42:39 +0700 Subject: [PATCH 3/3] =?UTF-8?q?#167=20=F0=9F=90=98=20tests=20for=20databes?= =?UTF-8?q?e=20interceptors?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../createDatabaseRoutes.test.ts | 30 ------------------- .../createNestedDatabaseRoutes.test.ts | 30 ++++++++++++++++--- .../createShallowDatabaseRoutes.test.ts | 28 +++++++++++++++-- 3 files changed, 51 insertions(+), 37 deletions(-) diff --git a/src/core/database/createDatabaseRoutes/createDatabaseRoutes.test.ts b/src/core/database/createDatabaseRoutes/createDatabaseRoutes.test.ts index 138a73a3..baf07319 100644 --- a/src/core/database/createDatabaseRoutes/createDatabaseRoutes.test.ts +++ b/src/core/database/createDatabaseRoutes/createDatabaseRoutes.test.ts @@ -114,33 +114,3 @@ describe('createDatabaseRoutes: routes /__routes and /__db', () => { expect(response.body).toStrictEqual(routes); }); }); - -describe('createDatabaseRoutes: interceptors', () => { - test('Should call response interceptors in order: api -> server', async () => { - const apiInterceptor = vi.fn(); - const serverInterceptor = vi.fn(); - - const data = { profile: { name: 'John' }, users: [{ id: 1 }, { id: 2 }] }; - const routes = { '/api/profile': '/profile' } as const; - const server = createServer({ - database: { - data, - routes, - interceptors: { - response: apiInterceptor - } - }, - interceptors: { - response: serverInterceptor - } - }); - - await request(server).get('/profile'); - - expect(apiInterceptor.mock.calls.length).toBe(1); - expect(serverInterceptor.mock.calls.length).toBe(1); - expect(apiInterceptor.mock.invocationCallOrder[0]).toBeLessThan( - serverInterceptor.mock.invocationCallOrder[0] - ); - }); -}); diff --git a/src/core/database/createDatabaseRoutes/helpers/createNestedDatabaseRoutes/createNestedDatabaseRoutes.test.ts b/src/core/database/createDatabaseRoutes/helpers/createNestedDatabaseRoutes/createNestedDatabaseRoutes.test.ts index e44c41e2..557f517f 100644 --- a/src/core/database/createDatabaseRoutes/helpers/createNestedDatabaseRoutes/createNestedDatabaseRoutes.test.ts +++ b/src/core/database/createDatabaseRoutes/helpers/createNestedDatabaseRoutes/createNestedDatabaseRoutes.test.ts @@ -2,13 +2,13 @@ import type { Express } from 'express'; import express from 'express'; import request from 'supertest'; -import type { NestedDatabase } from '@/utils/types'; +import type { Interceptors, NestedDatabase } from '@/utils/types'; import { MemoryStorage } from '../../storages'; import { createNestedDatabaseRoutes } from './createNestedDatabaseRoutes'; -describe('CreateNestedDatabaseRoutes', () => { +describe('createNestedDatabaseRoutes', () => { const createNestedDatabase = () => ({ users: [ { @@ -28,7 +28,13 @@ describe('CreateNestedDatabaseRoutes', () => { ] }); - const createServer = (nestedDatabase: NestedDatabase) => { + const createServer = ( + nestedDatabase: NestedDatabase, + responseInterceptors?: { + apiInterceptor?: Interceptors['response']; + serverInterceptor?: Interceptors['response']; + } + ) => { const server = express(); const routerBase = express.Router(); const storage = new MemoryStorage(nestedDatabase); @@ -36,7 +42,8 @@ describe('CreateNestedDatabaseRoutes', () => { const routerWithNestedDatabaseRoutes = createNestedDatabaseRoutes({ router: routerBase, database: nestedDatabase, - storage + storage, + responseInterceptors }); server.use(express.json()); @@ -675,4 +682,19 @@ describe('CreateNestedDatabaseRoutes', () => { ]); }); }); + + describe('createNestedDatabaseRoutes: interceptors', () => { + test('Should call response interceptors', async () => { + const apiInterceptor = vi.fn(); + const serverInterceptor = vi.fn(); + + const nestedDatabase = createNestedDatabase(); + const server = createServer(nestedDatabase, { apiInterceptor, serverInterceptor }); + + await request(server).get('/users'); + + expect(apiInterceptor.mock.calls.length).toBe(1); + expect(serverInterceptor.mock.calls.length).toBe(1); + }); + }); }); diff --git a/src/core/database/createDatabaseRoutes/helpers/createShallowDatabaseRoutes/createShallowDatabaseRoutes.test.ts b/src/core/database/createDatabaseRoutes/helpers/createShallowDatabaseRoutes/createShallowDatabaseRoutes.test.ts index c37f39b8..55468a3f 100644 --- a/src/core/database/createDatabaseRoutes/helpers/createShallowDatabaseRoutes/createShallowDatabaseRoutes.test.ts +++ b/src/core/database/createDatabaseRoutes/helpers/createShallowDatabaseRoutes/createShallowDatabaseRoutes.test.ts @@ -2,7 +2,7 @@ import type { Express } from 'express'; import express from 'express'; import request from 'supertest'; -import type { ShallowDatabase } from '@/utils/types'; +import type { Interceptors, ShallowDatabase } from '@/utils/types'; import { MemoryStorage } from '../../storages'; @@ -18,7 +18,13 @@ describe('createShallowDatabaseRoutes', () => { jane: { name: 'Jane Smith', age: 30 } }); - const createServer = (shallowDatabase: ShallowDatabase) => { + const createServer = ( + shallowDatabase: ShallowDatabase, + responseInterceptors?: { + apiInterceptor?: Interceptors['response']; + serverInterceptor?: Interceptors['response']; + } + ) => { const server = express(); const routerBase = express.Router(); const storage = new MemoryStorage(shallowDatabase); @@ -26,7 +32,8 @@ describe('createShallowDatabaseRoutes', () => { const routerWithRoutesForShallowDatabase = createShallowDatabaseRoutes({ router: routerBase, database: shallowDatabase, - storage + storage, + responseInterceptors }); server.use(express.json()); @@ -521,4 +528,19 @@ describe('createShallowDatabaseRoutes', () => { ]); }); }); + + describe('createShallowDatabaseRoutes: interceptors', () => { + test('Should call response interceptors', async () => { + const apiInterceptor = vi.fn(); + const serverInterceptor = vi.fn(); + + const shallowDatabase = createShallowDatabase(); + const server = createServer(shallowDatabase, { apiInterceptor, serverInterceptor }); + + await request(server).get('/users'); + + expect(apiInterceptor.mock.calls.length).toBe(1); + expect(serverInterceptor.mock.calls.length).toBe(1); + }); + }); });