@mimik/api-helper 2.0.6 → 2.0.8

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.
@@ -0,0 +1,282 @@
1
+ import esmock from 'esmock';
2
+ import { expect } from 'chai';
3
+ import fsReal from 'fs';
4
+ import os from 'os';
5
+ import path from 'path';
6
+
7
+ const getRichError = (type, message, info, cause) => {
8
+ const error = new Error(message);
9
+
10
+ error.type = type;
11
+ error.info = info;
12
+ if (cause) error.cause = cause;
13
+ return error;
14
+ };
15
+
16
+ const mockLogger = {
17
+ info: () => {
18
+ // empty method
19
+ },
20
+ debug: () => {
21
+ // empty method
22
+ },
23
+ warn: () => {
24
+ // empty method
25
+ },
26
+ error: () => {
27
+ // empty method
28
+ },
29
+ };
30
+
31
+ let validateSecuritySchemes;
32
+ let extractProperties;
33
+ let tmpDir;
34
+
35
+ describe('index.js sync functions', () => {
36
+ before(async () => {
37
+ const mod = await esmock('../index.js', {
38
+ '@mimik/response-helper': { getRichError },
39
+ '@mimik/sumologic-winston-logger': mockLogger,
40
+ '@mimik/swagger-helper': {
41
+ rejectRequest: () => {},
42
+ TOKEN_PARAMS: {
43
+ userId: 'userId', onBehalfId: 'onBehalfId', appId: 'appId',
44
+ clientId: 'clientId', customer: 'customer', onBehalf: 'onBehalf',
45
+ tokenType: 'tokenType', cluster: 'cluster', claims: 'claims',
46
+ },
47
+ },
48
+ '@mimik/request-retry': { rpRetry: () => Promise.resolve() },
49
+ 'swagger-client': { default: { resolve: () => Promise.resolve() } },
50
+ 'openapi-backend': {
51
+ OpenAPIBackend: class {
52
+ register() {}
53
+ registerSecurityHandler() {}
54
+ init() { return Promise.resolve(); }
55
+ },
56
+ },
57
+ });
58
+
59
+ ({ validateSecuritySchemes, extractProperties } = mod);
60
+ });
61
+
62
+ beforeEach(() => {
63
+ tmpDir = fsReal.mkdtempSync(path.join(os.tmpdir(), 'index-sync-test-'));
64
+ });
65
+
66
+ afterEach(() => {
67
+ fsReal.rmSync(tmpDir, { recursive: true, force: true });
68
+ });
69
+
70
+ describe('validateSecuritySchemes', () => {
71
+ it('should return empty array when no components', () => {
72
+ const result = validateSecuritySchemes({}, 'corr-1');
73
+
74
+ expect(result).to.deep.equal([]);
75
+ });
76
+
77
+ it('should return empty array when no securitySchemes', () => {
78
+ const result = validateSecuritySchemes({ components: {} }, 'corr-1');
79
+
80
+ expect(result).to.deep.equal([]);
81
+ });
82
+
83
+ it('should validate all known security scheme types', () => {
84
+ const apiDefinition = {
85
+ components: {
86
+ securitySchemes: {
87
+ AdminSecurity: { type: 'oauth2', flows: { clientCredentials: {} } },
88
+ SystemSecurity: { type: 'oauth2', flows: { clientCredentials: {} } },
89
+ PeerSecurity: { type: 'oauth2', flows: { clientCredentials: {} } },
90
+ UserSecurity: { type: 'oauth2', flows: { implicit: {} } },
91
+ ApiKeySecurity: { in: 'header', name: 'apiKey' },
92
+ },
93
+ },
94
+ };
95
+
96
+ const result = validateSecuritySchemes(apiDefinition, 'corr-2');
97
+ expect(result).to.include('AdminSecurity');
98
+ expect(result).to.include('SystemSecurity');
99
+ expect(result).to.include('PeerSecurity');
100
+ expect(result).to.include('UserSecurity');
101
+ expect(result).to.include('ApiKeySecurity');
102
+ });
103
+
104
+ it('should return null for missing scheme types', () => {
105
+ const apiDefinition = {
106
+ components: {
107
+ securitySchemes: {
108
+ AdminSecurity: { type: 'oauth2', flows: { clientCredentials: {} } },
109
+ },
110
+ },
111
+ };
112
+
113
+ const result = validateSecuritySchemes(apiDefinition, 'corr-3');
114
+ expect(result).to.include('AdminSecurity');
115
+ expect(result).to.include(null);
116
+ });
117
+
118
+ it('should throw for invalid oauth2 type', () => {
119
+ const apiDefinition = {
120
+ components: {
121
+ securitySchemes: {
122
+ AdminSecurity: { type: 'apiKey', flows: { clientCredentials: {} } },
123
+ },
124
+ },
125
+ };
126
+
127
+ expect(() => validateSecuritySchemes(apiDefinition, 'corr-4')).to.throw('auth type is not oauth2');
128
+ });
129
+ });
130
+
131
+ describe('extractProperties', () => {
132
+ it('should handle apiDefinition with no paths', () => {
133
+ const buildDir = path.join(tmpDir, 'build');
134
+
135
+ fsReal.mkdirSync(buildDir);
136
+ const controllersDir = path.join(tmpDir, 'controllers');
137
+
138
+ fsReal.mkdirSync(controllersDir);
139
+
140
+ extractProperties({}, controllersDir, buildDir, 'corr-1');
141
+ const registerFile = path.join(buildDir, 'register.js');
142
+
143
+ expect(fsReal.existsSync(registerFile)).to.equal(true);
144
+ });
145
+
146
+ it('should throw when controllers directory does not exist', () => {
147
+ const buildDir = path.join(tmpDir, 'build');
148
+
149
+ fsReal.mkdirSync(buildDir);
150
+ const controllersDir = path.join(tmpDir, 'nonexistent');
151
+
152
+ expect(() => extractProperties({}, controllersDir, buildDir, 'corr-2'))
153
+ .to.throw('file system error');
154
+ });
155
+
156
+ it('should extract operations from paths and create register file', () => {
157
+ const controllersDir = path.join(tmpDir, 'controllers');
158
+
159
+ fsReal.mkdirSync(controllersDir);
160
+ fsReal.writeFileSync(path.join(controllersDir, 'userCtrl.js'), 'export {\n getUsers,\n createUser,\n};');
161
+ const buildDir = path.join(tmpDir, 'build');
162
+
163
+ fsReal.mkdirSync(buildDir);
164
+ const apiDefinition = {
165
+ paths: {
166
+ '/users': {
167
+ get: {
168
+ 'operationId': 'getUsers',
169
+ 'x-swagger-router-controller': 'userCtrl',
170
+ },
171
+ post: {
172
+ 'operationId': 'createUser',
173
+ 'x-swagger-router-controller': 'userCtrl',
174
+ },
175
+ },
176
+ },
177
+ };
178
+
179
+ extractProperties(apiDefinition, controllersDir, buildDir, 'corr-3');
180
+ const content = fsReal.readFileSync(path.join(buildDir, 'register.js'), 'utf8');
181
+
182
+ expect(content).to.include('getUsers');
183
+ expect(content).to.include('createUser');
184
+ expect(content).to.include('userCtrl');
185
+ });
186
+
187
+ it('should throw when controller file does not exist', () => {
188
+ const controllersDir = path.join(tmpDir, 'controllers');
189
+
190
+ fsReal.mkdirSync(controllersDir);
191
+ const buildDir = path.join(tmpDir, 'build');
192
+
193
+ fsReal.mkdirSync(buildDir);
194
+ const apiDefinition = {
195
+ paths: {
196
+ '/items': {
197
+ get: {
198
+ 'operationId': 'getItems',
199
+ 'x-swagger-router-controller': 'missingCtrl',
200
+ },
201
+ },
202
+ },
203
+ };
204
+
205
+ expect(() => extractProperties(apiDefinition, controllersDir, buildDir, 'corr-4'))
206
+ .to.throw('file system error');
207
+ });
208
+
209
+ it('should throw when operationId is not found in controller file', () => {
210
+ const controllersDir = path.join(tmpDir, 'controllers');
211
+
212
+ fsReal.mkdirSync(controllersDir);
213
+ fsReal.writeFileSync(path.join(controllersDir, 'ctrl.js'), 'export const otherOp = () => {};');
214
+ const buildDir = path.join(tmpDir, 'build');
215
+
216
+ fsReal.mkdirSync(buildDir);
217
+ const apiDefinition = {
218
+ paths: {
219
+ '/test': {
220
+ get: {
221
+ 'operationId': 'missingOp',
222
+ 'x-swagger-router-controller': 'ctrl',
223
+ },
224
+ },
225
+ },
226
+ };
227
+
228
+ try {
229
+ extractProperties(apiDefinition, controllersDir, buildDir, 'corr-5');
230
+ expect.fail('should have thrown');
231
+ }
232
+ catch (err) {
233
+ expect(err.message).to.equal('file system error');
234
+ expect(err.cause.message).to.equal('missing operationId in controller file');
235
+ }
236
+ });
237
+
238
+ it('should throw when x-swagger-router-controller is missing', () => {
239
+ const controllersDir = path.join(tmpDir, 'controllers');
240
+
241
+ fsReal.mkdirSync(controllersDir);
242
+ const buildDir = path.join(tmpDir, 'build');
243
+
244
+ fsReal.mkdirSync(buildDir);
245
+ const apiDefinition = {
246
+ paths: {
247
+ '/test': {
248
+ get: { operationId: 'someOp' },
249
+ },
250
+ },
251
+ };
252
+
253
+ expect(() => extractProperties(apiDefinition, controllersDir, buildDir, 'corr-6'))
254
+ .to.throw('missing property');
255
+ });
256
+
257
+ it('should recognize operationId via export syntax', () => {
258
+ const controllersDir = path.join(tmpDir, 'controllers');
259
+
260
+ fsReal.mkdirSync(controllersDir);
261
+ fsReal.writeFileSync(path.join(controllersDir, 'ctrl.js'), 'export getItems');
262
+ const buildDir = path.join(tmpDir, 'build');
263
+
264
+ fsReal.mkdirSync(buildDir);
265
+ const apiDefinition = {
266
+ paths: {
267
+ '/items': {
268
+ get: {
269
+ 'operationId': 'getItems',
270
+ 'x-swagger-router-controller': 'ctrl',
271
+ },
272
+ },
273
+ },
274
+ };
275
+
276
+ extractProperties(apiDefinition, controllersDir, buildDir, 'corr-7');
277
+ const content = fsReal.readFileSync(path.join(buildDir, 'register.js'), 'utf8');
278
+
279
+ expect(content).to.include('getItems');
280
+ });
281
+ });
282
+ });
@@ -0,0 +1,136 @@
1
+ import esmock from 'esmock';
2
+ import { expect } from 'chai';
3
+
4
+ const getRichError = (type, message, info) => {
5
+ const error = new Error(message);
6
+
7
+ error.type = type;
8
+ error.info = info;
9
+ return error;
10
+ };
11
+
12
+ let validateOauth2;
13
+ let validateApiKey;
14
+
15
+ describe('oauthValidation-helper', () => {
16
+ before(async () => {
17
+ const mod = await esmock('../lib/oauthValidation-helper.js', {
18
+ '@mimik/response-helper': { getRichError },
19
+ });
20
+
21
+ ({ validateOauth2, validateApiKey } = mod);
22
+ });
23
+
24
+ describe('validateOauth2', () => {
25
+ it('should return null when securitySchemes is undefined', () => {
26
+ expect(validateOauth2(undefined, 'SystemSecurity', 'clientCredentials')).to.equal(null);
27
+ });
28
+
29
+ it('should return null when securitySchemes is null', () => {
30
+ expect(validateOauth2(null, 'SystemSecurity', 'clientCredentials')).to.equal(null);
31
+ });
32
+
33
+ it('should return null when the securityType does not exist in schemes', () => {
34
+ const schemes = { OtherSecurity: { type: 'oauth2' } };
35
+
36
+ expect(validateOauth2(schemes, 'SystemSecurity', 'clientCredentials')).to.equal(null);
37
+ });
38
+
39
+ it('should return the securityType when valid oauth2 with correct flow', () => {
40
+ const schemes = {
41
+ SystemSecurity: {
42
+ type: 'oauth2',
43
+ flows: { clientCredentials: { tokenUrl: 'https://example.com' } },
44
+ },
45
+ };
46
+
47
+ expect(validateOauth2(schemes, 'SystemSecurity', 'clientCredentials')).to.equal('SystemSecurity');
48
+ });
49
+
50
+ it('should throw when type is not oauth2', () => {
51
+ const schemes = {
52
+ SystemSecurity: {
53
+ type: 'apiKey',
54
+ flows: { clientCredentials: {} },
55
+ },
56
+ };
57
+
58
+ expect(() => validateOauth2(schemes, 'SystemSecurity', 'clientCredentials'))
59
+ .to.throw('auth type is not oauth2');
60
+ });
61
+
62
+ it('should throw when flows is missing', () => {
63
+ const schemes = {
64
+ SystemSecurity: { type: 'oauth2' },
65
+ };
66
+
67
+ expect(() => validateOauth2(schemes, 'SystemSecurity', 'clientCredentials'))
68
+ .to.throw('no flow type available');
69
+ });
70
+
71
+ it('should throw when the specific flow does not exist', () => {
72
+ const schemes = {
73
+ SystemSecurity: {
74
+ type: 'oauth2',
75
+ flows: { authorizationCode: {} },
76
+ },
77
+ };
78
+
79
+ expect(() => validateOauth2(schemes, 'SystemSecurity', 'clientCredentials'))
80
+ .to.throw('no flow type available');
81
+ });
82
+
83
+ it('should validate implicit flow for UserSecurity', () => {
84
+ const schemes = {
85
+ UserSecurity: {
86
+ type: 'oauth2',
87
+ flows: { implicit: { authorizationUrl: 'https://example.com' } },
88
+ },
89
+ };
90
+
91
+ expect(validateOauth2(schemes, 'UserSecurity', 'implicit')).to.equal('UserSecurity');
92
+ });
93
+ });
94
+
95
+ describe('validateApiKey', () => {
96
+ it('should return null when securitySchemes is undefined', () => {
97
+ expect(validateApiKey(undefined, 'ApiKeySecurity')).to.equal(null);
98
+ });
99
+
100
+ it('should return null when securitySchemes is null', () => {
101
+ expect(validateApiKey(null, 'ApiKeySecurity')).to.equal(null);
102
+ });
103
+
104
+ it('should return null when the securityType does not exist in schemes', () => {
105
+ const schemes = { OtherSecurity: { in: 'header', name: 'apiKey' } };
106
+
107
+ expect(validateApiKey(schemes, 'ApiKeySecurity')).to.equal(null);
108
+ });
109
+
110
+ it('should return the securityType when valid apiKey config', () => {
111
+ const schemes = {
112
+ ApiKeySecurity: { in: 'header', name: 'apiKey' },
113
+ };
114
+
115
+ expect(validateApiKey(schemes, 'ApiKeySecurity')).to.equal('ApiKeySecurity');
116
+ });
117
+
118
+ it('should throw when in is not header', () => {
119
+ const schemes = {
120
+ ApiKeySecurity: { in: 'query', name: 'apiKey' },
121
+ };
122
+
123
+ expect(() => validateApiKey(schemes, 'ApiKeySecurity'))
124
+ .to.throw('apikey security must be in header');
125
+ });
126
+
127
+ it('should throw when name is not apiKey', () => {
128
+ const schemes = {
129
+ ApiKeySecurity: { in: 'header', name: 'x-api-key' },
130
+ };
131
+
132
+ expect(() => validateApiKey(schemes, 'ApiKeySecurity'))
133
+ .to.throw('apikey security must be named apiKey');
134
+ });
135
+ });
136
+ });