@kumologica/sdk 3.0.0-alpha4

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 (76) hide show
  1. package/README.md +52 -0
  2. package/bin/kl.js +2 -0
  3. package/cli/KumologicaError.js +17 -0
  4. package/cli/cli.js +7 -0
  5. package/cli/commands/build-commands/aws.js +49 -0
  6. package/cli/commands/build-commands/azure.js +43 -0
  7. package/cli/commands/build-commands/kumohub.js +49 -0
  8. package/cli/commands/build.js +6 -0
  9. package/cli/commands/create-commands/create-project-iteratively.js +49 -0
  10. package/cli/commands/create-commands/index.js +5 -0
  11. package/cli/commands/create.js +66 -0
  12. package/cli/commands/deploy-commands/kumohub.js +114 -0
  13. package/cli/commands/deploy.js +6 -0
  14. package/cli/commands/doc-commands/html.js +60 -0
  15. package/cli/commands/doc.js +6 -0
  16. package/cli/commands/export-commands/cloudformation.js +371 -0
  17. package/cli/commands/export-commands/serverless.js +164 -0
  18. package/cli/commands/export-commands/terraform-commands/aws.js +193 -0
  19. package/cli/commands/export-commands/terraform-commands/azure.js +148 -0
  20. package/cli/commands/export-commands/terraform.js +6 -0
  21. package/cli/commands/export-commands/utils/validator.js +195 -0
  22. package/cli/commands/export.js +6 -0
  23. package/cli/commands/list-templates.js +24 -0
  24. package/cli/commands/open.js +53 -0
  25. package/cli/commands/start.js +165 -0
  26. package/cli/commands/test/TestSuiteRunner.js +76 -0
  27. package/cli/commands/test.js +123 -0
  28. package/cli/utils/download-template-from-repo.js +346 -0
  29. package/cli/utils/download-test.js +12 -0
  30. package/cli/utils/download.js +119 -0
  31. package/cli/utils/fs/copy-dir-contents-sync.js +15 -0
  32. package/cli/utils/fs/create-zip-file.js +39 -0
  33. package/cli/utils/fs/dir-exists-sync.js +14 -0
  34. package/cli/utils/fs/dir-exists.js +17 -0
  35. package/cli/utils/fs/file-exists-sync.js +14 -0
  36. package/cli/utils/fs/file-exists.js +12 -0
  37. package/cli/utils/fs/get-tmp-dir-path.js +22 -0
  38. package/cli/utils/fs/parse.js +40 -0
  39. package/cli/utils/fs/read-file-sync.js +11 -0
  40. package/cli/utils/fs/read-file.js +10 -0
  41. package/cli/utils/fs/safe-move-file.js +58 -0
  42. package/cli/utils/fs/walk-dir-sync.js +34 -0
  43. package/cli/utils/fs/write-file-sync.js +31 -0
  44. package/cli/utils/fs/write-file.js +32 -0
  45. package/cli/utils/logger.js +26 -0
  46. package/cli/utils/rename-service.js +49 -0
  47. package/package.json +72 -0
  48. package/src/api/core/comms.js +141 -0
  49. package/src/api/core/context.js +296 -0
  50. package/src/api/core/flows.js +286 -0
  51. package/src/api/core/index.js +29 -0
  52. package/src/api/core/library.js +106 -0
  53. package/src/api/core/nodes.js +476 -0
  54. package/src/api/core/projects.js +426 -0
  55. package/src/api/core/rest/context.js +42 -0
  56. package/src/api/core/rest/flow.js +53 -0
  57. package/src/api/core/rest/flows.js +53 -0
  58. package/src/api/core/rest/index.js +171 -0
  59. package/src/api/core/rest/nodes.js +164 -0
  60. package/src/api/core/rest/util.js +53 -0
  61. package/src/api/core/settings.js +287 -0
  62. package/src/api/tools/base/DesignerTool.js +108 -0
  63. package/src/api/tools/core/flow.js +58 -0
  64. package/src/api/tools/core/index.js +18 -0
  65. package/src/api/tools/core/node.js +77 -0
  66. package/src/api/tools/debugger/index.js +193 -0
  67. package/src/api/tools/filemanager/index.js +127 -0
  68. package/src/api/tools/git/index.js +103 -0
  69. package/src/api/tools/index.js +13 -0
  70. package/src/api/tools/test/index.js +56 -0
  71. package/src/api/tools/test/lib/TestCaseRunner.js +105 -0
  72. package/src/api/tools/test/lib/fixtures/example3-flow.json +148 -0
  73. package/src/api/tools/test/lib/fixtures/package.json +6 -0
  74. package/src/api/tools/test/lib/fixtures/s3-event.js +43 -0
  75. package/src/api/tools/test/lib/reporters/index.js +120 -0
  76. package/src/server/DesignerServer.js +141 -0
