mock-config-server 3.2.0 → 3.3.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +153 -3
- package/dist/bin/validateMockServerConfig/helpers/isDescriptorValueValid/isDescriptorValueValid.d.ts +2 -1
- package/dist/bin/validateMockServerConfig/helpers/isDescriptorValueValid/isDescriptorValueValid.js +30 -7
- package/dist/bin/validateMockServerConfig/validateGraphqlConfig/validateGraphqlConfig.js +9 -3
- package/dist/bin/validateMockServerConfig/validateGraphqlConfig/validateRoutes/validateRoutes.js +67 -18
- package/dist/bin/validateMockServerConfig/validateQueue/validateQueue.d.ts +1 -0
- package/dist/bin/validateMockServerConfig/validateQueue/validateQueue.js +29 -0
- package/dist/bin/validateMockServerConfig/validateRestConfig/validateRoutes/validateRoutes.js +67 -18
- package/dist/bin/validateMockServerConfig/validateSettings/validateSettings.d.ts +1 -0
- package/dist/bin/validateMockServerConfig/validateSettings/validateSettings.js +34 -0
- package/dist/src/core/database/createDatabaseRoutes/createDatabaseRoutes.test.ts +112 -0
- package/dist/src/core/database/createDatabaseRoutes/helpers/array/createNewId/createNewId.test.ts +13 -0
- package/dist/src/core/database/createDatabaseRoutes/helpers/array/findIndexById/findIndexById.test.ts +17 -0
- package/dist/src/core/database/createDatabaseRoutes/helpers/array/isIndex/isIndex.test.ts +30 -0
- package/dist/src/core/database/createDatabaseRoutes/helpers/createNestedDatabaseRoutes/createNestedDatabaseRoutes.js +45 -2
- package/dist/src/core/database/createDatabaseRoutes/helpers/createNestedDatabaseRoutes/createNestedDatabaseRoutes.test.ts +399 -0
- package/dist/src/core/database/createDatabaseRoutes/helpers/createShallowDatabaseRoutes/createShallowDatabaseRoutes.test.ts +118 -0
- package/dist/src/core/database/createDatabaseRoutes/helpers/filter/filter.d.ts +2 -1
- package/dist/src/core/database/createDatabaseRoutes/helpers/operators/operators.d.ts +3 -0
- package/dist/src/core/database/createDatabaseRoutes/helpers/operators/operators.js +30 -0
- package/dist/src/core/database/createDatabaseRoutes/helpers/pagination/pagination.d.ts +13 -0
- package/dist/src/core/database/createDatabaseRoutes/helpers/pagination/pagination.js +36 -0
- package/dist/src/core/database/createDatabaseRoutes/helpers/search/search.d.ts +3 -0
- package/dist/src/core/database/createDatabaseRoutes/helpers/search/search.js +31 -0
- package/dist/src/core/database/createDatabaseRoutes/helpers/sort/sort.d.ts +2 -0
- package/dist/src/core/database/createDatabaseRoutes/helpers/sort/sort.js +42 -0
- package/dist/src/core/database/createDatabaseRoutes/helpers/splitDatabaseByNesting/splitDatabaseByNesting.test.ts +25 -0
- package/dist/src/core/database/createDatabaseRoutes/storages/File/FileStorage.test.ts +156 -0
- package/dist/src/core/database/createDatabaseRoutes/storages/File/FileWriter.test.ts +48 -0
- package/dist/src/core/database/createDatabaseRoutes/storages/Memory/MemoryStorage.test.ts +96 -0
- package/dist/src/core/graphql/createGraphQLRoutes/createGraphQLRoutes.d.ts +7 -1
- package/dist/src/core/graphql/createGraphQLRoutes/createGraphQLRoutes.js +65 -24
- package/dist/src/core/graphql/createGraphQLRoutes/createGraphQLRoutes.test.ts +851 -0
- package/dist/src/core/graphql/createGraphQLRoutes/helpers/prepareGraphQLRequestConfigs/prepareGraphQLRequestConfigs.test.ts +116 -0
- package/dist/src/core/middlewares/cookieParseMiddleware/cookieParseMiddleware.test.ts +22 -0
- package/dist/src/core/middlewares/cookieParseMiddleware/helpers/parseCookie/parseCookie.test.ts +45 -0
- package/dist/src/core/middlewares/corsMiddleware/corsMiddleware.test.ts +152 -0
- package/dist/src/core/middlewares/corsMiddleware/helpers/getAllowedOrigins/getAllowedOrigins.test.ts +15 -0
- package/dist/src/core/middlewares/errorMiddleware/errorMiddleware.test.ts +29 -0
- package/dist/src/core/middlewares/noCorsMiddleware/noCorsMiddleware.test.ts +49 -0
- package/dist/src/core/middlewares/notFoundMiddleware/helpers/getGraphqlUrlSuggestions/getGraphqlUrlSuggestions.test.ts +27 -0
- package/dist/src/core/middlewares/notFoundMiddleware/helpers/getLevenshteinDistance/getLevenshteinDistance.test.ts +12 -0
- package/dist/src/core/middlewares/notFoundMiddleware/helpers/getRestUrlSuggestions/getRestUrlSuggestions.test.ts +54 -0
- package/dist/src/core/middlewares/notFoundMiddleware/helpers/getRestUrlSuggestions/helpers/getActualRestUrlMeaningfulString/getActualRestUrlMeaningfulString.test.ts +12 -0
- package/dist/src/core/middlewares/notFoundMiddleware/helpers/getRestUrlSuggestions/helpers/getPatternRestUrlMeaningfulString/getPatternRestUrlMeaningfulString.test.ts +10 -0
- package/dist/src/core/middlewares/notFoundMiddleware/notFoundMiddleware.js +1 -3
- package/dist/src/core/middlewares/notFoundMiddleware/notFoundMiddleware.test.ts +285 -0
- package/dist/src/core/middlewares/requestInterceptorMiddleware/requestInterceptorMiddleware.d.ts +7 -1
- package/dist/src/core/middlewares/requestInterceptorMiddleware/requestInterceptorMiddleware.js +6 -2
- package/dist/src/core/rest/createRestRoutes/createRestRoutes.d.ts +7 -1
- package/dist/src/core/rest/createRestRoutes/createRestRoutes.js +55 -12
- package/dist/src/core/rest/createRestRoutes/createRestRoutes.test.ts +648 -0
- package/dist/src/core/rest/createRestRoutes/helpers/prepareRestRequestConfigs/prepareRestRequestConfigs.test.ts +154 -0
- package/dist/src/server/createDatabaseMockServer/createDatabaseMockServer.js +4 -1
- package/dist/src/server/createGraphQLMockServer/createGraphQLMockServer.js +11 -4
- package/dist/src/server/createMockServer/createMockServer.js +28 -9
- package/dist/src/server/createRestMockServer/createRestMockServer.js +11 -4
- package/dist/src/server/index.d.ts +3 -3
- package/dist/src/server/index.js +23 -23
- package/dist/src/static/views/features/scheme/index.js +31 -31
- package/dist/src/utils/helpers/config/resolveEntityValues/resolveEntityValues.test.ts +1452 -0
- package/dist/src/utils/helpers/entities/convertToEntityDescriptor/convertToEntityDescriptor.test.ts +27 -0
- package/dist/src/utils/helpers/entities/isEntityDescriptor/isEntityDescriptor.d.ts +2 -1
- package/dist/src/utils/helpers/entities/isEntityDescriptor/isEntityDescriptor.test.ts +15 -0
- package/dist/src/utils/helpers/graphql/getGraphQLInput/getGraphQLInput.d.ts +7 -2
- package/dist/src/utils/helpers/graphql/getGraphQLInput/getGraphQLInput.js +6 -4
- package/dist/src/utils/helpers/graphql/getGraphQLInput/getGraphQLInput.test.ts +140 -0
- package/dist/src/utils/helpers/graphql/parseQuery/parseQuery.d.ts +1 -1
- package/dist/src/utils/helpers/graphql/parseQuery/parseQuery.js +1 -1
- package/dist/src/utils/helpers/graphql/parseQuery/parseQuery.test.ts +32 -0
- package/dist/src/utils/helpers/interceptors/callRequestInterceptor/callRequestInterceptors.test.ts +53 -0
- package/dist/src/utils/helpers/interceptors/callResponseInterceptors/callResponseInterceptors.js +19 -10
- package/dist/src/utils/helpers/interceptors/callResponseInterceptors/callResponseInterceptors.test.ts +262 -0
- package/dist/src/utils/helpers/isPlainObject/isPlainObject.test.ts +20 -0
- package/dist/src/utils/helpers/isPrimitive/isPrimitive.test.ts +26 -0
- package/dist/src/utils/helpers/isRegExp/isRegExp.test.ts +20 -0
- package/dist/src/utils/helpers/url/convertWin32PathToUnix/convertWin32PathToUnix.test.ts +21 -0
- package/dist/src/utils/helpers/url/getUrlParts/getUrlParts.test.ts +8 -0
- package/dist/src/utils/helpers/url/removeLeadingAndTrailingSlashes/removeLeadingAndTrailingSlashes.test.ts +10 -0
- package/dist/src/utils/helpers/url/urlJoin/urlJoin.test.ts +9 -0
- package/dist/src/utils/types/graphql.d.ts +60 -51
- package/dist/src/utils/types/index.d.ts +1 -0
- package/dist/src/utils/types/index.js +11 -0
- package/dist/src/utils/types/interceptors.d.ts +1 -1
- package/dist/src/utils/types/rest.d.ts +47 -40
- package/dist/src/utils/types/utils.d.ts +8 -0
- package/dist/src/utils/types/utils.js +1 -0
- package/dist/src/utils/types/values.d.ts +0 -1
- package/package.json +2 -3
|
@@ -0,0 +1,112 @@
|
|
|
1
|
+
import type { Express } from 'express';
|
|
2
|
+
import express from 'express';
|
|
3
|
+
import fs from 'fs';
|
|
4
|
+
import os from 'os';
|
|
5
|
+
import path from 'path';
|
|
6
|
+
import request from 'supertest';
|
|
7
|
+
|
|
8
|
+
import { createDatabaseRoutes } from '@/core/database';
|
|
9
|
+
import type { DatabaseConfig, MockServerConfig } from '@/utils/types';
|
|
10
|
+
|
|
11
|
+
import { findIndexById } from './helpers';
|
|
12
|
+
|
|
13
|
+
describe('createDatabaseRoutes', () => {
|
|
14
|
+
const createServer = (
|
|
15
|
+
mockServerConfig: Pick<MockServerConfig, 'baseUrl'> & { database: DatabaseConfig }
|
|
16
|
+
) => {
|
|
17
|
+
const server = express();
|
|
18
|
+
const routerBase = express.Router();
|
|
19
|
+
const routesWithDatabaseRoutes = createDatabaseRoutes(routerBase, mockServerConfig.database);
|
|
20
|
+
|
|
21
|
+
server.use(mockServerConfig.baseUrl ?? '/', routesWithDatabaseRoutes);
|
|
22
|
+
return server;
|
|
23
|
+
};
|
|
24
|
+
|
|
25
|
+
describe('createDatabaseRoutes: routes and data successfully works when passing them by object', () => {
|
|
26
|
+
const data = { profile: { name: 'John' }, users: [{ id: 1 }, { id: 2 }] };
|
|
27
|
+
const routes = { '/api/profile': '/profile' } as const;
|
|
28
|
+
const server = createServer({ database: { data, routes } });
|
|
29
|
+
|
|
30
|
+
test('Should overwrite routes according to routes object (but default url should work too)', async () => {
|
|
31
|
+
const overwrittenUrlResponse = await request(server).get('/api/profile');
|
|
32
|
+
expect(overwrittenUrlResponse.body).toStrictEqual(data.profile);
|
|
33
|
+
|
|
34
|
+
const defaultUrlResponse = await request(server).get('/profile');
|
|
35
|
+
expect(defaultUrlResponse.body).toStrictEqual(data.profile);
|
|
36
|
+
});
|
|
37
|
+
|
|
38
|
+
test('Should successfully handle requests to shallow and nested database parts', async () => {
|
|
39
|
+
const shallowDatabaseResponse = await request(server).get('/profile');
|
|
40
|
+
expect(shallowDatabaseResponse.body).toStrictEqual(data.profile);
|
|
41
|
+
|
|
42
|
+
const nestedDatabaseCollectionResponse = await request(server).get('/users');
|
|
43
|
+
expect(nestedDatabaseCollectionResponse.body).toStrictEqual(data.users);
|
|
44
|
+
|
|
45
|
+
const nestedDatabaseItemResponse = await request(server).get('/users/1');
|
|
46
|
+
expect(nestedDatabaseItemResponse.body).toStrictEqual(
|
|
47
|
+
data.users[findIndexById(data.users, 1)]
|
|
48
|
+
);
|
|
49
|
+
});
|
|
50
|
+
});
|
|
51
|
+
|
|
52
|
+
describe('createDatabaseRoutes: routes and data successfully works when passing them by file', () => {
|
|
53
|
+
const data = { profile: { name: 'John' }, users: [{ id: 1 }, { id: 2 }] };
|
|
54
|
+
const routes = { '/api/profile': '/profile' } as const;
|
|
55
|
+
|
|
56
|
+
let tmpDirPath: string;
|
|
57
|
+
let server: Express;
|
|
58
|
+
|
|
59
|
+
beforeAll(() => {
|
|
60
|
+
tmpDirPath = fs.mkdtempSync(os.tmpdir());
|
|
61
|
+
|
|
62
|
+
const pathToData = path.join(tmpDirPath, './data.json') as `${string}.json`;
|
|
63
|
+
fs.writeFileSync(pathToData, JSON.stringify(data));
|
|
64
|
+
|
|
65
|
+
const pathToRoutes = path.join(tmpDirPath, './routes.json') as `${string}.json`;
|
|
66
|
+
fs.writeFileSync(pathToRoutes, JSON.stringify(routes));
|
|
67
|
+
|
|
68
|
+
server = createServer({ database: { data: pathToData, routes: pathToRoutes } });
|
|
69
|
+
});
|
|
70
|
+
|
|
71
|
+
afterAll(() => {
|
|
72
|
+
fs.rmSync(tmpDirPath, { recursive: true, force: true });
|
|
73
|
+
});
|
|
74
|
+
|
|
75
|
+
test('Should overwrite routes according to routes object (but default url should work too)', async () => {
|
|
76
|
+
const overwrittenUrlResponse = await request(server).get('/api/profile');
|
|
77
|
+
expect(overwrittenUrlResponse.body).toStrictEqual(data.profile);
|
|
78
|
+
|
|
79
|
+
const defaultUrlResponse = await request(server).get('/profile');
|
|
80
|
+
expect(defaultUrlResponse.body).toStrictEqual(data.profile);
|
|
81
|
+
});
|
|
82
|
+
|
|
83
|
+
test('Should successfully handle requests to shallow and nested database parts', async () => {
|
|
84
|
+
const shallowDatabaseResponse = await request(server).get('/profile');
|
|
85
|
+
expect(shallowDatabaseResponse.body).toStrictEqual(data.profile);
|
|
86
|
+
|
|
87
|
+
const nestedDatabaseCollectionResponse = await request(server).get('/users');
|
|
88
|
+
expect(nestedDatabaseCollectionResponse.body).toStrictEqual(data.users);
|
|
89
|
+
|
|
90
|
+
const nestedDatabaseItemResponse = await request(server).get('/users/1');
|
|
91
|
+
expect(nestedDatabaseItemResponse.body).toStrictEqual(
|
|
92
|
+
data.users[findIndexById(data.users, 1)]
|
|
93
|
+
);
|
|
94
|
+
});
|
|
95
|
+
});
|
|
96
|
+
|
|
97
|
+
describe('createDatabaseRoutes: routes /__routes and /__db', () => {
|
|
98
|
+
const data = { profile: { name: 'John' }, users: [{ id: 1 }, { id: 2 }] };
|
|
99
|
+
const routes = { '/api/profile': '/profile' } as const;
|
|
100
|
+
const server = createServer({ database: { data, routes } });
|
|
101
|
+
|
|
102
|
+
test('Should create /__db route that return data from databaseConfig', async () => {
|
|
103
|
+
const response = await request(server).get('/__db');
|
|
104
|
+
expect(response.body).toStrictEqual(data);
|
|
105
|
+
});
|
|
106
|
+
|
|
107
|
+
test('Should create /__routes route that return routes from databaseConfig', async () => {
|
|
108
|
+
const response = await request(server).get('/__routes');
|
|
109
|
+
expect(response.body).toStrictEqual(routes);
|
|
110
|
+
});
|
|
111
|
+
});
|
|
112
|
+
});
|
package/dist/src/core/database/createDatabaseRoutes/helpers/array/createNewId/createNewId.test.ts
ADDED
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import { createNewId } from './createNewId';
|
|
2
|
+
|
|
3
|
+
describe('createNewId', () => {
|
|
4
|
+
test('Should increment max numeric id in array on 1', () => {
|
|
5
|
+
const array = [{ id: 1 }, { id: 'string' }, { id: 1000 }];
|
|
6
|
+
expect(createNewId(array)).toBe(1001);
|
|
7
|
+
});
|
|
8
|
+
|
|
9
|
+
test('Should return 0 if no numeric ids in array', () => {
|
|
10
|
+
const array = [{ id: 'string' }];
|
|
11
|
+
expect(createNewId(array)).toBe(0);
|
|
12
|
+
});
|
|
13
|
+
});
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import { findIndexById } from './findIndexById';
|
|
2
|
+
|
|
3
|
+
describe('findIndexById', () => {
|
|
4
|
+
const array = [{ id: 1 }, { id: 2 }];
|
|
5
|
+
|
|
6
|
+
test('Should return index of item by id', () => {
|
|
7
|
+
expect(findIndexById(array, 2)).toBe(1);
|
|
8
|
+
});
|
|
9
|
+
|
|
10
|
+
test('Should return -1 if item with id does not exists', () => {
|
|
11
|
+
expect(findIndexById(array, 3)).toBe(-1);
|
|
12
|
+
});
|
|
13
|
+
|
|
14
|
+
test('Should compare ids independently of type: string or number', () => {
|
|
15
|
+
expect(findIndexById(array, '2')).toBe(1);
|
|
16
|
+
});
|
|
17
|
+
});
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
import { isIndex } from './isIndex';
|
|
2
|
+
|
|
3
|
+
describe('isIndex', () => {
|
|
4
|
+
test('Should return true for positive integer value', () => {
|
|
5
|
+
expect(isIndex(5)).toBe(true);
|
|
6
|
+
});
|
|
7
|
+
|
|
8
|
+
test('Should return true for 0', () => {
|
|
9
|
+
expect(isIndex(0)).toBe(true);
|
|
10
|
+
});
|
|
11
|
+
|
|
12
|
+
test('Should return false for negative integer value', () => {
|
|
13
|
+
expect(isIndex(-5)).toBe(false);
|
|
14
|
+
});
|
|
15
|
+
|
|
16
|
+
test('Should return false for floating point value', () => {
|
|
17
|
+
expect(isIndex(1.5)).toBe(false);
|
|
18
|
+
});
|
|
19
|
+
|
|
20
|
+
test('Should return false for non-numeric value', () => {
|
|
21
|
+
expect(isIndex(true)).toBe(false);
|
|
22
|
+
expect(isIndex('hello')).toBe(false);
|
|
23
|
+
expect(isIndex(null)).toBe(false);
|
|
24
|
+
expect(isIndex(undefined)).toBe(false);
|
|
25
|
+
expect(isIndex({})).toBe(false);
|
|
26
|
+
expect(isIndex([])).toBe(false);
|
|
27
|
+
expect(isIndex(() => {})).toBe(false);
|
|
28
|
+
expect(isIndex(Symbol('symbol'))).toBe(false);
|
|
29
|
+
});
|
|
30
|
+
});
|
|
@@ -6,16 +6,59 @@ Object.defineProperty(exports, "__esModule", {
|
|
|
6
6
|
exports.createNestedDatabaseRoutes = void 0;
|
|
7
7
|
var _array = require("../array");
|
|
8
8
|
var _filter = require("../filter/filter");
|
|
9
|
+
var _pagination = require("../pagination/pagination");
|
|
10
|
+
var _sort2 = require("../sort/sort");
|
|
9
11
|
const createNestedDatabaseRoutes = (router, database, storage) => {
|
|
10
12
|
Object.keys(database).forEach(key => {
|
|
11
13
|
const collectionPath = `/${key}`;
|
|
12
14
|
const itemPath = `/${key}/:id`;
|
|
13
15
|
router.route(collectionPath).get((request, response) => {
|
|
16
|
+
var _request$query;
|
|
14
17
|
let data = storage.read(key);
|
|
15
18
|
if (request.query && Object.keys(request.query).length) {
|
|
16
|
-
|
|
19
|
+
const {
|
|
20
|
+
_page,
|
|
21
|
+
_limit,
|
|
22
|
+
_begin,
|
|
23
|
+
_end,
|
|
24
|
+
_sort,
|
|
25
|
+
_order,
|
|
26
|
+
...filters
|
|
27
|
+
} = request.query;
|
|
28
|
+
data = (0, _filter.filter)(data, filters);
|
|
29
|
+
}
|
|
30
|
+
if ((_request$query = request.query) !== null && _request$query !== void 0 && _request$query._page) {
|
|
31
|
+
data = (0, _pagination.pagination)(data, request.query);
|
|
32
|
+
if (data._link) {
|
|
33
|
+
const links = {};
|
|
34
|
+
const fullUrl = `${request.protocol}://${request.get('host')}${request.originalUrl}`;
|
|
35
|
+
if (data._link.first) {
|
|
36
|
+
links.first = fullUrl.replace(`page=${data._link.current}`, `page=${data._link.first}`);
|
|
37
|
+
}
|
|
38
|
+
if (data._link.prev) {
|
|
39
|
+
links.prev = fullUrl.replace(`page=${data._link.current}`, `page=${data._link.prev}`);
|
|
40
|
+
}
|
|
41
|
+
if (data._link.next) {
|
|
42
|
+
links.next = fullUrl.replace(`page=${data._link.current}`, `page=${data._link.next}`);
|
|
43
|
+
}
|
|
44
|
+
if (data._link.last) {
|
|
45
|
+
links.last = fullUrl.replace(`page=${data._link.current}`, `page=${data._link.last}`);
|
|
46
|
+
}
|
|
47
|
+
data._link = {
|
|
48
|
+
...data._link,
|
|
49
|
+
...links
|
|
50
|
+
};
|
|
51
|
+
response.links(links);
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
if (request.query && request.query._sort) {
|
|
55
|
+
data = (0, _sort2.sort)(data, request.query);
|
|
56
|
+
}
|
|
57
|
+
if (request.query._begin || request.query._end) {
|
|
58
|
+
var _request$query$_begin;
|
|
59
|
+
data = data.slice((_request$query$_begin = request.query._begin) !== null && _request$query$_begin !== void 0 ? _request$query$_begin : 0, request.query._end);
|
|
60
|
+
response.set('X-Total-Count', data.length);
|
|
17
61
|
}
|
|
18
|
-
|
|
19
62
|
// ✅ important:
|
|
20
63
|
// set 'Cache-Control' header for explicit browsers response revalidate
|
|
21
64
|
// https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Cache-Control
|
|
@@ -0,0 +1,399 @@
|
|
|
1
|
+
import type { Express } from 'express';
|
|
2
|
+
import express from 'express';
|
|
3
|
+
import request from 'supertest';
|
|
4
|
+
|
|
5
|
+
import type { NestedDatabase } from '@/utils/types';
|
|
6
|
+
|
|
7
|
+
import { MemoryStorage } from '../../storages';
|
|
8
|
+
|
|
9
|
+
import { createNestedDatabaseRoutes } from './createNestedDatabaseRoutes';
|
|
10
|
+
|
|
11
|
+
describe('CreateNestedDatabaseRoutes', () => {
|
|
12
|
+
const createNestedDatabase = () => ({
|
|
13
|
+
users: [
|
|
14
|
+
{ id: 1, name: 'John Doe', age: 25, address: { city: 'Novosibirsk' } },
|
|
15
|
+
{ id: 2, name: 'Jane Smith', age: 30, address: { city: 'Tomsk' } }
|
|
16
|
+
]
|
|
17
|
+
});
|
|
18
|
+
|
|
19
|
+
const createServer = (nestedDatabase: NestedDatabase) => {
|
|
20
|
+
const server = express();
|
|
21
|
+
const routerBase = express.Router();
|
|
22
|
+
const storage = new MemoryStorage(nestedDatabase);
|
|
23
|
+
|
|
24
|
+
const routerWithNestedDatabaseRoutes = createNestedDatabaseRoutes(
|
|
25
|
+
routerBase,
|
|
26
|
+
nestedDatabase,
|
|
27
|
+
storage
|
|
28
|
+
);
|
|
29
|
+
|
|
30
|
+
server.use(express.json());
|
|
31
|
+
server.use(express.text());
|
|
32
|
+
server.use('/', routerWithNestedDatabaseRoutes);
|
|
33
|
+
|
|
34
|
+
return server;
|
|
35
|
+
};
|
|
36
|
+
|
|
37
|
+
describe('createNestedDatabaseRoutes: GET method for collection', () => {
|
|
38
|
+
const nestedDatabase = createNestedDatabase();
|
|
39
|
+
const server = createServer(nestedDatabase);
|
|
40
|
+
|
|
41
|
+
test('Should return correct data for valid key', async () => {
|
|
42
|
+
const response = await request(server).get('/users');
|
|
43
|
+
|
|
44
|
+
expect(response.body).toStrictEqual(nestedDatabase.users);
|
|
45
|
+
});
|
|
46
|
+
|
|
47
|
+
test('Should return correct Cache-Control header for valid key', async () => {
|
|
48
|
+
const response = await request(server).get('/users');
|
|
49
|
+
|
|
50
|
+
expect(response.headers['cache-control']).toBe('max-age=0, must-revalidate');
|
|
51
|
+
});
|
|
52
|
+
});
|
|
53
|
+
|
|
54
|
+
describe('createNestedDatabaseRoutes: POST method for collection', () => {
|
|
55
|
+
let nestedDatabase: ReturnType<typeof createNestedDatabase>;
|
|
56
|
+
let server: Express;
|
|
57
|
+
beforeEach(() => {
|
|
58
|
+
nestedDatabase = createNestedDatabase();
|
|
59
|
+
server = createServer(nestedDatabase);
|
|
60
|
+
});
|
|
61
|
+
|
|
62
|
+
test('Should return correct data (ignored id) for valid key and successfully update database', async () => {
|
|
63
|
+
const jim = { id: 4, name: 'jim', age: 35 };
|
|
64
|
+
const postResponse = await request(server).post('/users').send(jim);
|
|
65
|
+
|
|
66
|
+
expect(postResponse.statusCode).toBe(201);
|
|
67
|
+
expect(postResponse.body).toStrictEqual({ ...jim, id: 3 });
|
|
68
|
+
|
|
69
|
+
const getResponse = await request(server).get('/users');
|
|
70
|
+
expect(getResponse.body).toContainEqual({ ...jim, id: 3 });
|
|
71
|
+
});
|
|
72
|
+
|
|
73
|
+
test('Should return correct Location header for valid key', async () => {
|
|
74
|
+
const jim = { id: 4, name: 'jim', age: 35 };
|
|
75
|
+
const postResponse = await request(server).post('/users').send(jim);
|
|
76
|
+
|
|
77
|
+
expect(postResponse.headers.location).toBe('/users/3');
|
|
78
|
+
});
|
|
79
|
+
});
|
|
80
|
+
|
|
81
|
+
describe('createNestedDatabaseRoutes: GET method for item', () => {
|
|
82
|
+
const nestedDatabase = createNestedDatabase();
|
|
83
|
+
const server = createServer(nestedDatabase);
|
|
84
|
+
|
|
85
|
+
test('Should return correct data for valid key and id', async () => {
|
|
86
|
+
const response = await request(server).get('/users/1');
|
|
87
|
+
|
|
88
|
+
expect(response.body).toStrictEqual(nestedDatabase.users.find((item) => item.id === 1));
|
|
89
|
+
});
|
|
90
|
+
|
|
91
|
+
test('Should correct Cache-Control header for valid key and id', async () => {
|
|
92
|
+
const response = await request(server).get('/users/1');
|
|
93
|
+
|
|
94
|
+
expect(response.headers['cache-control']).toBe('max-age=0, must-revalidate');
|
|
95
|
+
});
|
|
96
|
+
|
|
97
|
+
test('Should return 404 for non-existent id', async () => {
|
|
98
|
+
const response = await request(server).get('/users/3');
|
|
99
|
+
|
|
100
|
+
expect(response.status).toBe(404);
|
|
101
|
+
});
|
|
102
|
+
});
|
|
103
|
+
|
|
104
|
+
describe('createNestedDatabaseRoutes: PUT method for item', () => {
|
|
105
|
+
let nestedDatabase: ReturnType<typeof createNestedDatabase>;
|
|
106
|
+
let server: Express;
|
|
107
|
+
beforeEach(() => {
|
|
108
|
+
nestedDatabase = createNestedDatabase();
|
|
109
|
+
server = createServer(nestedDatabase);
|
|
110
|
+
});
|
|
111
|
+
|
|
112
|
+
test('Should correctly replace resource (ignoring id) for valid key and id', async () => {
|
|
113
|
+
const response = await request(server).put('/users/1').send({ id: 3, name: 'John Smith' });
|
|
114
|
+
|
|
115
|
+
expect(response.body).toStrictEqual({ id: 1, name: 'John Smith' });
|
|
116
|
+
});
|
|
117
|
+
|
|
118
|
+
test('Should return 404 for non-existent id', async () => {
|
|
119
|
+
const response = await request(server).put('/users/3');
|
|
120
|
+
|
|
121
|
+
expect(response.status).toBe(404);
|
|
122
|
+
});
|
|
123
|
+
});
|
|
124
|
+
|
|
125
|
+
describe('createNestedDatabaseRoutes: PATCH method for item', () => {
|
|
126
|
+
let nestedDatabase: ReturnType<typeof createNestedDatabase>;
|
|
127
|
+
let server: Express;
|
|
128
|
+
beforeEach(() => {
|
|
129
|
+
nestedDatabase = createNestedDatabase();
|
|
130
|
+
server = createServer(nestedDatabase);
|
|
131
|
+
});
|
|
132
|
+
|
|
133
|
+
test('Should correctly update resource (ignoring id) for valid key and id', async () => {
|
|
134
|
+
const response = await request(server).patch('/users/1').send({ id: 3, name: 'John Smith' });
|
|
135
|
+
|
|
136
|
+
expect(response.body).toStrictEqual({
|
|
137
|
+
id: 1,
|
|
138
|
+
name: 'John Smith',
|
|
139
|
+
age: 25,
|
|
140
|
+
address: { city: 'Novosibirsk' }
|
|
141
|
+
});
|
|
142
|
+
});
|
|
143
|
+
|
|
144
|
+
test('Should return 404 for non-existent id', async () => {
|
|
145
|
+
const response = await request(server).patch('/users/3');
|
|
146
|
+
|
|
147
|
+
expect(response.status).toBe(404);
|
|
148
|
+
});
|
|
149
|
+
});
|
|
150
|
+
|
|
151
|
+
describe('createNestedDatabase: DELETE method for item', () => {
|
|
152
|
+
let nestedDatabase: ReturnType<typeof createNestedDatabase>;
|
|
153
|
+
let server: Express;
|
|
154
|
+
beforeEach(() => {
|
|
155
|
+
nestedDatabase = createNestedDatabase();
|
|
156
|
+
server = createServer(nestedDatabase);
|
|
157
|
+
});
|
|
158
|
+
|
|
159
|
+
test('Should correctly delete item from collection for valid key and id', async () => {
|
|
160
|
+
const deleteResponse = await request(server).delete('/users/1');
|
|
161
|
+
expect(deleteResponse.status).toBe(204);
|
|
162
|
+
|
|
163
|
+
const getResponse = await request(server).get('/users/1');
|
|
164
|
+
expect(getResponse.status).toBe(404);
|
|
165
|
+
});
|
|
166
|
+
|
|
167
|
+
test('Should return 404 for non-existent id', async () => {
|
|
168
|
+
const response = await request(server).delete('/users/3');
|
|
169
|
+
|
|
170
|
+
expect(response.status).toBe(404);
|
|
171
|
+
});
|
|
172
|
+
});
|
|
173
|
+
|
|
174
|
+
describe('createNestedDatabaseRoutes: filter function', () => {
|
|
175
|
+
const nestedDatabase = createNestedDatabase();
|
|
176
|
+
const server = createServer(nestedDatabase);
|
|
177
|
+
|
|
178
|
+
test('Should return filtered array by query', async () => {
|
|
179
|
+
const response = await request(server).get('/users?name=John Doe');
|
|
180
|
+
|
|
181
|
+
expect(response.body).toStrictEqual([
|
|
182
|
+
{
|
|
183
|
+
id: 1,
|
|
184
|
+
name: 'John Doe',
|
|
185
|
+
age: 25,
|
|
186
|
+
address: { city: 'Novosibirsk' }
|
|
187
|
+
}
|
|
188
|
+
]);
|
|
189
|
+
});
|
|
190
|
+
|
|
191
|
+
test('Should return filtered array by identical queries names', async () => {
|
|
192
|
+
const response = await request(server).get('/users?id=1&id=2');
|
|
193
|
+
|
|
194
|
+
expect(response.body).toStrictEqual(nestedDatabase.users);
|
|
195
|
+
});
|
|
196
|
+
|
|
197
|
+
test('Should return filtered array by nested query', async () => {
|
|
198
|
+
const response = await request(server).get('/users?address.city=Novosibirsk');
|
|
199
|
+
|
|
200
|
+
expect(response.body).toStrictEqual([
|
|
201
|
+
{
|
|
202
|
+
id: 1,
|
|
203
|
+
name: 'John Doe',
|
|
204
|
+
age: 25,
|
|
205
|
+
address: { city: 'Novosibirsk' }
|
|
206
|
+
}
|
|
207
|
+
]);
|
|
208
|
+
});
|
|
209
|
+
});
|
|
210
|
+
|
|
211
|
+
describe('createNestedDatabaseRoutes: pagination function', () => {
|
|
212
|
+
const nestedDatabase = createNestedDatabase();
|
|
213
|
+
const server = createServer(nestedDatabase);
|
|
214
|
+
|
|
215
|
+
test('Should return paginationed data by query', async () => {
|
|
216
|
+
const response = await request(server).get('/users?_page=1');
|
|
217
|
+
|
|
218
|
+
expect(response.body.results).toStrictEqual([
|
|
219
|
+
{ id: 1, name: 'John Doe', age: 25, address: { city: 'Novosibirsk' } },
|
|
220
|
+
{ id: 2, name: 'Jane Smith', age: 30, address: { city: 'Tomsk' } }
|
|
221
|
+
]);
|
|
222
|
+
expect(response.body._link).toEqual(
|
|
223
|
+
expect.objectContaining({
|
|
224
|
+
count: 2,
|
|
225
|
+
pages: 1,
|
|
226
|
+
current: 1,
|
|
227
|
+
next: null,
|
|
228
|
+
prev: null
|
|
229
|
+
})
|
|
230
|
+
);
|
|
231
|
+
expect(response.body._link.first).toContain('/users?_page=1');
|
|
232
|
+
expect(response.body._link.last).toContain('/users?_page=1');
|
|
233
|
+
});
|
|
234
|
+
|
|
235
|
+
test('Should return paginationed data by query with limit', async () => {
|
|
236
|
+
const response = await request(server).get('/users?_page=1&_limit=1');
|
|
237
|
+
|
|
238
|
+
expect(response.body.results).toStrictEqual([
|
|
239
|
+
{ id: 1, name: 'John Doe', age: 25, address: { city: 'Novosibirsk' } }
|
|
240
|
+
]);
|
|
241
|
+
expect(response.body._link).toEqual(
|
|
242
|
+
expect.objectContaining({
|
|
243
|
+
count: 2,
|
|
244
|
+
pages: 2,
|
|
245
|
+
current: 1,
|
|
246
|
+
prev: null
|
|
247
|
+
})
|
|
248
|
+
);
|
|
249
|
+
expect(response.body._link.first).toContain('/users?_page=1&_limit=1');
|
|
250
|
+
expect(response.body._link.last).toContain('/users?_page=2&_limit=1');
|
|
251
|
+
expect(response.body._link.next).toContain('/users?_page=2&_limit=1');
|
|
252
|
+
});
|
|
253
|
+
|
|
254
|
+
test('Should return valid _link for paginationed data', async () => {
|
|
255
|
+
const linkHeaderRegexp = /<([^>]+)>;\s*rel="([^"]+)"/g;
|
|
256
|
+
const firstResponse = await request(server).get('/users?_page=1&_limit=1');
|
|
257
|
+
|
|
258
|
+
const firstResponseLinks = firstResponse.headers.link.match(linkHeaderRegexp);
|
|
259
|
+
expect(firstResponse.headers.link).toMatch(linkHeaderRegexp);
|
|
260
|
+
|
|
261
|
+
if (!firstResponseLinks) throw new Error('Link header not found');
|
|
262
|
+
|
|
263
|
+
expect(firstResponseLinks.length).toEqual(3);
|
|
264
|
+
|
|
265
|
+
const [firstNextLink, firstPrevLink, firstLastLink] = firstResponseLinks;
|
|
266
|
+
expect(firstNextLink).toContain('/users?_page=1&_limit=1>; rel="first"');
|
|
267
|
+
expect(firstPrevLink).toContain('/users?_page=2&_limit=1>; rel="next"');
|
|
268
|
+
expect(firstLastLink).toContain('/users?_page=2&_limit=1>; rel="last"');
|
|
269
|
+
|
|
270
|
+
expect(firstResponse.body.results).toStrictEqual([
|
|
271
|
+
{ id: 1, name: 'John Doe', age: 25, address: { city: 'Novosibirsk' } }
|
|
272
|
+
]);
|
|
273
|
+
expect(firstResponse.body._link).toEqual(
|
|
274
|
+
expect.objectContaining({
|
|
275
|
+
count: 2,
|
|
276
|
+
pages: 2,
|
|
277
|
+
current: 1,
|
|
278
|
+
prev: null
|
|
279
|
+
})
|
|
280
|
+
);
|
|
281
|
+
expect(firstResponse.body._link.first).toContain('/users?_page=1&_limit=1');
|
|
282
|
+
expect(firstResponse.body._link.last).toContain('/users?_page=2&_limit=1');
|
|
283
|
+
expect(firstResponse.body._link.next).toContain('/users?_page=2&_limit=1');
|
|
284
|
+
|
|
285
|
+
const secondResponse = await request(server).get('/users?_page=2&_limit=1');
|
|
286
|
+
|
|
287
|
+
const secondResponseLinks = firstResponse.headers.link.match(linkHeaderRegexp);
|
|
288
|
+
expect(secondResponse.headers.link).toMatch(linkHeaderRegexp);
|
|
289
|
+
|
|
290
|
+
if (!secondResponseLinks) throw new Error('Link header not found');
|
|
291
|
+
|
|
292
|
+
expect(secondResponseLinks.length).toEqual(3);
|
|
293
|
+
|
|
294
|
+
const [secondNextLink, secondPrevLink, secondLastLink] = secondResponseLinks;
|
|
295
|
+
expect(secondNextLink).toContain('/users?_page=1&_limit=1>; rel="first"');
|
|
296
|
+
expect(secondPrevLink).toContain('/users?_page=2&_limit=1>; rel="next"');
|
|
297
|
+
expect(secondLastLink).toContain('/users?_page=2&_limit=1>; rel="last"');
|
|
298
|
+
|
|
299
|
+
expect(secondResponse.body.results).toStrictEqual([
|
|
300
|
+
{ id: 2, name: 'Jane Smith', age: 30, address: { city: 'Tomsk' } }
|
|
301
|
+
]);
|
|
302
|
+
expect(secondResponse.body._link).toEqual(
|
|
303
|
+
expect.objectContaining({
|
|
304
|
+
count: 2,
|
|
305
|
+
pages: 2,
|
|
306
|
+
current: 2,
|
|
307
|
+
next: null
|
|
308
|
+
})
|
|
309
|
+
);
|
|
310
|
+
expect(secondResponse.body._link.first).toContain('/users?_page=1&_limit=1');
|
|
311
|
+
expect(secondResponse.body._link.last).toContain('/users?_page=2&_limit=1');
|
|
312
|
+
expect(secondResponse.body._link.prev).toContain('/users?_page=1&_limit=1');
|
|
313
|
+
});
|
|
314
|
+
|
|
315
|
+
test('Should return valid data by invalid pagination data', async () => {
|
|
316
|
+
const response = await request(server).get('/users?_page=2');
|
|
317
|
+
|
|
318
|
+
expect(response.body).toStrictEqual([
|
|
319
|
+
{ id: 1, name: 'John Doe', age: 25, address: { city: 'Novosibirsk' } },
|
|
320
|
+
{ id: 2, name: 'Jane Smith', age: 30, address: { city: 'Tomsk' } }
|
|
321
|
+
]);
|
|
322
|
+
});
|
|
323
|
+
});
|
|
324
|
+
|
|
325
|
+
describe('createNestedDatabaseRoutes: slice function', () => {
|
|
326
|
+
const nestedDatabase = createNestedDatabase();
|
|
327
|
+
const server = createServer(nestedDatabase);
|
|
328
|
+
|
|
329
|
+
test('Should return sliced array by _begin query', async () => {
|
|
330
|
+
const response = await request(server).get('/users?_begin=1');
|
|
331
|
+
|
|
332
|
+
expect(response.body).toStrictEqual(nestedDatabase.users.slice(1));
|
|
333
|
+
});
|
|
334
|
+
|
|
335
|
+
test('Should return sliced array by _end query', async () => {
|
|
336
|
+
const response = await request(server).get('/users?_end=1');
|
|
337
|
+
|
|
338
|
+
expect(response.body).toStrictEqual(nestedDatabase.users.slice(0, 1));
|
|
339
|
+
});
|
|
340
|
+
|
|
341
|
+
test('Should return sliced array by _begin and _end query', async () => {
|
|
342
|
+
const response = await request(server).get('/users?_begin=0&_end=2');
|
|
343
|
+
|
|
344
|
+
expect(response.body).toStrictEqual(nestedDatabase.users.slice(0, 2));
|
|
345
|
+
});
|
|
346
|
+
});
|
|
347
|
+
|
|
348
|
+
describe('createNestedDatabaseRoutes: sort function', () => {
|
|
349
|
+
const nestedDatabase = createNestedDatabase();
|
|
350
|
+
const server = createServer({
|
|
351
|
+
users: [
|
|
352
|
+
...nestedDatabase.users,
|
|
353
|
+
{ id: 3, name: 'Will Smith', age: 27, address: { city: 'Moscow' } }
|
|
354
|
+
]
|
|
355
|
+
});
|
|
356
|
+
|
|
357
|
+
test('Should return sorted data by query', async () => {
|
|
358
|
+
const response = await request(server).get('/users?_sort=age');
|
|
359
|
+
|
|
360
|
+
expect(response.body).toStrictEqual([
|
|
361
|
+
{ id: 1, name: 'John Doe', age: 25, address: { city: 'Novosibirsk' } },
|
|
362
|
+
{ id: 3, name: 'Will Smith', age: 27, address: { city: 'Moscow' } },
|
|
363
|
+
{ id: 2, name: 'Jane Smith', age: 30, address: { city: 'Tomsk' } }
|
|
364
|
+
]);
|
|
365
|
+
});
|
|
366
|
+
|
|
367
|
+
test('Should return sorted data by query with order', async () => {
|
|
368
|
+
const response = await request(server).get('/users?_sort=age&_order=desc');
|
|
369
|
+
|
|
370
|
+
expect(response.body).toStrictEqual([
|
|
371
|
+
{ id: 2, name: 'Jane Smith', age: 30, address: { city: 'Tomsk' } },
|
|
372
|
+
{ id: 3, name: 'Will Smith', age: 27, address: { city: 'Moscow' } },
|
|
373
|
+
{ id: 1, name: 'John Doe', age: 25, address: { city: 'Novosibirsk' } }
|
|
374
|
+
]);
|
|
375
|
+
});
|
|
376
|
+
|
|
377
|
+
test('Should return sorted data by multiple query', async () => {
|
|
378
|
+
const response = await request(server).get(
|
|
379
|
+
'/users?_sort=name&_order=asc&_sort=id&_order=desc'
|
|
380
|
+
);
|
|
381
|
+
|
|
382
|
+
expect(response.body).toStrictEqual([
|
|
383
|
+
{ id: 3, name: 'Will Smith', age: 27, address: { city: 'Moscow' } },
|
|
384
|
+
{ id: 2, name: 'Jane Smith', age: 30, address: { city: 'Tomsk' } },
|
|
385
|
+
{ id: 1, name: 'John Doe', age: 25, address: { city: 'Novosibirsk' } }
|
|
386
|
+
]);
|
|
387
|
+
});
|
|
388
|
+
|
|
389
|
+
test('Should return filtered array by nested query', async () => {
|
|
390
|
+
const response = await request(server).get('/users?_sort=address.city&_order=desc');
|
|
391
|
+
|
|
392
|
+
expect(response.body).toStrictEqual([
|
|
393
|
+
{ id: 2, name: 'Jane Smith', age: 30, address: { city: 'Tomsk' } },
|
|
394
|
+
{ id: 1, name: 'John Doe', age: 25, address: { city: 'Novosibirsk' } },
|
|
395
|
+
{ id: 3, name: 'Will Smith', age: 27, address: { city: 'Moscow' } }
|
|
396
|
+
]);
|
|
397
|
+
});
|
|
398
|
+
});
|
|
399
|
+
});
|