@mimik/api-helper 3.0.0 → 3.0.2
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 +64 -31
- package/index.js +54 -41
- package/lib/baseHandlers.js +6 -24
- package/lib/common.js +1 -1
- package/lib/extract-helper.js +7 -12
- package/lib/oauthValidation-helper.js +2 -2
- package/lib/securityHandlers.js +59 -132
- package/package.json +16 -13
- package/.claude/settings.local.json +0 -9
- package/.husky/pre-commit +0 -2
- package/.husky/pre-push +0 -2
- package/eslint.config.js +0 -82
- package/test/ajvHelpers.test.js +0 -159
- package/test/baseHandlers.test.js +0 -150
- package/test/extract-helper.test.js +0 -100
- package/test/index-async.test.js +0 -599
- package/test/index-sync.test.js +0 -282
- package/test/oauthValidation-helper.test.js +0 -136
- package/test/securityHandlers.test.js +0 -557
package/lib/securityHandlers.js
CHANGED
|
@@ -36,14 +36,7 @@ const SCOPE_INDEX = 0;
|
|
|
36
36
|
const CLAIMS_INDEX = 1;
|
|
37
37
|
const RESOURCE_INDEX = 0;
|
|
38
38
|
|
|
39
|
-
const getScopes = (conf, securityType) =>
|
|
40
|
-
let scopes = [];
|
|
41
|
-
|
|
42
|
-
conf.operation.security.forEach((security) => {
|
|
43
|
-
if (security[securityType]) scopes = scopes.concat(security[securityType]);
|
|
44
|
-
});
|
|
45
|
-
return scopes;
|
|
46
|
-
};
|
|
39
|
+
const getScopes = (conf, securityType) => conf.operation.security.flatMap(security => security[securityType] || []);
|
|
47
40
|
|
|
48
41
|
const getError = (message, statusCode) => {
|
|
49
42
|
const error = new Error(message);
|
|
@@ -53,15 +46,8 @@ const getError = (message, statusCode) => {
|
|
|
53
46
|
};
|
|
54
47
|
|
|
55
48
|
const checkToken = (authToken) => {
|
|
56
|
-
|
|
49
|
+
const token = jwt.decode(authToken);
|
|
57
50
|
|
|
58
|
-
try {
|
|
59
|
-
token = jwt.decode(authToken);
|
|
60
|
-
}
|
|
61
|
-
catch (err) {
|
|
62
|
-
err.statusCode = UNAUTHORIZED_ERROR;
|
|
63
|
-
throw err;
|
|
64
|
-
}
|
|
65
51
|
if (!token) {
|
|
66
52
|
throw getError('invalid token', UNAUTHORIZED_ERROR);
|
|
67
53
|
}
|
|
@@ -93,17 +79,17 @@ const checkScopes = (tokenScopes, defScopes, definition) => {
|
|
|
93
79
|
if (!tokenScopes) {
|
|
94
80
|
throw getError('no scope in authorization token', UNAUTHORIZED_ERROR);
|
|
95
81
|
}
|
|
96
|
-
|
|
82
|
+
const claims = [];
|
|
97
83
|
let onBehalf = false;
|
|
98
84
|
|
|
99
85
|
if (defScopes && defScopes.length !== EMPTY) {
|
|
100
86
|
const currentScopes = tokenScopes.split(SCOPES_SEPARATOR);
|
|
101
87
|
const intersects = [];
|
|
102
|
-
let resourceIndex = FIRST;
|
|
103
88
|
|
|
104
89
|
currentScopes.forEach((currentScope) => {
|
|
105
90
|
const analyzedScope = currentScope.split(SCOPE_CLAIMS_SEPARATOR);
|
|
106
91
|
const analyzedResource = analyzedScope[SCOPE_INDEX].split(RESOURCE_SEPARATOR);
|
|
92
|
+
let resourceIndex = FIRST;
|
|
107
93
|
|
|
108
94
|
if (analyzedResource[RESOURCE_INDEX] === ON_BEHALF) {
|
|
109
95
|
onBehalf = true;
|
|
@@ -129,7 +115,7 @@ const checkScopes = (tokenScopes, defScopes, definition) => {
|
|
|
129
115
|
if (claimsIntersects.length !== includedClaims.length) {
|
|
130
116
|
throw getError(`incorrect claims included: ${includedClaims.filter(cla => !claimsIntersects.includes(cla))}`, FORBIDDEN_ERROR);
|
|
131
117
|
}
|
|
132
|
-
claims
|
|
118
|
+
claims.push(...claimsIntersects);
|
|
133
119
|
}
|
|
134
120
|
intersects.push(analyzedScope[SCOPE_INDEX]);
|
|
135
121
|
}
|
|
@@ -141,13 +127,26 @@ const checkScopes = (tokenScopes, defScopes, definition) => {
|
|
|
141
127
|
return { onBehalf, claims };
|
|
142
128
|
};
|
|
143
129
|
|
|
130
|
+
const setParam = (req, request, key, value) => {
|
|
131
|
+
req[key] = value;
|
|
132
|
+
request[key] = value;
|
|
133
|
+
};
|
|
134
|
+
|
|
135
|
+
const setParams = (req, request, params) => {
|
|
136
|
+
Object.keys(params).forEach(key => setParam(req, request, key, params[key]));
|
|
137
|
+
};
|
|
138
|
+
|
|
139
|
+
const createMockHandler = params => (con, req) => {
|
|
140
|
+
setParams(req, con.request, params);
|
|
141
|
+
return true;
|
|
142
|
+
};
|
|
143
|
+
|
|
144
144
|
export const securityLib = (config) => {
|
|
145
145
|
const verifyTokenClientCredentials = (authToken) => {
|
|
146
146
|
const { server, generic } = config.security;
|
|
147
147
|
const options = {
|
|
148
148
|
audience: (generic.audience === NO_GENERIC) ? server.audience : generic.audience,
|
|
149
149
|
issuer: server.issuer,
|
|
150
|
-
// subject: `${config.serverSettings.id}@clients`,
|
|
151
150
|
};
|
|
152
151
|
|
|
153
152
|
try {
|
|
@@ -186,19 +185,24 @@ export const securityLib = (config) => {
|
|
|
186
185
|
}
|
|
187
186
|
};
|
|
188
187
|
|
|
188
|
+
const setClientParams = (req, request, token, scopeResult) => {
|
|
189
|
+
setParam(req, request, TOKEN_PARAMS.claims, scopeResult.claims);
|
|
190
|
+
if (scopeResult.onBehalf) setParam(req, request, TOKEN_PARAMS.onBehalf, true);
|
|
191
|
+
if (token.subType) setParam(req, request, TOKEN_PARAMS.tokenType, token.subType);
|
|
192
|
+
if (token.sub) setParam(req, request, TOKEN_PARAMS.clientId, token.sub);
|
|
193
|
+
if (token.cust) setParam(req, request, TOKEN_PARAMS.customer, token.cust);
|
|
194
|
+
};
|
|
195
|
+
|
|
189
196
|
const AdminSecurity = {
|
|
190
197
|
regular: (con, req) => {
|
|
191
198
|
const authToken = checkHeaders(req.headers);
|
|
192
199
|
const token = checkToken(authToken);
|
|
193
|
-
const { request } = con;
|
|
194
200
|
|
|
195
201
|
if (token.subType !== ADMIN && token.subType !== SUB_ADMIN) {
|
|
196
202
|
throw getError('invalid token: wrong type', FORBIDDEN_ERROR);
|
|
197
203
|
}
|
|
198
204
|
if (token.subType === SUB_ADMIN) {
|
|
199
|
-
if (!token.cust)
|
|
200
|
-
throw getError('invalid token: no customer', FORBIDDEN_ERROR);
|
|
201
|
-
}
|
|
205
|
+
if (!token.cust) throw getError('invalid token: no customer', FORBIDDEN_ERROR);
|
|
202
206
|
}
|
|
203
207
|
else if (token.sub !== `${config.security.admin.externalId}${CLIENT}`) {
|
|
204
208
|
throw getError(`jwt subject invalid: ${token.sub}`, FORBIDDEN_ERROR);
|
|
@@ -206,42 +210,21 @@ export const securityLib = (config) => {
|
|
|
206
210
|
verifyTokenClientCredentials(authToken);
|
|
207
211
|
const scopeResult = checkScopes(token.scope, getScopes(con, ADMIN_SECURITY), con.api.definition);
|
|
208
212
|
|
|
209
|
-
req
|
|
210
|
-
request[TOKEN_PARAMS.claims] = scopeResult.claims;
|
|
211
|
-
if (token.subType) {
|
|
212
|
-
req[TOKEN_PARAMS.tokenType] = token.subType;
|
|
213
|
-
request[TOKEN_PARAMS.tokenType] = token.subType;
|
|
214
|
-
}
|
|
215
|
-
if (token.sub) {
|
|
216
|
-
req[TOKEN_PARAMS.clientId] = token.sub;
|
|
217
|
-
request[TOKEN_PARAMS.clientId] = token.sub;
|
|
218
|
-
}
|
|
219
|
-
if (token.cust) {
|
|
220
|
-
req[TOKEN_PARAMS.customer] = token.cust;
|
|
221
|
-
request[TOKEN_PARAMS.customer] = token.cust;
|
|
222
|
-
}
|
|
223
|
-
return true;
|
|
224
|
-
},
|
|
225
|
-
mock: (con, req) => {
|
|
226
|
-
const { request } = con;
|
|
227
|
-
|
|
228
|
-
req[TOKEN_PARAMS.claims] = ['dummyClaims'];
|
|
229
|
-
req[TOKEN_PARAMS.tokenType] = ADMIN;
|
|
230
|
-
req[TOKEN_PARAMS.clientId] = 'dummyClientId';
|
|
231
|
-
req[TOKEN_PARAMS.customer] = 'dummyCustomer';
|
|
232
|
-
request[TOKEN_PARAMS.claims] = ['dummyClaims'];
|
|
233
|
-
request[TOKEN_PARAMS.tokenType] = ADMIN;
|
|
234
|
-
request[TOKEN_PARAMS.clientId] = 'dummyClientId';
|
|
235
|
-
request[TOKEN_PARAMS.customer] = 'dummyCustomer';
|
|
213
|
+
setClientParams(req, con.request, token, scopeResult);
|
|
236
214
|
return true;
|
|
237
215
|
},
|
|
216
|
+
mock: createMockHandler({
|
|
217
|
+
[TOKEN_PARAMS.claims]: ['dummyClaims'],
|
|
218
|
+
[TOKEN_PARAMS.tokenType]: ADMIN,
|
|
219
|
+
[TOKEN_PARAMS.clientId]: 'dummyClientId',
|
|
220
|
+
[TOKEN_PARAMS.customer]: 'dummyCustomer',
|
|
221
|
+
}),
|
|
238
222
|
};
|
|
239
223
|
|
|
240
224
|
const SystemSecurity = {
|
|
241
225
|
regular: (con, req) => {
|
|
242
226
|
const authToken = checkHeaders(req.headers);
|
|
243
227
|
const token = checkToken(authToken);
|
|
244
|
-
const { request } = con;
|
|
245
228
|
|
|
246
229
|
if (token.subType === ADMIN || token.subType === SUB_ADMIN) {
|
|
247
230
|
throw getError('invalid token: wrong type', FORBIDDEN_ERROR);
|
|
@@ -249,112 +232,56 @@ export const securityLib = (config) => {
|
|
|
249
232
|
verifyTokenClientCredentials(authToken);
|
|
250
233
|
const scopeResult = checkScopes(token.scope, getScopes(con, SYSTEM_SECURITY), con.api.definition);
|
|
251
234
|
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
request[TOKEN_PARAMS.onBehalf] = true;
|
|
255
|
-
}
|
|
256
|
-
req[TOKEN_PARAMS.claims] = scopeResult.claims;
|
|
257
|
-
request[TOKEN_PARAMS.claims] = scopeResult.claims;
|
|
258
|
-
if (token.subType) {
|
|
259
|
-
req[TOKEN_PARAMS.tokenType] = token.subType;
|
|
260
|
-
request[TOKEN_PARAMS.tokenType] = token.subType;
|
|
261
|
-
}
|
|
262
|
-
if (token.sub) {
|
|
263
|
-
req[TOKEN_PARAMS.clientId] = token.sub;
|
|
264
|
-
request[TOKEN_PARAMS.clientId] = token.sub;
|
|
265
|
-
}
|
|
266
|
-
if (token.cust) {
|
|
267
|
-
req[TOKEN_PARAMS.customer] = token.cust;
|
|
268
|
-
request[TOKEN_PARAMS.customer] = token.cust;
|
|
269
|
-
}
|
|
270
|
-
if (token.type === CLUSTER) {
|
|
271
|
-
req[TOKEN_PARAMS.cluster] = true;
|
|
272
|
-
request[TOKEN_PARAMS.cluster] = true;
|
|
273
|
-
}
|
|
274
|
-
return true;
|
|
275
|
-
},
|
|
276
|
-
mock: (con, req) => {
|
|
277
|
-
const { request } = con;
|
|
278
|
-
|
|
279
|
-
req[TOKEN_PARAMS.claims] = ['dummyClaims'];
|
|
280
|
-
req[TOKEN_PARAMS.tokenType] = 'dummyServiceType';
|
|
281
|
-
req[TOKEN_PARAMS.clientId] = 'dummyClientId';
|
|
282
|
-
req[TOKEN_PARAMS.customer] = 'dummyCustomer';
|
|
283
|
-
request[TOKEN_PARAMS.claims] = ['dummyClaims'];
|
|
284
|
-
request[TOKEN_PARAMS.tokenType] = 'dummyServiceType';
|
|
285
|
-
request[TOKEN_PARAMS.clientId] = 'dummyClientId';
|
|
286
|
-
request[TOKEN_PARAMS.customer] = 'dummyCustomer';
|
|
235
|
+
setClientParams(req, con.request, token, scopeResult);
|
|
236
|
+
if (token.type === CLUSTER) setParam(req, con.request, TOKEN_PARAMS.cluster, true);
|
|
287
237
|
return true;
|
|
288
238
|
},
|
|
239
|
+
mock: createMockHandler({
|
|
240
|
+
[TOKEN_PARAMS.claims]: ['dummyClaims'],
|
|
241
|
+
[TOKEN_PARAMS.tokenType]: 'dummyServiceType',
|
|
242
|
+
[TOKEN_PARAMS.clientId]: 'dummyClientId',
|
|
243
|
+
[TOKEN_PARAMS.customer]: 'dummyCustomer',
|
|
244
|
+
}),
|
|
289
245
|
};
|
|
290
246
|
|
|
291
247
|
const UserSecurity = {
|
|
292
248
|
regular: (con, req) => {
|
|
293
249
|
const authToken = checkHeaders(req.headers);
|
|
294
250
|
const token = checkToken(authToken);
|
|
295
|
-
const { request } = con;
|
|
296
251
|
|
|
297
252
|
verifyTokenImplicit(authToken);
|
|
298
253
|
const scopeResult = checkScopes(token.scope, getScopes(con, USER_SECURITY), con.api.definition);
|
|
299
254
|
|
|
300
|
-
if (scopeResult.onBehalf)
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
req
|
|
305
|
-
request[TOKEN_PARAMS.claims] = scopeResult.claims;
|
|
306
|
-
req[TOKEN_PARAMS.tokenType] = USER;
|
|
307
|
-
request[TOKEN_PARAMS.tokenType] = USER;
|
|
308
|
-
if (token.sub) {
|
|
309
|
-
req[TOKEN_PARAMS.userId] = token.sub;
|
|
310
|
-
request[TOKEN_PARAMS.userId] = token.sub;
|
|
311
|
-
}
|
|
312
|
-
if (token.azp) {
|
|
313
|
-
req[TOKEN_PARAMS.appId] = token.azp;
|
|
314
|
-
request[TOKEN_PARAMS.appId] = token.azp;
|
|
315
|
-
}
|
|
255
|
+
if (scopeResult.onBehalf) setParam(req, con.request, TOKEN_PARAMS.onBehalf, true);
|
|
256
|
+
setParam(req, con.request, TOKEN_PARAMS.claims, scopeResult.claims);
|
|
257
|
+
setParam(req, con.request, TOKEN_PARAMS.tokenType, USER);
|
|
258
|
+
if (token.sub) setParam(req, con.request, TOKEN_PARAMS.userId, token.sub);
|
|
259
|
+
if (token.azp) setParam(req, con.request, TOKEN_PARAMS.appId, token.azp);
|
|
316
260
|
if (token.may_act && token.may_act.sub) {
|
|
317
|
-
req
|
|
318
|
-
request
|
|
319
|
-
req[TOKEN_PARAMS.userId] = token.may_act.sub;
|
|
320
|
-
request[TOKEN_PARAMS.userId] = token.may_act.sub;
|
|
261
|
+
setParam(req, con.request, TOKEN_PARAMS.onBehalfId, token.sub);
|
|
262
|
+
setParam(req, con.request, TOKEN_PARAMS.userId, token.may_act.sub);
|
|
321
263
|
}
|
|
322
264
|
return true;
|
|
323
265
|
},
|
|
324
|
-
mock: (
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
req[TOKEN_PARAMS.tokenType] = USER;
|
|
331
|
-
request[TOKEN_PARAMS.claims] = ['dummyClaims'];
|
|
332
|
-
request[TOKEN_PARAMS.userId] = 'dummyUserId';
|
|
333
|
-
request[TOKEN_PARAMS.appId] = 'dummyAppId';
|
|
334
|
-
request[TOKEN_PARAMS.tokenType] = USER;
|
|
335
|
-
return true;
|
|
336
|
-
},
|
|
266
|
+
mock: createMockHandler({
|
|
267
|
+
[TOKEN_PARAMS.claims]: ['dummyClaims'],
|
|
268
|
+
[TOKEN_PARAMS.userId]: 'dummyUserId',
|
|
269
|
+
[TOKEN_PARAMS.appId]: 'dummyAppId',
|
|
270
|
+
[TOKEN_PARAMS.tokenType]: USER,
|
|
271
|
+
}),
|
|
337
272
|
};
|
|
338
273
|
|
|
339
274
|
const ApiKeySecurity = {
|
|
340
275
|
regular: (con, req) => {
|
|
341
276
|
const apiKey = req.headers ? req.headers[API_KEY_NAME.toLowerCase()] : null;
|
|
342
|
-
const { request } = con;
|
|
343
277
|
|
|
344
|
-
if (config.security.apiKeys.includes(apiKey)) {
|
|
345
|
-
req
|
|
346
|
-
request[API_KEY_NAME] = apiKey;
|
|
278
|
+
if (config.security.apiKeys && config.security.apiKeys.includes(apiKey)) {
|
|
279
|
+
setParam(req, con.request, API_KEY_NAME, apiKey);
|
|
347
280
|
return true;
|
|
348
281
|
}
|
|
349
282
|
throw getError('invalid API key', UNAUTHORIZED_ERROR);
|
|
350
283
|
},
|
|
351
|
-
mock: (
|
|
352
|
-
const { request } = con;
|
|
353
|
-
|
|
354
|
-
req[API_KEY_NAME] = 'dummyApiKey';
|
|
355
|
-
request[API_KEY_NAME] = 'dummyApiKey';
|
|
356
|
-
return true;
|
|
357
|
-
},
|
|
284
|
+
mock: createMockHandler({ [API_KEY_NAME]: 'dummyApiKey' }),
|
|
358
285
|
};
|
|
359
286
|
|
|
360
287
|
return {
|
package/package.json
CHANGED
|
@@ -1,13 +1,14 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@mimik/api-helper",
|
|
3
|
-
"version": "3.0.
|
|
3
|
+
"version": "3.0.2",
|
|
4
4
|
"description": "helper for openAPI backend and mimik service",
|
|
5
5
|
"main": "./index.js",
|
|
6
6
|
"type": "module",
|
|
7
|
+
"exports": "./index.js",
|
|
7
8
|
"scripts": {
|
|
8
9
|
"lint": "eslint . --no-error-on-unmatched-pattern",
|
|
9
10
|
"docs": "jsdoc2md index.js > README.md",
|
|
10
|
-
"test": "mocha test/ --recursive",
|
|
11
|
+
"test": "mocha --reporter mochawesome --bail --check-leaks --exit test/ --recursive",
|
|
11
12
|
"test-ci": "c8 --reporter=lcov --reporter=text npm test",
|
|
12
13
|
"prepublishOnly": "npm run docs && npm run lint && npm run test-ci",
|
|
13
14
|
"commit-ready": "npm run docs && npm run lint && npm run test-ci"
|
|
@@ -27,29 +28,31 @@
|
|
|
27
28
|
"url": "https://bitbucket.org/mimiktech/api-helper"
|
|
28
29
|
},
|
|
29
30
|
"dependencies": {
|
|
30
|
-
"@mimik/request-retry": "^4.0.
|
|
31
|
-
"@mimik/response-helper": "^4.0.
|
|
32
|
-
"@mimik/sumologic-winston-logger": "^2.
|
|
33
|
-
"@mimik/swagger-helper": "^5.0.
|
|
31
|
+
"@mimik/request-retry": "^4.0.11",
|
|
32
|
+
"@mimik/response-helper": "^4.0.11",
|
|
33
|
+
"@mimik/sumologic-winston-logger": "^2.2.2",
|
|
34
|
+
"@mimik/swagger-helper": "^5.0.4",
|
|
34
35
|
"ajv-formats": "3.0.1",
|
|
35
36
|
"js-base64": "3.7.8",
|
|
36
37
|
"js-yaml": "4.1.1",
|
|
37
38
|
"jsonwebtoken": "9.0.3",
|
|
38
39
|
"openapi-backend": "5.16.1",
|
|
39
|
-
"swagger-client": "3.
|
|
40
|
+
"swagger-client": "3.37.1"
|
|
40
41
|
},
|
|
41
42
|
"devDependencies": {
|
|
42
|
-
"@eslint/js": "9.
|
|
43
|
-
"@mimik/eslint-plugin-document-env": "^2.0.
|
|
44
|
-
"@
|
|
43
|
+
"@eslint/js": "9.39.4",
|
|
44
|
+
"@mimik/eslint-plugin-document-env": "^2.0.9",
|
|
45
|
+
"@mimik/eslint-plugin-logger": "^1.0.3",
|
|
46
|
+
"@stylistic/eslint-plugin": "5.10.0",
|
|
45
47
|
"c8": "11.0.0",
|
|
46
48
|
"chai": "6.2.2",
|
|
47
|
-
"eslint": "9.
|
|
49
|
+
"eslint": "9.39.4",
|
|
48
50
|
"eslint-plugin-import": "2.32.0",
|
|
49
51
|
"esmock": "2.7.3",
|
|
50
|
-
"globals": "17.
|
|
52
|
+
"globals": "17.4.0",
|
|
51
53
|
"husky": "9.1.7",
|
|
52
54
|
"jsdoc-to-markdown": "9.1.3",
|
|
53
|
-
"mocha": "11.7.5"
|
|
55
|
+
"mocha": "11.7.5",
|
|
56
|
+
"mochawesome": "7.1.4"
|
|
54
57
|
}
|
|
55
58
|
}
|
package/.husky/pre-commit
DELETED
package/.husky/pre-push
DELETED
package/eslint.config.js
DELETED
|
@@ -1,82 +0,0 @@
|
|
|
1
|
-
import globals from 'globals';
|
|
2
|
-
import importPlugin from 'eslint-plugin-import';
|
|
3
|
-
import js from '@eslint/js';
|
|
4
|
-
import processDoc from '@mimik/eslint-plugin-document-env';
|
|
5
|
-
import stylistic from '@stylistic/eslint-plugin';
|
|
6
|
-
|
|
7
|
-
const MAX_LENGTH_LINE = 180;
|
|
8
|
-
const MAX_FUNCTION_PARAMETERS = 6;
|
|
9
|
-
const MAX_LINES_IN_FILES = 600;
|
|
10
|
-
const MAX_LINES_IN_FUNCTION = 150;
|
|
11
|
-
const MAX_STATEMENTS_IN_FUNCTION = 45;
|
|
12
|
-
const MIN_KEYS_IN_OBJECT = 10;
|
|
13
|
-
const MAX_COMPLEXITY = 30;
|
|
14
|
-
const ECMA_VERSION = 'latest';
|
|
15
|
-
const MAX_DEPTH = 6;
|
|
16
|
-
const ALLOWED_CONSTANTS = [0, 1, -1];
|
|
17
|
-
|
|
18
|
-
export default [
|
|
19
|
-
{
|
|
20
|
-
ignores: ['mochawesome-report/**', 'node_modules/**', 'dist/**'],
|
|
21
|
-
},
|
|
22
|
-
importPlugin.flatConfigs.recommended,
|
|
23
|
-
stylistic.configs.recommended,
|
|
24
|
-
js.configs.all,
|
|
25
|
-
{
|
|
26
|
-
plugins: {
|
|
27
|
-
processDoc,
|
|
28
|
-
},
|
|
29
|
-
languageOptions: {
|
|
30
|
-
ecmaVersion: ECMA_VERSION,
|
|
31
|
-
globals: {
|
|
32
|
-
...globals.mocha,
|
|
33
|
-
...globals.nodeBuiltin,
|
|
34
|
-
},
|
|
35
|
-
sourceType: 'module',
|
|
36
|
-
},
|
|
37
|
-
rules: {
|
|
38
|
-
'@stylistic/brace-style': ['warn', 'stroustrup', { allowSingleLine: true }],
|
|
39
|
-
'@stylistic/line-comment-position': ['off'],
|
|
40
|
-
'@stylistic/max-len': ['warn', MAX_LENGTH_LINE, { ignoreComments: true, ignoreStrings: true, ignoreRegExpLiterals: true }],
|
|
41
|
-
'@stylistic/quotes': ['warn', 'single'],
|
|
42
|
-
'@stylistic/semi': ['error', 'always'],
|
|
43
|
-
'capitalized-comments': ['off'],
|
|
44
|
-
'complexity': ['error', MAX_COMPLEXITY],
|
|
45
|
-
'curly': ['off'],
|
|
46
|
-
'id-length': ['error', { exceptions: ['x', 'y', 'z', 'i', 'j', 'k'] }],
|
|
47
|
-
'import/no-extraneous-dependencies': ['error', { devDependencies: true }],
|
|
48
|
-
'import/no-unresolved': ['error', { amd: true, caseSensitiveStrict: true, commonjs: true }],
|
|
49
|
-
'init-declarations': ['off'],
|
|
50
|
-
'linebreak-style': ['off'],
|
|
51
|
-
'max-depth': ['error', MAX_DEPTH],
|
|
52
|
-
'max-len': ['off'],
|
|
53
|
-
'max-lines': ['warn', { max: MAX_LINES_IN_FILES, skipComments: true, skipBlankLines: true }],
|
|
54
|
-
'max-lines-per-function': ['warn', { max: MAX_LINES_IN_FUNCTION, skipComments: true, skipBlankLines: true }],
|
|
55
|
-
'max-params': ['error', MAX_FUNCTION_PARAMETERS],
|
|
56
|
-
'max-statements': ['warn', MAX_STATEMENTS_IN_FUNCTION],
|
|
57
|
-
'no-confusing-arrow': ['off'],
|
|
58
|
-
'no-inline-comments': ['off'],
|
|
59
|
-
'no-magic-numbers': ['error', { ignore: ALLOWED_CONSTANTS, enforceConst: true, detectObjects: true }],
|
|
60
|
-
'no-process-env': ['error'],
|
|
61
|
-
'no-ternary': ['off'],
|
|
62
|
-
'no-undefined': ['off'],
|
|
63
|
-
'one-var': ['error', 'never'],
|
|
64
|
-
'processDoc/validate-document-env': ['error'],
|
|
65
|
-
'quotes': ['off'],
|
|
66
|
-
'sort-imports': ['error', { allowSeparatedGroups: true }],
|
|
67
|
-
'sort-keys': ['error', 'asc', { caseSensitive: true, minKeys: MIN_KEYS_IN_OBJECT, natural: false, allowLineSeparatedGroups: true }],
|
|
68
|
-
},
|
|
69
|
-
},
|
|
70
|
-
{
|
|
71
|
-
files: ['test/**/*.js'],
|
|
72
|
-
rules: {
|
|
73
|
-
'class-methods-use-this': ['off'],
|
|
74
|
-
'max-classes-per-file': ['off'],
|
|
75
|
-
'max-lines': ['off'],
|
|
76
|
-
'max-lines-per-function': ['off'],
|
|
77
|
-
'max-statements': ['off'],
|
|
78
|
-
'no-empty-function': ['off'],
|
|
79
|
-
'no-magic-numbers': ['off'],
|
|
80
|
-
},
|
|
81
|
-
},
|
|
82
|
-
];
|
package/test/ajvHelpers.test.js
DELETED
|
@@ -1,159 +0,0 @@
|
|
|
1
|
-
import esmock from 'esmock';
|
|
2
|
-
import { expect } from 'chai';
|
|
3
|
-
|
|
4
|
-
let ajvFormats;
|
|
5
|
-
let addedLibFormats;
|
|
6
|
-
let addedCustomFormats;
|
|
7
|
-
|
|
8
|
-
describe('ajvHelpers', () => {
|
|
9
|
-
before(async () => {
|
|
10
|
-
const mod = await esmock('../lib/ajvHelpers.js', {
|
|
11
|
-
'ajv-formats': {
|
|
12
|
-
default: (ajv, formats) => {
|
|
13
|
-
addedLibFormats = formats;
|
|
14
|
-
},
|
|
15
|
-
},
|
|
16
|
-
});
|
|
17
|
-
|
|
18
|
-
({ ajvFormats } = mod);
|
|
19
|
-
});
|
|
20
|
-
|
|
21
|
-
beforeEach(() => {
|
|
22
|
-
addedLibFormats = null;
|
|
23
|
-
addedCustomFormats = {};
|
|
24
|
-
});
|
|
25
|
-
|
|
26
|
-
const createMockAjv = () => ({
|
|
27
|
-
addFormat: (name, format) => {
|
|
28
|
-
addedCustomFormats[name] = format;
|
|
29
|
-
},
|
|
30
|
-
});
|
|
31
|
-
|
|
32
|
-
describe('with no extra formats', () => {
|
|
33
|
-
it('should add default library formats', () => {
|
|
34
|
-
const ajv = createMockAjv();
|
|
35
|
-
const configurer = ajvFormats();
|
|
36
|
-
|
|
37
|
-
configurer(ajv);
|
|
38
|
-
expect(addedLibFormats).to.deep.equal(['date', 'time', 'date-time', 'byte', 'uuid', 'uri', 'email', 'ipv4', 'ipv6']);
|
|
39
|
-
});
|
|
40
|
-
|
|
41
|
-
it('should add built-in semver and ip custom formats', () => {
|
|
42
|
-
const ajv = createMockAjv();
|
|
43
|
-
const configurer = ajvFormats();
|
|
44
|
-
|
|
45
|
-
configurer(ajv);
|
|
46
|
-
expect(addedCustomFormats).to.have.property('semver');
|
|
47
|
-
expect(addedCustomFormats.semver.type).to.equal('string');
|
|
48
|
-
expect(addedCustomFormats).to.have.property('ip');
|
|
49
|
-
expect(addedCustomFormats.ip.type).to.equal('string');
|
|
50
|
-
});
|
|
51
|
-
|
|
52
|
-
it('should return the ajv instance', () => {
|
|
53
|
-
const ajv = createMockAjv();
|
|
54
|
-
const configurer = ajvFormats();
|
|
55
|
-
const result = configurer(ajv);
|
|
56
|
-
|
|
57
|
-
expect(result).to.equal(ajv);
|
|
58
|
-
});
|
|
59
|
-
});
|
|
60
|
-
|
|
61
|
-
describe('with null extra formats', () => {
|
|
62
|
-
it('should treat null as no extra formats', () => {
|
|
63
|
-
const ajv = createMockAjv();
|
|
64
|
-
const configurer = ajvFormats(null);
|
|
65
|
-
|
|
66
|
-
configurer(ajv);
|
|
67
|
-
expect(addedLibFormats).to.deep.equal(['date', 'time', 'date-time', 'byte', 'uuid', 'uri', 'email', 'ipv4', 'ipv6']);
|
|
68
|
-
});
|
|
69
|
-
});
|
|
70
|
-
|
|
71
|
-
describe('with extra library formats (string-only)', () => {
|
|
72
|
-
it('should append format names to the library formats list', () => {
|
|
73
|
-
const ajv = createMockAjv();
|
|
74
|
-
const configurer = ajvFormats({ hostname: {} });
|
|
75
|
-
|
|
76
|
-
configurer(ajv);
|
|
77
|
-
expect(addedLibFormats).to.include('hostname');
|
|
78
|
-
expect(addedLibFormats).to.have.lengthOf(10);
|
|
79
|
-
});
|
|
80
|
-
});
|
|
81
|
-
|
|
82
|
-
describe('with extra custom formats (with type)', () => {
|
|
83
|
-
it('should add custom format via ajv.addFormat', () => {
|
|
84
|
-
const customFormat = { type: 'string', validate: /^[A-Z]+$/u };
|
|
85
|
-
const ajv = createMockAjv();
|
|
86
|
-
const configurer = ajvFormats({ uppercase: customFormat });
|
|
87
|
-
|
|
88
|
-
configurer(ajv);
|
|
89
|
-
expect(addedCustomFormats).to.have.property('uppercase');
|
|
90
|
-
expect(addedCustomFormats.uppercase).to.equal(customFormat);
|
|
91
|
-
expect(addedLibFormats).to.not.include('uppercase');
|
|
92
|
-
});
|
|
93
|
-
});
|
|
94
|
-
|
|
95
|
-
describe('semver validation', () => {
|
|
96
|
-
it('should accept valid semver strings', () => {
|
|
97
|
-
const ajv = createMockAjv();
|
|
98
|
-
|
|
99
|
-
ajvFormats()(ajv);
|
|
100
|
-
const { validate } = addedCustomFormats.semver;
|
|
101
|
-
|
|
102
|
-
expect(validate.test('1.0.0')).to.equal(true);
|
|
103
|
-
expect(validate.test('0.1.0')).to.equal(true);
|
|
104
|
-
expect(validate.test('1.2.3-alpha.1')).to.equal(true);
|
|
105
|
-
expect(validate.test('1.2.3+build.123')).to.equal(true);
|
|
106
|
-
expect(validate.test('1.2.3-beta+build')).to.equal(true);
|
|
107
|
-
});
|
|
108
|
-
|
|
109
|
-
it('should reject invalid semver strings', () => {
|
|
110
|
-
const ajv = createMockAjv();
|
|
111
|
-
|
|
112
|
-
ajvFormats()(ajv);
|
|
113
|
-
const { validate } = addedCustomFormats.semver;
|
|
114
|
-
|
|
115
|
-
expect(validate.test('1.0')).to.equal(false);
|
|
116
|
-
expect(validate.test('abc')).to.equal(false);
|
|
117
|
-
expect(validate.test('1.0.0.0')).to.equal(false);
|
|
118
|
-
});
|
|
119
|
-
});
|
|
120
|
-
|
|
121
|
-
describe('ip validation', () => {
|
|
122
|
-
it('should accept valid IPv4 addresses', () => {
|
|
123
|
-
const ajv = createMockAjv();
|
|
124
|
-
|
|
125
|
-
ajvFormats()(ajv);
|
|
126
|
-
const { validate } = addedCustomFormats.ip;
|
|
127
|
-
|
|
128
|
-
expect(validate.test('192.168.1.1')).to.equal(true);
|
|
129
|
-
expect(validate.test('10.0.0.1')).to.equal(true);
|
|
130
|
-
expect(validate.test('255.255.255.255')).to.equal(true);
|
|
131
|
-
});
|
|
132
|
-
|
|
133
|
-
it('should accept valid IPv6 addresses', () => {
|
|
134
|
-
const ajv = createMockAjv();
|
|
135
|
-
|
|
136
|
-
ajvFormats()(ajv);
|
|
137
|
-
const { validate } = addedCustomFormats.ip;
|
|
138
|
-
|
|
139
|
-
expect(validate.test('::1')).to.equal(true);
|
|
140
|
-
expect(validate.test('2001:0db8:85a3:0000:0000:8a2e:0370:7334')).to.equal(true);
|
|
141
|
-
});
|
|
142
|
-
});
|
|
143
|
-
|
|
144
|
-
describe('DEFAULT_FORMATS isolation', () => {
|
|
145
|
-
it('should not mutate DEFAULT_FORMATS between calls', () => {
|
|
146
|
-
const ajv1 = createMockAjv();
|
|
147
|
-
const ajv2 = createMockAjv();
|
|
148
|
-
|
|
149
|
-
ajvFormats({ extra1: {} })(ajv1);
|
|
150
|
-
const firstCallLength = addedLibFormats.length;
|
|
151
|
-
|
|
152
|
-
ajvFormats()(ajv2);
|
|
153
|
-
const secondCallLength = addedLibFormats.length;
|
|
154
|
-
|
|
155
|
-
expect(secondCallLength).to.equal(9);
|
|
156
|
-
expect(firstCallLength).to.equal(10);
|
|
157
|
-
});
|
|
158
|
-
});
|
|
159
|
-
});
|