@@ -0,0 +1,13 @@
1
+ const { TestTool } = require('./test');
2
+ const { CoreTools } = require('./core');
3
+ const { DebuggerTool } = require('./debugger');
4
+ const { GitTool } = require('./git');
5
+ const { FileManagerTool } = require('./filemanager');
6
+
7
+ module.exports = {
8
+ CoreTools,
9
+ TestTool,
10
+ DebuggerTool,
11
+ GitTool,
12
+ FileManagerTool
13
+ }
@@ -0,0 +1,56 @@
1
+ const { DesignerTool } = require('../base/DesignerTool');
2
+ const { TestCaseRunner, InMemoryReporter } = require('./lib/TestCaseRunner');
3
+
4
+
5
+ class TestTool extends DesignerTool {
6
+ constructor(designServer) {
7
+ super(designServer);
8
+ }
9
+ install() {
10
+ /**
11
+ * { type: 'test:run',
12
+ * payload: { testcaseid: '123432.34343'} }
13
+ */
14
+ this.addEventHandler('test:run', this.executeTestRunAction('test:run'));
15
+ this.addEventHandler('test:findTestCases', this.executeTestFindTestCasesAction('test:findTestCases'));
16
+ }
17
+ executeTestRunAction(eventType) {
18
+ return async (ws, payload) => {
19
+ const { testcaseid } = payload || {};
20
+ const reporter = new InMemoryReporter();
21
+ const runner = new TestCaseRunner(this.flowServer, testcaseid, [reporter]);
22
+ try {
23
+ await runner.runAsync();
24
+
25
+ let response = {
26
+ testCaseExecutionTimeInMs: reporter.executionTimeInMs,
27
+ testCaseID: reporter.testCaseID,
28
+ testCaseDescription: reporter.testCaseDescription,
29
+ assertions: reporter.assertionResults,
30
+ flowResponse: reporter.flowResponse
31
+ }
32
+ this.sendResponse(ws, eventType, response);
33
+
34
+ } catch (err) {
35
+ this.sendError(ws, eventType, err.message);
36
+ }
37
+
38
+ }
39
+ }
40
+
41
+ executeTestFindTestCasesAction(eventType) {
42
+ return async (ws, payload) => {
43
+ const flowApp = this.flowServer.nodes.getFlows();
44
+ let testNodes = []
45
+ if (flowApp && flowApp.flows && flowApp.flows.length > 0) {
46
+ testNodes = flowApp.flows.filter((node)=> node.type.toLowerCase() === 'httptestcase' || node.type.toLowerCase() ==='testcase')
47
+ }
48
+ this.sendResponse(ws, eventType, testNodes);
49
+ }
50
+ }
51
+ }
52
+
53
+
54
+ module.exports = {
55
+ TestTool
56
+ }
@@ -0,0 +1,105 @@
1
+ const got = require('got');
2
+ const { performance } = require('perf_hooks');
3
+ const { EventEmitter } = require('events').EventEmitter;
4
+
5
+ const { TerminalReporter, InMemoryReporter } = require('./reporters');
6
+
7
+ const TESTCASE_SIGNALS = {
8
+ TESTCASE_STOP: '__testrunner-stop__',
9
+ };
10
+
11
+ class TestCaseRunner {
12
+ constructor(flowServer, testCaseId, testReporters) {
13
+ this.flowServer = flowServer;
14
+ this.testCaseId = testCaseId;
15
+ this.testReporters = testReporters;
16
+
17
+ // Measuring the execution time
18
+ this.timeTestCaseStart = null;
19
+ this.timeTestCaseEnd = null;
20
+
21
+ // Managing runtime events
22
+ this.runtimeEventEmitter = this.flowServer.events;
23
+
24
+ // Signal the start/end of testcase execution
25
+ this.testCaseRunnerEmitter = new EventEmitter();
26
+
27
+ }
28
+
29
+ subscribeRuntimeEvents() {
30
+ let runtimeEventTypes = this.flowServer.eventTypes;
31
+
32
+ this.runtimeEventEmitter.on('runtime-event', async (data) => {
33
+ let p = data.payload;
34
+
35
+ // Attributes from p.testCaseResults
36
+ let testCaseID;
37
+ let testCaseDescription;
38
+ let target; // flow:*
39
+ let targetOutput; // { response: {}, error: {}},
40
+ let assertionResults; // [...]
41
+ let error;
42
+
43
+ switch (data.id) {
44
+ case runtimeEventTypes.TEST_TESTCASE_START:
45
+ this.timeTestCaseStart = performance.now();
46
+ ({ testCaseID, testCaseDescription } = p.testCaseResults);
47
+ this.testReporters.map(reporter => reporter.handleStart(testCaseID, testCaseDescription));
48
+ break;
49
+ case runtimeEventTypes.TEST_TESTCASE_END:
50
+ case runtimeEventTypes.TEST_TESTCASE_END_WITH_ERROR:
51
+ this.timeTestCaseEnd = performance.now();
52
+ ({ testCaseID, testCaseDescription, target, assertionResults, error, targetOutput } = p.testCaseResults); // targetOutput.response: { statusCode, headers, body } }
53
+ this.testReporters.map(reporter => reporter.handleResult(testCaseID, testCaseDescription, target, assertionResults, error, this.timeTestCaseStart, this.timeTestCaseEnd, targetOutput));
54
+ this.testCaseRunnerEmitter.emit(TESTCASE_SIGNALS.TESTCASE_STOP);
55
+ break;
56
+ default:
57
+ // do nothing
58
+ }
59
+
60
+ });
61
+
62
+ }
63
+
64
+ async runAsync() {
65
+ return new Promise(async (resolve, reject) => {
66
+ // Subscribe to the runtime events
67
+ this.subscribeRuntimeEvents();
68
+ // Wait for the end of the testcase execution
69
+ this.testCaseRunnerEmitter.on(TESTCASE_SIGNALS.TESTCASE_STOP, () => {
70
+ resolve();
71
+ });
72
+ try {
73
+ // Trigger the execution of the testcase
74
+ await this.invokeTest(this.testCaseId);
75
+ } catch (err) {
76
+ if (err.response.statusCode === 404 &&
77
+ err.response.body === '{"errorMessage":"Targeted node not found"}') {
78
+ reject(new Error('TestCaseID not found'));
79
+ } else {
80
+ resolve();
81
+ }
82
+ }
83
+
84
+ })
85
+
86
+ }
87
+
88
+ async invokeTest(testCaseId) {
89
+ let resp = await got({
90
+ url: `http://127.0.0.1:${this.flowServer.settings.port}`,
91
+ method: 'POST',
92
+ headers: { 'x-kumologica-testcasenode': `${testCaseId}` },
93
+ });
94
+ return resp;
95
+ }
96
+ }
97
+
98
+
99
+
100
+ module.exports = {
101
+ TestCaseRunner,
102
+ // Reporters
103
+ TerminalReporter,
104
+ InMemoryReporter
105
+ }
@@ -0,0 +1,148 @@
1
+ [
2
+ {
3
+ "id": "main.flow",
4
+ "type": "tab",
5
+ "label": "main",
6
+ "disabled": false,
7
+ "info": ""
8
+ },
9
+ {
10
+ "id": "test.flow",
11
+ "type": "tab",
12
+ "label": "test",
13
+ "disabled": false,
14
+ "info": ""
15
+ },
16
+ {
17
+ "id": "eacbb9ef.69d5c8",
18
+ "type": "EventListener",
19
+ "z": "main.flow",
20
+ "name": "GET /hello",
21
+ "provider": "aws",
22
+ "eventSource": "api",
23
+ "dynamodbOperation": "",
24
+ "apiMethod": "get",
25
+ "apiUrl": "/",
26
+ "albMethod": "any",
27
+ "albUrl": "",
28
+ "bucketName": "",
29
+ "event": "s3:ObjectCreated:*",
30
+ "kapiUrl": "",
31
+ "kcronexpression": "",
32
+ "x": 120,
33
+ "y": 160,
34
+ "wires": [
35
+ [
36
+ "5eb3cd61.894f04"
37
+ ]
38
+ ],
39
+ "caname": "event-handler",
40
+ "category": "general"
41
+ },
42
+ {
43
+ "id": "5eb3cd61.894f04",
44
+ "type": "Logger",
45
+ "z": "main.flow",
46
+ "name": "Log",
47
+ "level": "INFO",
48
+ "message": "Request received",
49
+ "x": 262.5,
50
+ "y": 160,
51
+ "wires": [
52
+ [
53
+ "b5fc6ccd.17dd7"
54
+ ]
55
+ ],
56
+ "caname": "logger",
57
+ "category": "logging"
58
+ },
59
+ {
60
+ "id": "b5fc6ccd.17dd7",
61
+ "type": "EventListener-End",
62
+ "z": "main.flow",
63
+ "name": "Success",
64
+ "statusCode": "500",
65
+ "headers": {
66
+ "Content-Type": "application/json"
67
+ },
68
+ "payload": "{\"hello\": \"world\"}",
69
+ "x": 522.5,
70
+ "y": 160,
71
+ "wires": [],
72
+ "caname": "eventlistenerend",
73
+ "category": "general"
74
+ },
75
+ {
76
+ "id": "b970b7d2.5454c8",
77
+ "type": "Assertion",
78
+ "z": "test.flow",
79
+ "name": "check response",
80
+ "selector": "jsonBody",
81
+ "property": "hello",
82
+ "comparison": "equals",
83
+ "value": "world",
84
+ "x": 265,
85
+ "y": 160,
86
+ "wires": [
87
+ [
88
+ "e0721cc7.653eb"
89
+ ]
90
+ ],
91
+ "caname": "test-assertion",
92
+ "category": "testing"
93
+ },
94
+ {
95
+ "id": "e0721cc7.653eb",
96
+ "type": "TestCaseEnd",
97
+ "z": "test.flow",
98
+ "name": "TestCaseEnd",
99
+ "x": 425,
100
+ "y": 160,
101
+ "wires": [],
102
+ "caname": "test-case-end",
103
+ "category": "testing"
104
+ },
105
+ {
106
+ "id": "e8fb91dc.46c24",
107
+ "type": "HTTPTestCase",
108
+ "z": "test.flow",
109
+ "name": "HTTPTestCase",
110
+ "method": "GET",
111
+ "path": "/",
112
+ "headers": {
113
+ "Accept": "application/json"
114
+ },
115
+ "authtype": "none",
116
+ "secUser": "",
117
+ "secPassword": "",
118
+ "secToken": "",
119
+ "payload": "",
120
+ "x": 122.5,
121
+ "y": 160,
122
+ "wires": [
123
+ [
124
+ "b970b7d2.5454c8"
125
+ ]
126
+ ],
127
+ "caname": "http-test-case",
128
+ "category": "testing"
129
+ },
130
+ {
131
+ "id": "d974781.eca0588",
132
+ "type": "TestCase",
133
+ "z": "test.flow",
134
+ "name": "TestCase",
135
+ "selectedTargetNode": "eacbb9ef.69d5c8",
136
+ "payload": "{\n \"path\": \"/settings\",\n \"headers\": {\n \"Accept\": \"text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,*/*;q=0.8\",\n \"Accept-Encoding\": \"gzip, deflate, lzma, sdch, br\",\n \"Accept-Language\": \"en-US,en;q=0.8\",\n \"CloudFront-Forwarded-Proto\": \"https\",\n \"CloudFront-Is-Desktop-Viewer\": \"true\",\n \"CloudFront-Is-Mobile-Viewer\": \"false\",\n \"CloudFront-Is-SmartTV-Viewer\": \"false\",\n \"CloudFront-Is-Tablet-Viewer\": \"false\",\n \"CloudFront-Viewer-Country\": \"US\",\n \"Host\": \"wt6mne2s9k.execute-api.us-west-2.amazonaws.com\",\n \"Upgrade-Insecure-Requests\": \"1\",\n \"User-Agent\": \"Mozilla/5.0 (Macintosh; Intel Mac OS X 10_11_6) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/52.0.2743.82 Safari/537.36 OPR/39.0.2256.48\",\n \"Via\": \"1.1 fb7cca60f0ecd82ce07790c9c5eef16c.cloudfront.net (CloudFront)\",\n \"X-Amz-Cf-Id\": \"nBsWBOrSHMgnaROZJK1wGCZ9PcRcSpq_oSXZNQwQ10OTZL4cimZo3g==\",\n \"X-Forwarded-For\": \"192.168.100.1, 192.168.1.1\",\n \"X-Forwarded-Port\": \"443\",\n \"X-Forwarded-Proto\": \"https\"\n },\n \"pathParameters\": {\n \"proxy\": \"hello\"\n },\n \"requestContext\": {\n \"accountId\": \"123456789012\",\n \"resourceId\": \"us4z18\",\n \"stage\": \"test\",\n \"requestId\": \"41b45ea3-70b5-11e6-b7bd-69b5aaebc7d9\",\n \"identity\": {\n \"cognitoIdentityPoolId\": \"\",\n \"accountId\": \"\",\n \"cognitoIdentityId\": \"\",\n \"caller\": \"\",\n \"apiKey\": \"\",\n \"sourceIp\": \"192.168.100.1\",\n \"cognitoAuthenticationType\": \"\",\n \"cognitoAuthenticationProvider\": \"\",\n \"userArn\": \"\",\n \"userAgent\": \"Mozilla/5.0 (Macintosh; Intel Mac OS X 10_11_6) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/52.0.2743.82 Safari/537.36 OPR/39.0.2256.48\",\n \"user\": \"\"\n },\n \"resourcePath\": \"/{proxy+}\",\n \"httpMethod\": \"GET\",\n \"apiId\": \"wt6mne2s9k\"\n },\n \"resource\": \"/{proxy+}\",\n \"httpMethod\": \"GET\",\n \"queryStringParameters\": {\n \"name\": \"me\"\n },\n \"stageVariables\": {\n \"stageVarName\": \"stageVarValue\"\n }\n}",
137
+ "initialLoad": "false",
138
+ "x": 122.5,
139
+ "y": 60,
140
+ "wires": [
141
+ [
142
+ "b970b7d2.5454c8"
143
+ ]
144
+ ],
145
+ "caname": "test-case",
146
+ "category": "testing"
147
+ }
148
+ ]
@@ -0,0 +1,6 @@
1
+ {
2
+ "name": "cloudaction-project",
3
+ "description": "A CloudAction Project",
4
+ "version": "0.0.1",
5
+ "private": true
6
+ }
@@ -0,0 +1,43 @@
1
+ const MOCK_S3_EVENT = {
2
+ Records: [
3
+ {
4
+ eventVersion: '2.1',
5
+ eventSource: 'aws:s3',
6
+ awsRegion: 'us-east-2',
7
+ eventTime: '2019-09-03T19:37:27.192Z',
8
+ eventName: 'ObjectCreated:Put',
9
+ userIdentity: {
10
+ principalId: 'AWS:AIDAINPONIXQXHT3IKHL2'
11
+ },
12
+ requestParameters: {
13
+ sourceIPAddress: '205.255.255.255'
14
+ },
15
+ responseElements: {
16
+ 'x-amz-request-id': 'D82B88E5F771F645',
17
+ 'x-amz-id-2':
18
+ 'vlR7PnpV2Ce81l0PRw6jlUpck7Jo5ZsQjryTjKlc5aLWGVHPZLj5NeC6qMa0emYBDXOo6QBU0Wo='
19
+ },
20
+ s3: {
21
+ s3SchemaVersion: '1.0',
22
+ configurationId: '828aa6fc-f7b5-4305-8584-487c791949c1',
23
+ bucket: {
24
+ name: 'lambda-artifacts-deafc19498e3f2df',
25
+ ownerIdentity: {
26
+ principalId: 'A3I5XTEXAMAI3E'
27
+ },
28
+ arn: 'arn:aws:s3:::lambda-artifacts-deafc19498e3f2df'
29
+ },
30
+ object: {
31
+ key: 'b21b84d653bb07b05b1e6b33684dc11b',
32
+ size: 1305107,
33
+ eTag: 'b21b84d653bb07b05b1e6b33684dc11b',
34
+ sequencer: '0C0F6F405D6ED209E1'
35
+ }
36
+ }
37
+ }
38
+ ]
39
+ };
40
+
41
+ module.exports = {
42
+ MOCK_S3_EVENT
43
+ };
@@ -0,0 +1,120 @@
1
+ const chalk = require('chalk');
2
+ const log = console.log;
3
+
4
+ const STATUS_PASSED = 'passed';
5
+ const STATUS_FAILED = 'failed';
6
+
7
+ class TestReporter {
8
+ handleStart(testCaseID, testCaseDescription) { /* TO BE IMPLEMENTED */ }
9
+ handleResult(testCaseID, testCaseDescription, target, assertionResults, error, timeStart, timeEnd, targetOutput) { /* TO BE IMPLEMENTED */ }
10
+ }
11
+
12
+ class InMemoryReporter extends TestReporter {
13
+ handleStart(testCaseID, testCaseDescription) {
14
+ this.testCaseID = testCaseID;
15
+ this.testCaseDescription = testCaseDescription;
16
+ }
17
+ handleResult(testCaseID, testCaseDescription, target, assertionResults, error, timeStart, timeEnd, targetOutput) {
18
+ this.assertionResults = assertionResults.map(({ status, description, diffReported }) => ({
19
+ status,
20
+ description,
21
+ diffReported
22
+ }));
23
+ this.executionTimeInMs = timeEnd - timeStart;
24
+ this.flowResponse = targetOutput.response;
25
+ }
26
+ isFailedStatus() {
27
+ let failedAssertions = this.assertionResults.filter(ar => ar.status === STATUS_FAILED);
28
+ return failedAssertions.length >= 1;
29
+ }
30
+ }
31
+
32
+ class TerminalReporter extends TestReporter {
33
+ handleStart(testCaseID, testCaseDescription) {
34
+ //console.log(`TestCase started: ${testCaseID} - ${testCaseDescription}`);
35
+ }
36
+ handleResult(testCaseID, testCaseDescription, target, assertionResults, error, timeStart, timeEnd, targetOutput) {
37
+ // Print header
38
+ this.printTestCaseHeader(testCaseDescription, target);
39
+
40
+ // Print assertions
41
+ this.printTestAssertionResult(assertionResults || []);
42
+
43
+ // Print status
44
+ if (!error) {
45
+ this.printTestCaseStatus(assertionResults);
46
+ } else {
47
+ this.printTestCaseWithError(testCaseDescription, target, error);
48
+ }
49
+
50
+ }
51
+ printTestCaseHeader(description, target) {
52
+ let prettyName = `${description}`; // mvp: only full flow being targeted > ${target}
53
+ const sep = '=';
54
+ let underline = sep.repeat(prettyName.length);
55
+ let headerLine = `- ${prettyName}\n ${underline}`;
56
+ log(chalk.cyan(headerLine));
57
+ log(` Results:`);
58
+ }
59
+ printTestAssertionResult(assertionResults) {
60
+ // Print Assertions
61
+ if (assertionResults.length === 0) {
62
+ log(chalk.dim(` (No assertions found)`));
63
+ } else {
64
+ assertionResults.map((ar, index) => {
65
+ this.printTestAssertion(ar, index);
66
+ });
67
+ }
68
+ }
69
+ printTestAssertion(ar, index) {
70
+ // console.log('Assertion:', ar)
71
+ let { description, selector, property, comparison, valueExpected, diffReported } = ar;
72
+ let status = diffReported ? STATUS_FAILED : STATUS_PASSED;
73
+ let icon;
74
+ if (status === STATUS_FAILED) {
75
+ icon = chalk.red('\u2716'); // cross mark
76
+ } else {
77
+ icon = chalk.green(`\u2713`); // tick
78
+ }
79
+ // Description
80
+ let descriptionName;
81
+ if (description && description.length > 0) {
82
+ descriptionName = description;
83
+ } else {
84
+ descriptionName = `${selector} - ${property} - ${comparison} - ${valueExpected}`;
85
+ }
86
+ let prettyName = ` ${index}. ${icon} ${descriptionName}`;
87
+ if (diffReported) {
88
+ prettyName = `${prettyName}
89
+ Diff report:\n${chalk.red(JSON.stringify(diffReported, null, 2))}`;
90
+ }
91
+ log(prettyName);
92
+ }
93
+
94
+ printTestCaseStatus(assertionResults) {
95
+ // See if any assertion has failed
96
+ let failedAssertions = assertionResults.filter(ar => ar.status === STATUS_FAILED);
97
+ let failedTestCase = failedAssertions.length >= 1 ? true : false;
98
+ // Print it nicely
99
+ let coloredStatus;
100
+ if (failedTestCase) {
101
+ coloredStatus = chalk.bgRed.white(` Failed `);
102
+ } else {
103
+ coloredStatus = chalk.bgGreen.white(` Passed `);
104
+ }
105
+ log(` Status: ${coloredStatus}\n`);
106
+ }
107
+
108
+ printTestCaseWithError(testCaseDescription, target, error) {
109
+ // Print header
110
+ this.printTestCaseHeader(testCaseDescription, target);
111
+ log(` ERROR: ${error}`);
112
+ log(` Status: ${chalk.bgRed.white(` Failed `)}\n`);
113
+ }
114
+
115
+ }
116
+
117
+ module.exports = {
118
+ TerminalReporter,
119
+ InMemoryReporter
120
+ }
@@ -0,0 +1,141 @@
1
+ const {
2
+ AbstractServerfulServer,
3
+ ConfigBuilder,
4
+ PLATFORMS
5
+ } = require('@kumologica/runtime');
6
+
7
+ const { WebSocket } = require('ws');
8
+ const url = require('url');
9
+ const dotenv = require('dotenv');
10
+ const AdminServerApi = require('../api/core/rest');
11
+ const Tools = require('../api/tools');
12
+
13
+ /**
14
+ * This class will encapsulate the adminApp and FlowApp into two servers.
15
+ * It will be the one running on the development box of the user (either local or remote),
16
+ * and it will serve all the functioanlity to the Kumologica Cloud Editor.
17
+ */
18
+
19
+ class DesignerServer {
20
+ /**
21
+ *
22
+ * @param {string} flow - path of the flowFile. It supports relative and absolute paths. If flow does not exist, the process will be exited.
23
+ * @param {boolean} safe - create an empty shell of a project in the event that the flow file is not created.
24
+ * @param {object} cliParams -
25
+ */
26
+ constructor(flowPath, safe, cliParams) {
27
+ // WebSocket based admin server
28
+ this.wss = null;
29
+ this.subscribers = {}; // collection of all eventTypes, actions attached to the ws admin api
30
+
31
+ if (!flowPath) {
32
+ console.log(
33
+ '[ERROR] Flow is not defined. Make sure you pass the flow file to the constructor of FlowBuilder'
34
+ );
35
+ os.exit(1);
36
+ }
37
+
38
+ this.config = ConfigBuilder.getInstance().buildConfig(
39
+ flowPath,
40
+ PLATFORMS.LOCAL,
41
+ cliParams
42
+ );
43
+
44
+ this.flowServer = new AbstractServerfulServer(this.config);
45
+
46
+ if (this.config.adminEnabled) {
47
+ //this.adminServer = AdminServerApi.init(this.flowServer.runtime._);
48
+ this.installTools();
49
+ }
50
+
51
+
52
+ }
53
+
54
+ installTools() {
55
+ new Tools.CoreTools(this).install();
56
+ new Tools.TestTool(this).install();
57
+ new Tools.DebuggerTool(this).install();
58
+ new Tools.GitTool(this).install();
59
+ new Tools.FileManagerTool(this).install();
60
+ // new AWSTool(this).install();
61
+ }
62
+
63
+ async listen() {
64
+ await this.flowServer.start();
65
+ this.flowServer.log.debug(`Runtime Configuration:${JSON.stringify(this.config, null, 2)}`);
66
+ }
67
+
68
+ async listenAdminServer() {
69
+ if (this.config.adminEnabled) {
70
+ // this.adminServer.listen(this.config.adminPort);
71
+ this.wss = new WebSocket.Server({ port: this.config.adminPort });
72
+ this.wss.on('connection', (ws, req) => {
73
+ // ws://localhost:1990?token=xxxxxxxx
74
+ let token = url.parse(req.url, true).query.token;
75
+ if (token !== 'supersecret') {
76
+ ws.terminate();
77
+ }
78
+ // TODO: Check the security to set up the connection
79
+ // otherwise ws.terminate()
80
+ this.flowServer.log.info(`Client connected to the Admin WebSocket API successfully`);
81
+ ws.on('message', (message) => {
82
+ this.handleMessage(ws, message);
83
+ });
84
+
85
+ ws.on('close', () => {
86
+ console.log('WebSocket client disconnected');
87
+ });
88
+ });
89
+ }
90
+ }
91
+
92
+
93
+ handleMessage(ws, message) {
94
+ try {
95
+ const data = JSON.parse(message.toString('utf8'));
96
+
97
+ if (data.type && this.subscribers[data.type]) {
98
+ this.subscribers[data.type].forEach((subscriber) => {
99
+ subscriber.action(ws, data.payload);
100
+ });
101
+ } else {
102
+ throw new Error('Invalid event format received');
103
+ }
104
+
105
+ } catch (err) {
106
+
107
+ let response = {
108
+ type: 'error',
109
+ payload: {
110
+ reason: err.message
111
+ }
112
+ };
113
+ ws.send(JSON.stringify(response))
114
+ }
115
+ }
116
+
117
+ /**
118
+ * Used by the designer tools to register their ws api actions
119
+ * @param {*} eventType
120
+ * @param {*} action
121
+ */
122
+ registerEvent(eventType, action) {
123
+ if (!this.subscribers[eventType]) {
124
+ this.subscribers[eventType] = [];
125
+ }
126
+ this.subscribers[eventType].push({ action });
127
+ }
128
+
129
+
130
+ getFlowServer() {
131
+ return this.flowServer;
132
+ }
133
+
134
+ getAdminServer() {
135
+ return this.adminServer;
136
+ }
137
+ }
138
+
139
+ module.exports = {
140
+ DesignerServer,
141
+ };