@adobe/aio-cli-plugin-api-mesh 2.1.0 → 2.2.0-beta.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.
Files changed (31) hide show
  1. package/oclif.manifest.json +1 -1
  2. package/package.json +4 -2
  3. package/src/commands/__fixtures__/env_invalid +8 -0
  4. package/src/commands/__fixtures__/env_valid +3 -0
  5. package/src/commands/__fixtures__/files/requestParams.json +3 -0
  6. package/src/commands/__fixtures__/openapi-schema.json +4 -0
  7. package/src/commands/__fixtures__/requestParams.json +3 -0
  8. package/src/commands/__fixtures__/sample_fully_qualified_mesh.json +29 -0
  9. package/src/commands/__fixtures__/sample_invalid_mesh.txt +17 -0
  10. package/src/commands/__fixtures__/sample_mesh_files.json +23 -0
  11. package/src/commands/__fixtures__/sample_mesh_invalid_file_content.json +14 -0
  12. package/src/commands/__fixtures__/sample_mesh_invalid_file_name.json +27 -0
  13. package/src/commands/__fixtures__/sample_mesh_invalid_paths.json +23 -0
  14. package/src/commands/__fixtures__/sample_mesh_invalid_type.json +27 -0
  15. package/src/commands/__fixtures__/sample_mesh_mismatching_path.json +29 -0
  16. package/src/commands/__fixtures__/sample_mesh_outside_workspace_dir.json +23 -0
  17. package/src/commands/__fixtures__/sample_mesh_path_from_home.json +14 -0
  18. package/src/commands/__fixtures__/sample_mesh_subdirectory.json +23 -0
  19. package/src/commands/__fixtures__/sample_mesh_with_files_array.json +29 -0
  20. package/src/commands/__fixtures__/sample_mesh_with_placeholder +17 -0
  21. package/src/commands/api-mesh/__tests__/create.test.js +1202 -1
  22. package/src/commands/api-mesh/__tests__/init.test.js +390 -0
  23. package/src/commands/api-mesh/__tests__/update.test.js +419 -4
  24. package/src/commands/api-mesh/create.js +47 -14
  25. package/src/commands/api-mesh/init.js +168 -0
  26. package/src/commands/api-mesh/update.js +49 -17
  27. package/src/helpers.js +254 -3
  28. package/src/lib/devConsole.js +1 -2
  29. package/src/templates/gitignore +1 -0
  30. package/src/templates/package.json +38 -0
  31. package/src/utils.js +337 -33
@@ -17,6 +17,7 @@ jest.mock('../../../helpers', () => ({
17
17
  initSdk: jest.fn().mockResolvedValue({}),
18
18
  initRequestId: jest.fn().mockResolvedValue({}),
19
19
  promptConfirm: jest.fn().mockResolvedValue(true),
20
+ importFiles: jest.fn().mockResolvedValue(),
20
21
  }));
