@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.
- package/README.md +52 -0
- package/bin/kl.js +2 -0
- package/cli/KumologicaError.js +17 -0
- package/cli/cli.js +7 -0
- package/cli/commands/build-commands/aws.js +49 -0
- package/cli/commands/build-commands/azure.js +43 -0
- package/cli/commands/build-commands/kumohub.js +49 -0
- package/cli/commands/build.js +6 -0
- package/cli/commands/create-commands/create-project-iteratively.js +49 -0
- package/cli/commands/create-commands/index.js +5 -0
- package/cli/commands/create.js +66 -0
- package/cli/commands/deploy-commands/kumohub.js +114 -0
- package/cli/commands/deploy.js +6 -0
- package/cli/commands/doc-commands/html.js +60 -0
- package/cli/commands/doc.js +6 -0
- package/cli/commands/export-commands/cloudformation.js +371 -0
- package/cli/commands/export-commands/serverless.js +164 -0
- package/cli/commands/export-commands/terraform-commands/aws.js +193 -0
- package/cli/commands/export-commands/terraform-commands/azure.js +148 -0
- package/cli/commands/export-commands/terraform.js +6 -0
- package/cli/commands/export-commands/utils/validator.js +195 -0
- package/cli/commands/export.js +6 -0
- package/cli/commands/list-templates.js +24 -0
- package/cli/commands/open.js +53 -0
- package/cli/commands/start.js +165 -0
- package/cli/commands/test/TestSuiteRunner.js +76 -0
- package/cli/commands/test.js +123 -0
- package/cli/utils/download-template-from-repo.js +346 -0
- package/cli/utils/download-test.js +12 -0
- package/cli/utils/download.js +119 -0
- package/cli/utils/fs/copy-dir-contents-sync.js +15 -0
- package/cli/utils/fs/create-zip-file.js +39 -0
- package/cli/utils/fs/dir-exists-sync.js +14 -0
- package/cli/utils/fs/dir-exists.js +17 -0
- package/cli/utils/fs/file-exists-sync.js +14 -0
- package/cli/utils/fs/file-exists.js +12 -0
- package/cli/utils/fs/get-tmp-dir-path.js +22 -0
- package/cli/utils/fs/parse.js +40 -0
- package/cli/utils/fs/read-file-sync.js +11 -0
- package/cli/utils/fs/read-file.js +10 -0
- package/cli/utils/fs/safe-move-file.js +58 -0
- package/cli/utils/fs/walk-dir-sync.js +34 -0
- package/cli/utils/fs/write-file-sync.js +31 -0
- package/cli/utils/fs/write-file.js +32 -0
- package/cli/utils/logger.js +26 -0
- package/cli/utils/rename-service.js +49 -0
- package/package.json +72 -0
- package/src/api/core/comms.js +141 -0
- package/src/api/core/context.js +296 -0
- package/src/api/core/flows.js +286 -0
- package/src/api/core/index.js +29 -0
- package/src/api/core/library.js +106 -0
- package/src/api/core/nodes.js +476 -0
- package/src/api/core/projects.js +426 -0
- package/src/api/core/rest/context.js +42 -0
- package/src/api/core/rest/flow.js +53 -0
- package/src/api/core/rest/flows.js +53 -0
- package/src/api/core/rest/index.js +171 -0
- package/src/api/core/rest/nodes.js +164 -0
- package/src/api/core/rest/util.js +53 -0
- package/src/api/core/settings.js +287 -0
- package/src/api/tools/base/DesignerTool.js +108 -0
- package/src/api/tools/core/flow.js +58 -0
- package/src/api/tools/core/index.js +18 -0
- package/src/api/tools/core/node.js +77 -0
- package/src/api/tools/debugger/index.js +193 -0
- package/src/api/tools/filemanager/index.js +127 -0
- package/src/api/tools/git/index.js +103 -0
- package/src/api/tools/index.js +13 -0
- package/src/api/tools/test/index.js +56 -0
- package/src/api/tools/test/lib/TestCaseRunner.js +105 -0
- package/src/api/tools/test/lib/fixtures/example3-flow.json +148 -0
- package/src/api/tools/test/lib/fixtures/package.json +6 -0
- package/src/api/tools/test/lib/fixtures/s3-event.js +43 -0
- package/src/api/tools/test/lib/reporters/index.js +120 -0
- 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,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
|
+
};
|