@onlineapps/conn-orch-api-mapper 1.0.21 → 1.0.22

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.
@@ -1,624 +0,0 @@
1
- 'use strict';
2
-
3
- const { ApiMapper, create } = require('../../src/index');
4
- const axios = require('axios');
5
-
6
- // Mock axios
7
- jest.mock('axios');
8
-
9
- describe('API Mapper Component Tests @component', () => {
10
- let apiMapper;
11
- let mockOpenApiSpec;
12
- let mockLogger;
13
-
14
- beforeEach(() => {
15
- jest.clearAllMocks();
16
-
17
- // Mock logger
18
- mockLogger = {
19
- info: jest.fn(),
20
- error: jest.fn(),
21
- debug: jest.fn(),
22
- warn: jest.fn()
23
- };
24
-
25
- // Sample OpenAPI spec
26
- mockOpenApiSpec = {
27
- openapi: '3.0.0',
28
- info: {
29
- title: 'Test API',
30
- version: '1.0.0'
31
- },
32
- servers: [
33
- { url: 'http://localhost:3000' }
34
- ],
35
- paths: {
36
- '/users': {
37
- get: {
38
- operationId: 'listUsers',
39
- summary: 'List all users',
40
- parameters: [
41
- {
42
- name: 'limit',
43
- in: 'query',
44
- schema: { type: 'integer' }
45
- },
46
- {
47
- name: 'offset',
48
- in: 'query',
49
- schema: { type: 'integer' }
50
- }
51
- ],
52
- responses: {
53
- '200': {
54
- description: 'User list',
55
- content: {
56
- 'application/json': {
57
- schema: {
58
- type: 'array',
59
- items: { $ref: '#/components/schemas/User' }
60
- }
61
- }
62
- }
63
- }
64
- }
65
- },
66
- post: {
67
- operationId: 'createUser',
68
- summary: 'Create a new user',
69
- requestBody: {
70
- required: true,
71
- content: {
72
- 'application/json': {
73
- schema: { $ref: '#/components/schemas/User' }
74
- }
75
- }
76
- },
77
- responses: {
78
- '201': {
79
- description: 'User created',
80
- content: {
81
- 'application/json': {
82
- schema: { $ref: '#/components/schemas/User' }
83
- }
84
- }
85
- }
86
- }
87
- }
88
- },
89
- '/users/{userId}': {
90
- get: {
91
- operationId: 'getUser',
92
- summary: 'Get user by ID',
93
- parameters: [
94
- {
95
- name: 'userId',
96
- in: 'path',
97
- required: true,
98
- schema: { type: 'string' }
99
- }
100
- ],
101
- responses: {
102
- '200': {
103
- description: 'User details',
104
- content: {
105
- 'application/json': {
106
- schema: { $ref: '#/components/schemas/User' }
107
- }
108
- }
109
- },
110
- '404': {
111
- description: 'User not found'
112
- }
113
- }
114
- },
115
- put: {
116
- operationId: 'updateUser',
117
- summary: 'Update user',
118
- parameters: [
119
- {
120
- name: 'userId',
121
- in: 'path',
122
- required: true,
123
- schema: { type: 'string' }
124
- }
125
- ],
126
- requestBody: {
127
- required: true,
128
- content: {
129
- 'application/json': {
130
- schema: { $ref: '#/components/schemas/User' }
131
- }
132
- }
133
- },
134
- responses: {
135
- '200': {
136
- description: 'User updated'
137
- }
138
- }
139
- },
140
- delete: {
141
- operationId: 'deleteUser',
142
- summary: 'Delete user',
143
- parameters: [
144
- {
145
- name: 'userId',
146
- in: 'path',
147
- required: true,
148
- schema: { type: 'string' }
149
- }
150
- ],
151
- responses: {
152
- '204': {
153
- description: 'User deleted'
154
- }
155
- }
156
- }
157
- },
158
- '/auth/login': {
159
- post: {
160
- operationId: 'login',
161
- summary: 'User login',
162
- requestBody: {
163
- required: true,
164
- content: {
165
- 'application/json': {
166
- schema: {
167
- type: 'object',
168
- properties: {
169
- username: { type: 'string' },
170
- password: { type: 'string' }
171
- }
172
- }
173
- }
174
- }
175
- },
176
- responses: {
177
- '200': {
178
- description: 'Login successful',
179
- content: {
180
- 'application/json': {
181
- schema: {
182
- type: 'object',
183
- properties: {
184
- token: { type: 'string' },
185
- user: { $ref: '#/components/schemas/User' }
186
- }
187
- }
188
- }
189
- }
190
- }
191
- }
192
- }
193
- }
194
- },
195
- components: {
196
- schemas: {
197
- User: {
198
- type: 'object',
199
- properties: {
200
- id: { type: 'string' },
201
- name: { type: 'string' },
202
- email: { type: 'string' },
203
- role: { type: 'string' }
204
- }
205
- }
206
- }
207
- }
208
- };
209
-
210
- apiMapper = new ApiMapper({
211
- openApiSpec: mockOpenApiSpec,
212
- serviceUrl: 'http://test-service:3000',
213
- logger: mockLogger
214
- });
215
- });
216
-
217
- describe('Complete Mapping Flow', () => {
218
- it('should map and execute GET request with query parameters', async () => {
219
- const mockResponse = {
220
- data: [
221
- { id: '1', name: 'John', email: 'john@example.com' },
222
- { id: '2', name: 'Jane', email: 'jane@example.com' }
223
- ],
224
- status: 200,
225
- headers: { 'content-type': 'application/json' }
226
- };
227
- axios.mockResolvedValue(mockResponse);
228
-
229
- const params = {
230
- limit: 10,
231
- offset: 0
232
- };
233
-
234
- const result = await apiMapper.callOperation('listUsers', params);
235
-
236
- expect(axios).toHaveBeenCalledWith({
237
- method: 'GET',
238
- url: 'http://test-service:3000/users',
239
- params: {
240
- limit: 10,
241
- offset: 0
242
- },
243
- headers: {},
244
- data: null
245
- });
246
-
247
- expect(result).toEqual(mockResponse.data);
248
- });
249
-
250
- it('should map and execute POST request with body', async () => {
251
- const mockResponse = {
252
- data: { id: '3', name: 'Bob', email: 'bob@example.com', role: 'user' },
253
- status: 201,
254
- headers: { 'content-type': 'application/json' }
255
- };
256
- axios.mockResolvedValue(mockResponse);
257
-
258
- const params = {
259
- body: {
260
- name: 'Bob',
261
- email: 'bob@example.com',
262
- role: 'user'
263
- }
264
- };
265
-
266
- const result = await apiMapper.callOperation('createUser', params);
267
-
268
- expect(axios).toHaveBeenCalledWith({
269
- method: 'POST',
270
- url: 'http://test-service:3000/users',
271
- params: {},
272
- headers: { 'Content-Type': 'application/json' },
273
- data: params.body
274
- });
275
-
276
- expect(result).toEqual(mockResponse.data);
277
- });
278
-
279
- it('should map and execute request with path parameters', async () => {
280
- const mockResponse = {
281
- data: { id: '123', name: 'Alice', email: 'alice@example.com' },
282
- status: 200
283
- };
284
- axios.mockResolvedValue(mockResponse);
285
-
286
- const params = {
287
- userId: '123'
288
- };
289
-
290
- const result = await apiMapper.callOperation('getUser', params);
291
-
292
- expect(axios).toHaveBeenCalledWith({
293
- method: 'GET',
294
- url: 'http://test-service:3000/users/123',
295
- params: {},
296
- headers: {},
297
- data: null
298
- });
299
-
300
- expect(result).toEqual(mockResponse.data);
301
- });
302
-
303
- it('should handle mixed parameters (path, query, body)', async () => {
304
- // Add a complex endpoint to the spec
305
- apiMapper.operations['complexOperation'] = {
306
- method: 'POST',
307
- path: '/api/v1/organizations/{orgId}/users/{userId}/actions',
308
- parameters: [
309
- { name: 'orgId', in: 'path', required: true },
310
- { name: 'userId', in: 'path', required: true },
311
- { name: 'action', in: 'query' },
312
- { name: 'notify', in: 'query' }
313
- ],
314
- requestBody: {
315
- content: {
316
- 'application/json': {
317
- schema: { type: 'object' }
318
- }
319
- }
320
- }
321
- };
322
-
323
- const mockResponse = { data: { success: true }, status: 200 };
324
- axios.mockResolvedValue(mockResponse);
325
-
326
- const timestamp = Date.now();
327
- const params = {
328
- orgId: 'org-123',
329
- userId: 'user-456',
330
- action: 'activate',
331
- notify: true,
332
- body: { metadata: { timestamp } }
333
- };
334
-
335
- await apiMapper.callOperation('complexOperation', params);
336
-
337
- expect(axios).toHaveBeenCalledWith({
338
- method: 'POST',
339
- url: 'http://test-service:3000/api/v1/organizations/org-123/users/user-456/actions',
340
- params: {
341
- action: 'activate',
342
- notify: true
343
- },
344
- headers: { 'Content-Type': 'application/json' },
345
- data: { metadata: { timestamp } }
346
- });
347
- });
348
- });
349
-
350
- describe('Error Handling', () => {
351
- it('should handle 404 errors appropriately', async () => {
352
- const error = {
353
- response: {
354
- status: 404,
355
- data: { error: 'User not found' }
356
- }
357
- };
358
- axios.mockRejectedValue(error);
359
-
360
- await expect(apiMapper.callOperation('getUser', { userId: 'nonexistent' }))
361
- .rejects.toThrow();
362
-
363
- expect(mockLogger.error).toHaveBeenCalled();
364
- });
365
-
366
- it('should handle network errors', async () => {
367
- const error = new Error('Network error');
368
- error.code = 'ECONNREFUSED';
369
- axios.mockRejectedValue(error);
370
-
371
- await expect(apiMapper.callOperation('listUsers', {}))
372
- .rejects.toThrow('Network error');
373
- });
374
-
375
- it('should handle malformed responses', async () => {
376
- axios.mockResolvedValue({
377
- data: 'Invalid JSON response',
378
- status: 200
379
- });
380
-
381
- const result = await apiMapper.callOperation('listUsers', {});
382
- expect(result).toBe('Invalid JSON response');
383
- });
384
-
385
- it('should throw error for unknown operations', () => {
386
- expect(() => apiMapper.mapOperationToEndpoint('unknownOperation'))
387
- .toThrow('Operation not found: unknownOperation');
388
- });
389
- });
390
-
391
- describe('Request Transformation', () => {
392
- it('should correctly transform parameters based on OpenAPI spec', () => {
393
- const operation = apiMapper.operations['getUser'];
394
- const cookbookParams = {
395
- userId: '123',
396
- extraParam: 'should-be-ignored'
397
- };
398
-
399
- const transformed = apiMapper.transformRequest(cookbookParams, operation.parameters || []);
400
-
401
- expect(transformed.params.userId).toBe('123');
402
- expect(transformed.params.extraParam).toBeUndefined();
403
- });
404
-
405
- it('should handle optional parameters', () => {
406
- const operation = apiMapper.operations['listUsers'];
407
- const cookbookParams = {
408
- limit: 5
409
- // offset is omitted
410
- };
411
-
412
- const transformed = apiMapper.transformRequest(cookbookParams, operation.parameters || []);
413
-
414
- expect(transformed.query.limit).toBe(5);
415
- expect(transformed.query.offset).toBeUndefined();
416
- });
417
-
418
- it('should separate body from other parameters', () => {
419
- const operation = apiMapper.operations['updateUser'];
420
- const cookbookParams = {
421
- userId: '123',
422
- body: {
423
- name: 'Updated Name',
424
- email: 'updated@example.com'
425
- }
426
- };
427
-
428
- const transformed = apiMapper.transformRequest(cookbookParams, operation.parameters || []);
429
-
430
- expect(transformed.params.userId).toBe('123');
431
- expect(transformed.body).toEqual({
432
- name: 'Updated Name',
433
- email: 'updated@example.com'
434
- });
435
- });
436
- });
437
-
438
- describe('Direct Call Mode', () => {
439
- it('should use Express app for direct calls when configured', async () => {
440
- const mockApp = {
441
- _router: {
442
- stack: []
443
- }
444
- };
445
-
446
- const mockReq = {};
447
- const mockRes = {
448
- status: jest.fn().mockReturnThis(),
449
- json: jest.fn(),
450
- send: jest.fn()
451
- };
452
-
453
- const directMapper = new ApiMapper({
454
- openApiSpec: mockOpenApiSpec,
455
- service: mockApp,
456
- directCall: true,
457
- logger: mockLogger
458
- });
459
-
460
- // Mock _callDirectly method
461
- directMapper._callDirectly = jest.fn().mockResolvedValue({
462
- data: { id: '1' },
463
- status: 200
464
- });
465
-
466
- const result = await directMapper.callOperation('getUser', { userId: '1' });
467
-
468
- expect(directMapper._callDirectly).toHaveBeenCalled();
469
- expect(result).toEqual({ id: '1' });
470
- });
471
- });
472
-
473
- describe('OpenAPI Parsing', () => {
474
- it('should parse all operations from OpenAPI spec', () => {
475
- const operations = apiMapper.operations;
476
-
477
- expect(operations).toHaveProperty('listUsers');
478
- expect(operations).toHaveProperty('createUser');
479
- expect(operations).toHaveProperty('getUser');
480
- expect(operations).toHaveProperty('updateUser');
481
- expect(operations).toHaveProperty('deleteUser');
482
- expect(operations).toHaveProperty('login');
483
-
484
- expect(operations.listUsers.method).toBe('GET');
485
- expect(operations.listUsers.path).toBe('/users');
486
- expect(operations.createUser.method).toBe('POST');
487
- expect(operations.createUser.path).toBe('/users');
488
- });
489
-
490
- it('should handle spec without operationId', () => {
491
- const specWithoutOperationId = {
492
- ...mockOpenApiSpec,
493
- paths: {
494
- '/test': {
495
- get: {
496
- summary: 'Test endpoint'
497
- // No operationId
498
- }
499
- }
500
- }
501
- };
502
-
503
- const mapper = new ApiMapper({
504
- openApiSpec: specWithoutOperationId,
505
- logger: mockLogger
506
- });
507
-
508
- // Should generate operationId from method and path
509
- expect(mapper.operations).toHaveProperty('get__test');
510
- });
511
- });
512
-
513
- describe('Response Transformation', () => {
514
- it('should extract data from successful responses', async () => {
515
- const response = {
516
- data: { id: '1', name: 'John' },
517
- status: 200,
518
- headers: { 'content-type': 'application/json' }
519
- };
520
-
521
- const transformed = await apiMapper.transformResponse(response);
522
- expect(transformed).toEqual({ id: '1', name: 'John' });
523
- });
524
-
525
- it('should handle empty responses', async () => {
526
- const response = {
527
- data: null,
528
- status: 204
529
- };
530
-
531
- const transformed = await apiMapper.transformResponse(response);
532
- expect(transformed).toEqual(response);
533
- });
534
-
535
- it('should preserve error responses', async () => {
536
- const response = {
537
- data: { error: 'Bad request', details: 'Invalid email' },
538
- status: 400
539
- };
540
-
541
- const transformed = await apiMapper.transformResponse(response);
542
- expect(transformed).toEqual({ error: 'Bad request', details: 'Invalid email' });
543
- });
544
- });
545
-
546
- describe('Factory Function', () => {
547
- it('should create ApiMapper instance using factory', () => {
548
- const mapper = create({
549
- openApiSpec: mockOpenApiSpec,
550
- serviceUrl: 'http://factory-test:3000'
551
- });
552
-
553
- expect(mapper).toBeInstanceOf(ApiMapper);
554
- expect(mapper.serviceUrl).toBe('http://factory-test:3000');
555
- });
556
-
557
- it('should throw error when spec is missing', () => {
558
- expect(() => create({}))
559
- .toThrow('OpenAPI specification is required');
560
- });
561
- });
562
-
563
- describe('Spec Reloading', () => {
564
- it('should reload OpenAPI spec dynamically', () => {
565
- const newSpec = {
566
- ...mockOpenApiSpec,
567
- paths: {
568
- '/new-endpoint': {
569
- get: {
570
- operationId: 'newOperation',
571
- summary: 'New endpoint'
572
- }
573
- }
574
- }
575
- };
576
-
577
- apiMapper.loadOpenApiSpec(newSpec);
578
-
579
- expect(apiMapper.operations).toHaveProperty('newOperation');
580
- expect(apiMapper.operations.newOperation.path).toBe('/new-endpoint');
581
- });
582
- });
583
-
584
- describe('Variable Resolution', () => {
585
- it('should resolve cookbook variables in parameters', () => {
586
- // Check if resolveVariables method exists
587
- if (!apiMapper._resolveVariables && !apiMapper.resolveVariables) {
588
- // Skip test if method doesn't exist
589
- return;
590
- }
591
- const params = {
592
- userId: '${context.user.id}',
593
- name: '${input.userName}'
594
- };
595
-
596
- const context = {
597
- user: { id: '123' }
598
- };
599
-
600
- const input = {
601
- userName: 'John Doe'
602
- };
603
-
604
- const resolved = apiMapper._resolveVariables ?
605
- apiMapper._resolveVariables(params, { context, input }) :
606
- apiMapper.resolveVariables(params, { context, input });
607
-
608
- expect(resolved.userId).toBe('123');
609
- expect(resolved.name).toBe('John Doe');
610
- });
611
-
612
- it('should handle missing variables gracefully', () => {
613
- const params = {
614
- userId: '${context.user.id}',
615
- name: 'static-value'
616
- };
617
-
618
- const resolved = apiMapper._resolveVariables(params, {});
619
-
620
- expect(resolved.userId).toBeUndefined();
621
- expect(resolved.name).toBe('static-value');
622
- });
623
- });
624
- });