21
22
  jest.mock('@adobe/aio-cli-lib-console', () => ({
22
23
  init: jest.fn().mockResolvedValue(mockConsoleCLIInstance),
@@ -34,7 +35,7 @@ const selectedWorkspace = { id: '123456789', title: 'Workspace01' };
34
35
  const { readFile } = require('fs/promises');
35
36
 
36
37
  const UpdateCommand = require('../update');
37
- const { initSdk, initRequestId, promptConfirm } = require('../../../helpers');
38
+ const { initSdk, initRequestId, promptConfirm, importFiles } = require('../../../helpers');
38
39
  const { getMeshId, updateMesh } = require('../../../lib/devConsole');
39
40
 
40
41
  let logSpy = null;
@@ -58,7 +59,7 @@ describe('update command tests', () => {
58
59
  logSpy = jest.spyOn(UpdateCommand.prototype, 'log');
59
60
  errorLogSpy = jest.spyOn(UpdateCommand.prototype, 'error');
60
61
 
61
- readFile.mockResolvedValue(true);
62
+ readFile.mockResolvedValue('{}');
62
63
 
63
64
  getMeshId.mockResolvedValue('mesh_id');
64
65
  updateMesh.mockResolvedValue({ status: 'success' });
@@ -74,6 +75,30 @@ describe('update command tests', () => {
74
75
  });
75
76
 
76
77
  test('should pass with valid args', async () => {
78
+ let sampleMesh = {
79
+ meshConfig: {
80
+ sources: [
81
+ {
82
+ name: '<api_name>',
83
+ handler: {
84
+ graphql: {
85
+ endpoint: '<gql_endpoint>',
86
+ },
87
+ },
88
+ },
89
+ ],
90
+ },
91
+ };
92
+ readFile.mockResolvedValue(JSON.stringify(sampleMesh));
93
+
94
+ parseSpy.mockResolvedValueOnce({
95
+ args: { file: 'src/commands/__fixtures__/sample_mesh.json' },
96
+ flags: {
97
+ ignoreCache: mockIgnoreCacheFlag,
98
+ autoConfirmAction: mockAutoApproveAction,
99
+ },
100
+ });
101
+
77
102
  const runResult = await UpdateCommand.run();
78
103
 
79
104
  expect(runResult).toMatchInlineSnapshot(`
@@ -109,8 +134,24 @@ describe('update command tests', () => {
109
134
  });
110
135
 
111
136
  test('should pass with valid args and ignoreCache flag', async () => {
137
+ let sampleMesh = {
138
+ meshConfig: {
139
+ sources: [
140
+ {
141
+ name: '<api_name>',
142
+ handler: {
143
+ graphql: {
144
+ endpoint: '<gql_endpoint>',
145
+ },
146
+ },
147
+ },
148
+ ],
149
+ },
150
+ };
151
+ readFile.mockResolvedValue(JSON.stringify(sampleMesh));
152
+
112
153
  parseSpy.mockResolvedValueOnce({
113
- args: { file: 'valid_file_name' },
154
+ args: { file: 'src/commands/__fixtures__/sample_mesh.json' },
114
155
  flags: {
115
156
  ignoreCache: Promise.resolve(true),
116
157
  autoConfirmAction: mockAutoApproveAction,
@@ -152,8 +193,24 @@ describe('update command tests', () => {
152
193
  });
153
194
 
154
195
  test('should pass with valid args if autoConfirmAction flag is set', async () => {
196
+ let sampleMesh = {
197
+ meshConfig: {
198
+ sources: [
199
+ {
200
+ name: '<api_name>',
201
+ handler: {
202
+ graphql: {
203
+ endpoint: '<gql_endpoint>',
204
+ },
205
+ },
206
+ },
207
+ ],
208
+ },
209
+ };
210
+ readFile.mockResolvedValue(JSON.stringify(sampleMesh));
211
+
155
212
  parseSpy.mockResolvedValueOnce({
156
- args: { file: 'valid_file_name' },
213
+ args: { file: 'src/commands/__fixtures__/sample_mesh.json' },
157
214
  flags: {
158
215
  ignoreCache: mockIgnoreCacheFlag,
159
216
  autoConfirmAction: Promise.resolve(true),
@@ -196,6 +253,30 @@ describe('update command tests', () => {
196
253
  });
197
254
 
198
255
  test('should fail if mesh id is missing', async () => {
256
+ let sampleMesh = {
257
+ meshConfig: {
258
+ sources: [
259
+ {
260
+ name: '<api_name>',
261
+ handler: {
262
+ graphql: {
263
+ endpoint: '<gql_endpoint>',
264
+ },
265
+ },
266
+ },
267
+ ],
268
+ },
269
+ };
270
+ readFile.mockResolvedValue(JSON.stringify(sampleMesh));
271
+
272
+ parseSpy.mockResolvedValueOnce({
273
+ args: { file: 'src/commands/__fixtures__/sample_mesh.json' },
274
+ flags: {
275
+ ignoreCache: mockIgnoreCacheFlag,
276
+ autoConfirmAction: mockAutoApproveAction,
277
+ },
278
+ });
279
+
199
280
  getMeshId.mockResolvedValue(null);
200
281
  const runResult = UpdateCommand.run();
201
282
 
@@ -213,6 +294,30 @@ describe('update command tests', () => {
213
294
  });
214
295
 
215
296
  test('should fail if getMeshId api failed', async () => {
297
+ let sampleMesh = {
298
+ meshConfig: {
299
+ sources: [
300
+ {
301
+ name: '<api_name>',
302
+ handler: {
303
+ graphql: {
304
+ endpoint: '<gql_endpoint>',
305
+ },
306
+ },
307
+ },
308
+ ],
309
+ },
310
+ };
311
+ readFile.mockResolvedValue(JSON.stringify(sampleMesh));
312
+
313
+ parseSpy.mockResolvedValueOnce({
314
+ args: { file: 'src/commands/__fixtures__/sample_mesh.json' },
315
+ flags: {
316
+ ignoreCache: mockIgnoreCacheFlag,
317
+ autoConfirmAction: mockAutoApproveAction,
318
+ },
319
+ });
320
+
216
321
  getMeshId.mockRejectedValue(new Error('getMeshId api failed'));
217
322
  const runResult = UpdateCommand.run();
218
323
 
@@ -230,6 +335,30 @@ describe('update command tests', () => {
230
335
  });
231
336
 
232
337
  test('should fail if updateMesh method failed', async () => {
338
+ let sampleMesh = {
339
+ meshConfig: {
340
+ sources: [
341
+ {
342
+ name: '<api_name>',
343
+ handler: {
344
+ graphql: {
345
+ endpoint: '<gql_endpoint>',
346
+ },
347
+ },
348
+ },
349
+ ],
350
+ },
351
+ };
352
+ readFile.mockResolvedValue(JSON.stringify(sampleMesh));
353
+
354
+ parseSpy.mockResolvedValueOnce({
355
+ args: { file: 'src/commands/__fixtures__/sample_mesh.json' },
356
+ flags: {
357
+ ignoreCache: mockIgnoreCacheFlag,
358
+ autoConfirmAction: mockAutoApproveAction,
359
+ },
360
+ });
361
+
233
362
  updateMesh.mockRejectedValueOnce(new Error('dummy_error'));
234
363
 
235
364
  const runResult = UpdateCommand.run();
@@ -308,6 +437,30 @@ describe('update command tests', () => {
308
437
  });
309
438
 
310
439
  test('should not update if user prompt returns false', async () => {
440
+ let sampleMesh = {
441
+ meshConfig: {
442
+ sources: [
443
+ {
444
+ name: '<api_name>',
445
+ handler: {
446
+ graphql: {
447
+ endpoint: '<gql_endpoint>',
448
+ },
449
+ },
450
+ },
451
+ ],
452
+ },
453
+ };
454
+ readFile.mockResolvedValue(JSON.stringify(sampleMesh));
455
+
456
+ parseSpy.mockResolvedValueOnce({
457
+ args: { file: 'src/commands/__fixtures__/sample_mesh.json' },
458
+ flags: {
459
+ ignoreCache: mockIgnoreCacheFlag,
460
+ autoConfirmAction: mockAutoApproveAction,
461
+ },
462
+ });
463
+
311
464
  promptConfirm.mockResolvedValueOnce(false);
312
465
 
313
466
  const runResult = await UpdateCommand.run();
@@ -322,4 +475,266 @@ describe('update command tests', () => {
322
475
  `);
323
476
  expect(errorLogSpy.mock.calls).toMatchInlineSnapshot(`[]`);
324
477
  });
478
+
479
+ test('should pass if there are local files in meshConfig i.e., the file is appended in files array', async () => {
480
+ let sampleMesh = {
481
+ meshConfig: {
482
+ sources: [
483
+ {
484
+ name: '<json_source_name>',
485
+ handler: {
486
+ JsonSchema: {
487
+ baseUrl: '<json_source__baseurl>',
488
+ operations: [
489
+ {
490
+ type: 'Query',
491
+ field: '<query>',
492
+ path: '<query_path>',
493
+ method: 'POST',
494
+ requestSchema: './requestParams.json',
495
+ },
496
+ ],
497
+ },
498
+ },
499
+ },
500
+ ],
501
+ },
502
+ };
503
+ readFile.mockResolvedValue(JSON.stringify(sampleMesh));
504
+
505
+ let meshConfig = {
506
+ sources: [
507
+ {
508
+ name: '<json_source_name>',
509
+ handler: {
510
+ JsonSchema: {
511
+ baseUrl: '<json_source__baseurl>',
512
+ operations: [
513
+ {
514
+ type: 'Query',
515
+ field: '<query>',
516
+ path: '<query_path>',
517
+ method: 'POST',
518
+ requestSchema: './requestParams.json',
519
+ },
520
+ ],
521
+ },
522
+ },
523
+ },
524
+ ],
525
+ files: [
526
+ {
527
+ path: './requestParams.json',
528
+ content: '{"type":"updatedContent"}',
529
+ },
530
+ ],
531
+ };
532
+
533
+ updateMesh.mockResolvedValue({
534
+ meshId: 'dummy_mesh_id',
535
+ meshConfig: meshConfig,
536
+ });
537
+
538
+ parseSpy.mockResolvedValue({
539
+ args: { file: 'src/commands/__fixtures__/sample_mesh_files.json' },
540
+ flags: {
541
+ autoConfirmAction: mockAutoApproveAction,
542
+ },
543
+ });
544
+
545
+ importFiles.mockResolvedValueOnce({
546
+ meshConfig,
547
+ });
548
+
549
+ const output = await UpdateCommand.run();
550
+
551
+ expect(initRequestId).toHaveBeenCalled();
552
+ expect(updateMesh.mock.calls[0]).toMatchInlineSnapshot(`
553
+ [
554
+ "1234",
555
+ "5678",
556
+ "123456789",
557
+ "mesh_id",
558
+ {
559
+ "meshConfig": {
560
+ "files": [
561
+ {
562
+ "content": "{"type":"updatedContent"}",
563
+ "path": "./requestParams.json",
564
+ },
565
+ ],
566
+ "sources": [
567
+ {
568
+ "handler": {
569
+ "JsonSchema": {
570
+ "baseUrl": "<json_source__baseurl>",
571
+ "operations": [
572
+ {
573
+ "field": "<query>",
574
+ "method": "POST",
575
+ "path": "<query_path>",
576
+ "requestSchema": "./requestParams.json",
577
+ "type": "Query",
578
+ },
579
+ ],
580
+ },
581
+ },
582
+ "name": "<json_source_name>",
583
+ },
584
+ ],
585
+ },
586
+ },
587
+ ]
588
+ `);
589
+
590
+ expect(output).toMatchInlineSnapshot(`
591
+ {
592
+ "meshConfig": {
593
+ "files": [
594
+ {
595
+ "content": "{"type":"updatedContent"}",
596
+ "path": "./requestParams.json",
597
+ },
598
+ ],
599
+ "sources": [
600
+ {
601
+ "handler": {
602
+ "JsonSchema": {
603
+ "baseUrl": "<json_source__baseurl>",
604
+ "operations": [
605
+ {
606
+ "field": "<query>",
607
+ "method": "POST",
608
+ "path": "<query_path>",
609
+ "requestSchema": "./requestParams.json",
610
+ "type": "Query",
611
+ },
612
+ ],
613
+ },
614
+ },
615
+ "name": "<json_source_name>",
616
+ },
617
+ ],
618
+ },
619
+ "meshId": "dummy_mesh_id",
620
+ }
621
+ `);
622
+ });
623
+
624
+ test('should fail if the input mesh config file is inavlid i.e., file name has more than 25 characters', async () => {
625
+ let sampleMesh = {
626
+ meshConfig: {
627
+ sources: [
628
+ {
629
+ name: '<json_source_name>',
630
+ handler: {
631
+ JsonSchema: {
632
+ baseUrl: '<json_source__baseurl>',
633
+ operations: [
634
+ {
635
+ type: 'Query',
636
+ field: '<query>',
637
+ path: '<query_path>',
638
+ method: 'POST',
639
+ requestSchema: './requestJSONParameters.json',
640
+ },
641
+ ],
642
+ },
643
+ },
644
+ },
645
+ ],
646
+ },
647
+ };
648
+ readFile.mockResolvedValue(JSON.stringify(sampleMesh));
649
+
650
+ parseSpy.mockResolvedValue({
651
+ args: { file: 'src/commands/__fixtures__/sample_mesh_invalid_file_name.json' },
652
+ flags: {
653
+ ignoreCache: mockIgnoreCacheFlag,
654
+ autoConfirmAction: mockAutoApproveAction,
655
+ },
656
+ });
657
+
658
+ const output = UpdateCommand.run();
659
+
660
+ await expect(output).rejects.toEqual(new Error('Input mesh config is not valid.'));
661
+
662
+ expect(logSpy.mock.calls).toMatchInlineSnapshot(`
663
+ [
664
+ [
665
+ "Mesh file names must be less than 25 characters. The following file(s) are invalid: requestJSONParameters.json.",
666
+ ],
667
+ ]
668
+ `);
669
+
670
+ expect(errorLogSpy.mock.calls).toMatchInlineSnapshot(`
671
+ [
672
+ [
673
+ "Input mesh config is not valid.",
674
+ ],
675
+ ]
676
+ `);
677
+ });
678
+
679
+ test('should fail if the import files function fails', async () => {
680
+ let sampleMesh = {
681
+ meshConfig: {
682
+ sources: [
683
+ {
684
+ name: '<json_source_name>',
685
+ handler: {
686
+ JsonSchema: {
687
+ baseUrl: '<json_source__baseurl>',
688
+ operations: [
689
+ {
690
+ type: 'Query',
691
+ field: '<query>',
692
+ path: '<query_path>',
693
+ method: 'POST',
694
+ requestSchema: './requestParams.json',
695
+ },
696
+ ],
697
+ },
698
+ },
699
+ },
700
+ ],
701
+ },
702
+ };
703
+ readFile.mockResolvedValue(JSON.stringify(sampleMesh));
704
+
705
+ parseSpy.mockResolvedValue({
706
+ args: { file: 'src/commands/__fixtures__/sample_mesh_files.json' },
707
+ flags: {
708
+ ignoreCache: mockIgnoreCacheFlag,
709
+ autoConfirmAction: mockAutoApproveAction,
710
+ },
711
+ });
712
+
713
+ importFiles.mockImplementation(() => {
714
+ throw new Error('Error reading the file.');
715
+ });
716
+
717
+ const output = UpdateCommand.run();
718
+ await expect(output).rejects.toEqual(
719
+ new Error(
720
+ 'Unable to import the files in the mesh config. Please check the file and try again.',
721
+ ),
722
+ );
723
+
724
+ expect(logSpy.mock.calls).toMatchInlineSnapshot(`
725
+ [
726
+ [
727
+ "Error reading the file.",
728
+ ],
729
+ ]
730
+ `);
731
+
732
+ expect(errorLogSpy.mock.calls).toMatchInlineSnapshot(`
733
+ [
734
+ [
735
+ "Unable to import the files in the mesh config. Please check the file and try again.",
736
+ ],
737
+ ]
738
+ `);
739
+ });
325
740
  });
@@ -10,20 +10,26 @@ governing permissions and limitations under the License.
10
10
  */
11
11
 
12
12
  const { Command } = require('@oclif/core');
13
- const { readFile } = require('fs/promises');
14
13
 
15
- const { initSdk, initRequestId, promptConfirm } = require('../../helpers');
14
+ const { initSdk, initRequestId, promptConfirm, importFiles } = require('../../helpers');
16
15
  const logger = require('../../classes/logger');
17
16
  const CONSTANTS = require('../../constants');
18
- const { ignoreCacheFlag, autoConfirmActionFlag, jsonFlag } = require('../../utils');
17
+ const {
18
+ ignoreCacheFlag,
19
+ autoConfirmActionFlag,
20
+ jsonFlag,
21
+ getFilesInMeshConfig,
22
+ envFileFlag,
23
+ checkPlaceholders,
24
+ readFileContents,
25
+ validateAndInterpolateMesh,
26
+ } = require('../../utils');
19
27
  const {
20
28
  createMesh,
21
29
  createAPIMeshCredentials,
22
30
  subscribeCredentialToMeshService,
23
31
  } = require('../../lib/devConsole');
24
32
 
25
- require('dotenv').config();
26
-
27
33
  const { MULTITENANT_GRAPHQL_SERVER_BASE_URL } = CONSTANTS;
28
34
 
29
35
  class CreateCommand extends Command {
@@ -32,6 +38,7 @@ class CreateCommand extends Command {
32
38
  ignoreCache: ignoreCacheFlag,
33
39
  autoConfirmAction: autoConfirmActionFlag,
34
40
  json: jsonFlag,
41
+ env: envFileFlag,
35
42
  };
36
43
 
37
44
  static enableJsonFlag = true;
@@ -51,22 +58,47 @@ class CreateCommand extends Command {
51
58
 
52
59
  const ignoreCache = await flags.ignoreCache;
53
60
  const autoConfirmAction = await flags.autoConfirmAction;
54
-
61
+ const envFilePath = await flags.env;
55
62
  const { imsOrgId, projectId, workspaceId } = await initSdk({
56
63
  ignoreCache,
57
64
  });
58
65
 
66
+ //Input the mesh data from the input file
67
+ let inputMeshData = await readFileContents(args.file, this, 'mesh');
68
+
59
69
  let data;
60
70
 
71
+ if (checkPlaceholders(inputMeshData)) {
72
+ this.log('The provided mesh contains placeholders. Starting mesh interpolation process.');
73
+ data = await validateAndInterpolateMesh(inputMeshData, envFilePath, this);
74
+ } else {
75
+ try {
76
+ data = JSON.parse(inputMeshData);
77
+ } catch (err) {
78
+ this.log(err.message);
79
+ this.error('Input mesh file is not a valid JSON. Please check the file provided.');
80
+ }
81
+ }
82
+
83
+ let filesList = [];
84
+
61
85
  try {
62
- data = JSON.parse(await readFile(args.file, 'utf8'));
63
- } catch (error) {
64
- logger.error(error);
65
-
66
- this.log(error.message);
67
- this.error(
68
- 'Unable to read the mesh configuration file provided. Please check the file and try again.',
69
- );
86
+ filesList = getFilesInMeshConfig(data, args.file);
87
+ } catch (err) {
88
+ this.log(err.message);
89
+ this.error('Input mesh config is not valid.');
90
+ }
91
+
92
+ // if local files are present, import them in files array in meshConfig
93
+ if (filesList.length) {
94
+ try {
95
+ data = await importFiles(data, filesList, args.file, flags.autoConfirmAction);
96
+ } catch (err) {
97
+ this.log(err.message);
98
+ this.error(
99
+ 'Unable to import the files in the mesh config. Please check the file and try again.',
100
+ );
101
+ }
70
102
  }
71
103
 
72
104
  let shouldContinue = true;
@@ -78,6 +110,7 @@ class CreateCommand extends Command {
78
110
  if (shouldContinue) {
79
111
  try {
80
112
  const mesh = await createMesh(imsOrgId, projectId, workspaceId, data);
113
+
81
114
  let sdkList = [];
82
115
 
83
116
  if (mesh) {