@kumologica/sdk 3.3.0-beta9 → 3.4.0-beta2
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/cli/commands/open.js +6 -7
- package/cli/commands/test-commands/TestSuiteRunner.js +75 -0
- package/cli/commands/test-commands/lib/TestCaseRunner.js +105 -0
- package/cli/commands/test-commands/lib/index.js +12 -0
- package/cli/commands/test-commands/lib/reporters/index.js +120 -0
- package/cli/commands/test.js +92 -120
- package/package.json +4 -5
- package/src/app/lib/stores/store.js +15 -1
- package/src/app/lib/stores/user-preference-store.js +2 -0
- package/src/app/lib/stores/workspace-preference-store.js +42 -0
- package/src/app/main-process/editor-manager.js +11 -51
- package/src/app/main-process/main-window.js +10 -0
- package/src/app/main-process/menu.js +4 -2
- package/src/app/main-process/modal-home.js +5 -7
- package/src/app/main-process/modal-newproject.js +4 -6
- package/src/app/main-process/modal-newtab.js +4 -6
- package/src/app/main-process/modal-nodelibrary.js +4 -6
- package/src/app/main-process/modal-welcome.js +5 -8
- package/src/app/main.js +3 -1
- package/src/app/preload.js +28 -4
- package/src/app/ui/editor-api/lib/index.js +6 -9
- package/src/app/ui/editor-client/public/red/red.js +434 -176
- package/src/app/ui/editor-client/public/red/red.min.js +3 -3
- package/src/app/ui/editor-client/public/red/style.min.css +1 -1
- package/src/app/ui/editor-client/public/vendor/vendor.css +21 -1
- package/src/app/ui/editor-client/src/js/red.js +1 -1
- package/src/app/ui/editor-client/src/js/ui/clipboard.js +8 -0
- package/src/app/ui/editor-client/src/js/ui/header.js +2 -40
- package/src/app/ui/editor-client/src/js/ui/palette-explorer.js +328 -0
- package/src/app/ui/editor-client/src/js/ui/palette.js +10 -8
- package/src/app/ui/editor-client/src/js/ui/project-info.js +10 -8
- package/src/app/ui/editor-client/src/js/ui/search.js +147 -44
- package/src/app/ui/editor-client/src/js/ui/ui-settings.js +1 -1
- package/src/app/ui/editor-client/src/js/ui/view.js +2 -5
- package/src/app/ui/editor-client/src/js/validators.js +2 -2
- package/src/app/ui/editor-client/src/sass/dropdownMenu.scss +1 -1
- package/src/app/ui/editor-client/src/sass/editor.scss +1 -0
- package/src/app/ui/editor-client/src/sass/header.scss +16 -7
- package/src/app/ui/editor-client/src/sass/palette.scss +46 -5
- package/src/app/ui/editor-client/src/sass/project-info.scss +4 -3
- package/src/app/ui/editor-client/src/sass/search.scss +49 -21
- package/src/app/ui/editor-client/src/sass/style.scss +1 -0
- package/src/app/ui/editor-client/src/sass/ui/common/editableList.scss +3 -3
- package/src/app/ui/editor-client/src/sass/ui/common/searchBox.scss +1 -2
- package/src/app/ui/editor-client/src/sass/ui-settings.scss +5 -3
- package/src/app/ui/editor-client/src/vendor/jqtree/jqtree.css +21 -1
- package/src/app/ui/editor-client/templates/index.mst +89 -79
- package/src/server/DesignerServer.js +161 -0
- package/src/server/certificate.pem +23 -0
- package/src/server/private-key.pem +28 -0
- package/cli/commands/test-utils/TestSuiteController.js +0 -363
- package/cli/commands/test-utils/TestSuiteController.test.js +0 -171
- package/cli/commands/test-utils/util/output.js +0 -14
- package/cli/commands/test-utils/util/updates/index.js +0 -17
- package/cli/commands/test-utils/util/updates/pkg.js +0 -13
- package/cli/commands/test-utils/util/updates/templates/default-settings.js +0 -209
- package/src/app/ui/editor-client/src/js/ui/palette-navigator.js +0 -144
- /package/cli/commands/{test-utils → test-commands/lib}/fixtures/example3-flow.json +0 -0
- /package/cli/commands/{test-utils → test-commands/lib}/fixtures/package.json +0 -0
- /package/cli/commands/{test-utils → test-commands/lib}/fixtures/s3-event.js +0 -0
package/cli/commands/open.js
CHANGED
|
@@ -1,18 +1,17 @@
|
|
|
1
1
|
const path = require('path');
|
|
2
|
-
const {
|
|
2
|
+
const { spawn } = require("child_process");
|
|
3
3
|
|
|
4
|
+
const { logError } = require('../utils/logger');
|
|
4
5
|
const rootPath = path.join(__dirname, '..', '..',);
|
|
5
6
|
|
|
6
7
|
function startElectron(projectDir) {
|
|
7
8
|
console.debug(`Project directory: ${projectDir}`);
|
|
8
9
|
console.debug(`Root directory: ${rootPath}`);
|
|
9
10
|
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
const e = spawn(
|
|
14
|
-
['.', projectDir],
|
|
15
|
-
{ cwd: rootPath, shell: true });
|
|
11
|
+
let electronPath = process.env["KUMOLOGICA_ELECTRON_PATH"] || path.join(rootPath, "node_modules", ".bin", "electron");
|
|
12
|
+
console.debug(`Electron path: ${electronPath}`);
|
|
13
|
+
|
|
14
|
+
const e = spawn( electronPath, ['.', projectDir], { cwd: rootPath, shell: true } );
|
|
16
15
|
|
|
17
16
|
e.stdout.on("data", data => {
|
|
18
17
|
console.log(data.toString().trim());
|
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
const { performance } = require('perf_hooks');
|
|
2
|
+
const { TestCaseRunner, InMemoryReporter, TerminalReporter } = require('./lib');
|
|
3
|
+
const { logError, logNotice, logInfo, logFatal } = require('../../utils/logger');
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
class TestSuiteRunner {
|
|
7
|
+
constructor(designerServer) {
|
|
8
|
+
this.designerServer = designerServer;
|
|
9
|
+
this.flowServer = this.designerServer.getFlowServer();
|
|
10
|
+
}
|
|
11
|
+
/**
|
|
12
|
+
*
|
|
13
|
+
* @param {*} testcases
|
|
14
|
+
* return an array containing found errors during execution, or undefined if successful
|
|
15
|
+
*/
|
|
16
|
+
async runAll(testcases) {
|
|
17
|
+
logInfo(`TestCases - Executing\n`);
|
|
18
|
+
let testSuiteTimeStart = performance.now();
|
|
19
|
+
|
|
20
|
+
let totalPassedTestCases = 0;
|
|
21
|
+
let totalFailedCases = 0;
|
|
22
|
+
const totalTestCases = testcases.length;
|
|
23
|
+
for (let i = 0; i <= totalTestCases - 1; i++) {
|
|
24
|
+
let success = await this.runTestCase(testcases[i]);
|
|
25
|
+
if (success) {
|
|
26
|
+
totalPassedTestCases = totalPassedTestCases + 1;
|
|
27
|
+
} else {
|
|
28
|
+
totalFailedCases = totalFailedCases + 1;
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
let testSuiteTimeEnd = performance.now();
|
|
32
|
+
let testSuiteExecutionTimeInMs = testSuiteTimeEnd - testSuiteTimeStart;
|
|
33
|
+
this.printSummary(testSuiteExecutionTimeInMs, totalTestCases, totalFailedCases, totalPassedTestCases);
|
|
34
|
+
return 0;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
/**
|
|
38
|
+
*
|
|
39
|
+
* @param {*} testcase
|
|
40
|
+
* @returns true if success, otherwise false
|
|
41
|
+
*/
|
|
42
|
+
async runTestCase(testcase) {
|
|
43
|
+
let testcaseid = testcase.id;
|
|
44
|
+
|
|
45
|
+
const inMemReporter = new InMemoryReporter;
|
|
46
|
+
const terminalReporter = new TerminalReporter;
|
|
47
|
+
|
|
48
|
+
const testcaseRunner = new TestCaseRunner(this.flowServer, testcaseid, [inMemReporter, terminalReporter]);
|
|
49
|
+
try {
|
|
50
|
+
await testcaseRunner.runAsync();
|
|
51
|
+
return !inMemReporter.isFailedStatus()
|
|
52
|
+
} catch (err) {
|
|
53
|
+
console.log(err);
|
|
54
|
+
logFatal(`Unexpected error occurred while running testcase: "${testcase.name}" due to: ${err.message}.`)
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
printSummary(totalExecutionTimeInMs, totalTestCases, totalFailedCases, totalPassedTestCases) {
|
|
59
|
+
logInfo(`TestCases - Completed
|
|
60
|
+
|
|
61
|
+
Final Execution Statistics
|
|
62
|
+
-------------------------------
|
|
63
|
+
Execution Time: ${Math.floor(totalExecutionTimeInMs)} ms
|
|
64
|
+
Total Test Cases: ${totalTestCases}
|
|
65
|
+
Failed: ${totalFailedCases}
|
|
66
|
+
Passed: ${totalPassedTestCases}
|
|
67
|
+
-------------------------------
|
|
68
|
+
`);
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
module.exports = {
|
|
74
|
+
TestSuiteRunner
|
|
75
|
+
}
|
|
@@ -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 && err.response && err.response.statusCode === 404 && err.response.body === '{"errorMessage":"Targeted node not found"}') {
|
|
77
|
+
reject(new Error('TestCaseID not found'));
|
|
78
|
+
} else {
|
|
79
|
+
console.log(`Error found in TestRunner due to`, err.toString());
|
|
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,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
|
+
}
|
package/cli/commands/test.js
CHANGED
|
@@ -1,151 +1,123 @@
|
|
|
1
|
-
const chalk = require('chalk');
|
|
2
|
-
const { Select } = require('enquirer');
|
|
3
|
-
const fs = require('fs');
|
|
4
1
|
const path = require('path');
|
|
2
|
+
const { Select } = require('enquirer');
|
|
5
3
|
const wcmatch = require('wildcard-match');
|
|
6
|
-
|
|
7
|
-
const { AppServer } = require('@kumologica/runtime');
|
|
4
|
+
const { util } = require('@kumologica/runtime');
|
|
8
5
|
const { codegen } = require('@kumologica/builder');
|
|
9
6
|
|
|
10
|
-
const {
|
|
11
|
-
const {
|
|
12
|
-
|
|
13
|
-
const log = console.log;
|
|
14
|
-
const APP_SERVER_PORT = 1990;
|
|
15
|
-
|
|
16
|
-
async function runTest(flowFilePath, testcaseSelected, iterative) {
|
|
17
|
-
log(`\n> Starting runtime on port ${APP_SERVER_PORT}...`);
|
|
18
|
-
|
|
19
|
-
let appServer = new AppServer({
|
|
20
|
-
projectDir: path.dirname(flowFilePath),
|
|
21
|
-
flowFilePath: flowFilePath,
|
|
22
|
-
port: APP_SERVER_PORT,
|
|
23
|
-
serverMode: AppServer.mode.HEADLESS_KUMOLOGICA,
|
|
24
|
-
logLevel: AppServer.logLevel.ERROR,
|
|
25
|
-
editorApi: undefined,
|
|
26
|
-
startupEmitter: undefined
|
|
27
|
-
});
|
|
28
|
-
|
|
29
|
-
try {
|
|
30
|
-
await appServer.start();
|
|
31
|
-
log(`> FlowFile to be tested: ${chalk.bold(path.resolve(flowFilePath))} \n`);
|
|
32
|
-
let testSuiteController = new TestSuiteController(appServer);
|
|
33
|
-
|
|
34
|
-
// If testcase is null, default to universal wildcard
|
|
35
|
-
testcaseSelected = testcaseSelected || "**";
|
|
36
|
-
|
|
37
|
-
// Find out all testcases available on the flow
|
|
38
|
-
let testcasesAvailable = testSuiteController.findTestCasesFromFlow(flowFilePath);
|
|
39
|
-
if (!testcasesAvailable || (testcasesAvailable && testcasesAvailable.length === 0)) {
|
|
40
|
-
logError(`No testcases found on flow file: ${flowFileAbsPath}`);
|
|
41
|
-
process.exit(1);
|
|
42
|
-
};
|
|
43
|
-
let testcaseAvailableNames = testcasesAvailable.map(tc=>tc.name);
|
|
44
|
-
|
|
45
|
-
// Capture the testcase from user on iterative mode
|
|
46
|
-
if (iterative) {
|
|
47
|
-
const prompt = new Select({
|
|
48
|
-
name: 'testcase',
|
|
49
|
-
message: 'What testcase do you want to run?',
|
|
50
|
-
choices: [...testcaseAvailableNames, 'Run all TestCases...']
|
|
51
|
-
});
|
|
52
|
-
await prompt.run()
|
|
53
|
-
.then(tc => {
|
|
54
|
-
if (tc === 'Run all TestCases...'){
|
|
55
|
-
testcaseSelected = "**";
|
|
56
|
-
}else {
|
|
57
|
-
testcaseSelected = tc;
|
|
58
|
-
}
|
|
59
|
-
})
|
|
60
|
-
.catch(err => {
|
|
61
|
-
logError(`Error found while running tests on iterative mode due to: `, err.message);
|
|
62
|
-
process.exit(1);
|
|
63
|
-
});
|
|
64
|
-
}
|
|
7
|
+
const { TestSuiteRunner } = require('./test-commands/TestSuiteRunner');
|
|
8
|
+
const { DesignerServer } = require('../../src/server/DesignerServer');
|
|
9
|
+
const { logError, logNotice, logInfo, logFatal } = require('../utils/logger');
|
|
65
10
|
|
|
66
|
-
|
|
67
|
-
const isMatch = wcmatch(testcaseSelected);
|
|
68
|
-
let testCasesSelected = [];
|
|
69
|
-
testcasesAvailable.forEach(async tc => {
|
|
70
|
-
if (isMatch(tc.name)){
|
|
71
|
-
testCasesSelected.push({ name: tc.name, id: tc.id });
|
|
72
|
-
}
|
|
73
|
-
});
|
|
11
|
+
const isDirectory = util.isDirectorySync;
|
|
74
12
|
|
|
75
|
-
// Execute the testcasesIds if any, otherwise throw an error
|
|
76
|
-
if (testCasesSelected.length === 0){
|
|
77
|
-
logError(`No matched testcases found`);
|
|
78
|
-
process.exit(1);
|
|
79
|
-
} else {
|
|
80
|
-
const errors = await testSuiteController.runTestSuite(testCasesSelected);
|
|
81
|
-
process.exit(errors > 0);
|
|
82
|
-
}
|
|
83
|
-
|
|
84
|
-
} catch (err) {
|
|
85
|
-
log(
|
|
86
|
-
chalk.red(
|
|
87
|
-
`Unexpected error occurred while starting server due to <${err.message}>`
|
|
88
|
-
)
|
|
89
|
-
);
|
|
90
|
-
console.log(err);
|
|
91
|
-
process.exit(1);
|
|
92
|
-
}
|
|
93
|
-
}
|
|
94
|
-
|
|
95
|
-
function isDirectory(dir) {
|
|
96
|
-
try{
|
|
97
|
-
let stats = fs.statSync(dir);
|
|
98
|
-
return stats.isDirectory();
|
|
99
|
-
} catch (err) {
|
|
100
|
-
return undefined;
|
|
101
|
-
}
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
}
|
|
105
13
|
|
|
106
14
|
exports.command = 'test [project_directory]';
|
|
107
15
|
exports.desc = `Run test suite`;
|
|
108
16
|
exports.builder = (yargs) => {
|
|
109
17
|
yargs.positional(`project_directory`, {
|
|
110
|
-
|
|
111
|
-
|
|
18
|
+
type: 'string',
|
|
19
|
+
describe: 'Path to a valid kumologica project directory or flow file. (Optional)'
|
|
112
20
|
})
|
|
113
21
|
yargs.option(`testcase`, {
|
|
114
22
|
describe: "Testcase name to run",
|
|
115
23
|
type: 'string',
|
|
116
24
|
alias: 't',
|
|
117
25
|
nargs: 1
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
26
|
+
});
|
|
27
|
+
yargs.option(`iterative`, {
|
|
28
|
+
describe: "Manually select the testcase to run from all available testcases",
|
|
29
|
+
type: 'boolean',
|
|
30
|
+
alias: 'i'
|
|
31
|
+
});
|
|
124
32
|
}
|
|
125
33
|
exports.handler = ({ project_directory, testcase, iterative }) => {
|
|
126
34
|
let projectDirOrFile = project_directory || process.cwd();
|
|
127
35
|
let projectFlowPath = projectDirOrFile;
|
|
128
36
|
|
|
129
37
|
let isDir = isDirectory(projectDirOrFile);
|
|
130
|
-
if (isDir){
|
|
38
|
+
if (isDir) {
|
|
131
39
|
let flowFileName = codegen.findFlowFile(projectDirOrFile); // returns only the flowname
|
|
132
|
-
if (!flowFileName){
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
}else {
|
|
40
|
+
if (!flowFileName) {
|
|
41
|
+
logFatal(`No flow found in directory: ${projectDirOrFile}`);
|
|
42
|
+
} else {
|
|
136
43
|
projectFlowPath = path.join(projectDirOrFile, flowFileName);
|
|
137
44
|
}
|
|
138
|
-
} else if (isDir === false){
|
|
45
|
+
} else if (isDir === false) {
|
|
139
46
|
// do nothing as it was assumed to be a file
|
|
140
47
|
} else {
|
|
141
|
-
|
|
142
|
-
process.exit(1);
|
|
48
|
+
logFatal(`Directory does not exist: ${project_directory}`);
|
|
143
49
|
}
|
|
144
50
|
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
51
|
+
try {
|
|
52
|
+
runTestOnNewServer(projectFlowPath, testcase, iterative);
|
|
53
|
+
} catch (e) {
|
|
54
|
+
logFatal(e.message);
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
async function runTestOnNewServer(flowFilePath, testcaseSelected, iterative) {
|
|
59
|
+
let designerServer = new DesignerServer(
|
|
60
|
+
flowFilePath,
|
|
61
|
+
false,
|
|
62
|
+
{
|
|
63
|
+
loglevel: "error",
|
|
64
|
+
noadmin: true
|
|
65
|
+
});
|
|
66
|
+
|
|
67
|
+
try {
|
|
68
|
+
await designerServer.listen();
|
|
69
|
+
logInfo(`> Flow file: ${path.resolve(flowFilePath)} \n`);
|
|
70
|
+
let testSuiteRunner = new TestSuiteRunner(designerServer);
|
|
71
|
+
|
|
72
|
+
// If testcase is null, default to universal wildcard
|
|
73
|
+
testcaseSelected = testcaseSelected || "**";
|
|
74
|
+
|
|
75
|
+
// Find out all testcases available on the flow
|
|
76
|
+
let testcasesAvailable = codegen.findTestCasesFromFlow(flowFilePath);
|
|
77
|
+
if (!testcasesAvailable || (testcasesAvailable && testcasesAvailable.length === 0)) {
|
|
78
|
+
logFatal(`No testcases found on flow file: ${flowFileAbsPath}`);
|
|
79
|
+
};
|
|
80
|
+
let testcaseAvailableNames = testcasesAvailable.map(tc => tc.name);
|
|
81
|
+
|
|
82
|
+
// Capture the testcase from user on iterative mode
|
|
83
|
+
if (iterative) {
|
|
84
|
+
const prompt = new Select({
|
|
85
|
+
name: 'testcase',
|
|
86
|
+
message: 'What testcase do you want to run?',
|
|
87
|
+
choices: [...testcaseAvailableNames, 'Run all TestCases...']
|
|
88
|
+
});
|
|
89
|
+
await prompt.run()
|
|
90
|
+
.then(tc => {
|
|
91
|
+
if (tc === 'Run all TestCases...') {
|
|
92
|
+
testcaseSelected = "**";
|
|
93
|
+
} else {
|
|
94
|
+
testcaseSelected = tc;
|
|
95
|
+
}
|
|
96
|
+
})
|
|
97
|
+
.catch(err => {
|
|
98
|
+
logFatal(`Error found while running tests on iterative mode due to: `, err.message);
|
|
99
|
+
});
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
// Filter all testcases to be part of the test suite
|
|
103
|
+
const isMatch = wcmatch(testcaseSelected);
|
|
104
|
+
let testCasesSelected = [];
|
|
105
|
+
testcasesAvailable.forEach(async tc => {
|
|
106
|
+
if (isMatch(tc.name)) {
|
|
107
|
+
testCasesSelected.push({ name: tc.name, id: tc.id });
|
|
108
|
+
}
|
|
109
|
+
});
|
|
110
|
+
|
|
111
|
+
// Execute the testcasesIds if any, otherwise throw an error
|
|
112
|
+
if (testCasesSelected.length === 0) {
|
|
113
|
+
logFatal(`No matched testcases found`);
|
|
114
|
+
} else {
|
|
115
|
+
const errors = await testSuiteRunner.runAll(testCasesSelected);
|
|
116
|
+
process.exit(errors > 0);
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
} catch (err) {
|
|
120
|
+
console.log(err);
|
|
121
|
+
logFatal(`Unexpected error occurred while starting server due to <${err.message}>`);
|
|
122
|
+
}
|
|
151
123
|
}
|
package/package.json
CHANGED
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
"productName": "Kumologica Designer",
|
|
4
4
|
"copyright": "Copyright 2020 Kumologica Pty Ltd, All Rights Reserved.",
|
|
5
5
|
"author": "Kumologica Pty Ltd <contact@kumologica.com>",
|
|
6
|
-
"version": "3.
|
|
6
|
+
"version": "3.4.0-beta2",
|
|
7
7
|
"description": "Kumologica Designer, harnessing Serverless for your cloud integration needs",
|
|
8
8
|
"main": "src/app/main.js",
|
|
9
9
|
"files": [
|
|
@@ -64,9 +64,9 @@
|
|
|
64
64
|
"license": "Proprietary",
|
|
65
65
|
"dependencies": {
|
|
66
66
|
"@electron/remote": "^2.0.8",
|
|
67
|
-
"@kumologica/builder": "3.
|
|
68
|
-
"@kumologica/devkit": "3.
|
|
69
|
-
"@kumologica/runtime": "3.
|
|
67
|
+
"@kumologica/builder": "3.4.0-beta2",
|
|
68
|
+
"@kumologica/devkit": "3.4.0-beta2",
|
|
69
|
+
"@kumologica/runtime": "3.4.0-beta2",
|
|
70
70
|
"adm-zip": "0.4.13",
|
|
71
71
|
"ajv": "8.10.0",
|
|
72
72
|
"archive-type": "^4.0.0",
|
|
@@ -124,7 +124,6 @@
|
|
|
124
124
|
"simplemde": "1.11.2",
|
|
125
125
|
"smoketail": "0.2.2",
|
|
126
126
|
"static-eval": "2.0.2",
|
|
127
|
-
"tcp-port-used": "1.0.2",
|
|
128
127
|
"url": "^0.11.0",
|
|
129
128
|
"util": "0.12.1",
|
|
130
129
|
"whatwg-url": "^11.0.0",
|
|
@@ -44,11 +44,13 @@ class Store {
|
|
|
44
44
|
// ...and this will set it
|
|
45
45
|
set(key, val) {
|
|
46
46
|
this.data[key] = val;
|
|
47
|
+
let value = JSON.stringify(this.data);
|
|
48
|
+
|
|
47
49
|
// Wait, I thought using the node.js' synchronous APIs was bad form?
|
|
48
50
|
// We're not writing a server so there's not nearly the same IO demand on the process
|
|
49
51
|
// Also if we used an async API and our app was quit before the asynchronous write had a chance to complete,
|
|
50
52
|
// we might lose that data. Note that in a real app, we would try/catch this.
|
|
51
|
-
fs.outputFileSync(this.path,
|
|
53
|
+
fs.outputFileSync(this.path, value);
|
|
52
54
|
}
|
|
53
55
|
|
|
54
56
|
trim(key, size) {}
|
|
@@ -78,6 +80,18 @@ class Store {
|
|
|
78
80
|
this.set(key, values);
|
|
79
81
|
}
|
|
80
82
|
}
|
|
83
|
+
remove(key, val) {
|
|
84
|
+
let values = this.get(key) || [];
|
|
85
|
+
if (!Array.isArray(values)) {
|
|
86
|
+
throw new Error(
|
|
87
|
+
`Key: ${key} be an array. File: ${this.configName} may be corrupted`
|
|
88
|
+
);
|
|
89
|
+
}
|
|
90
|
+
if (values.indexOf(val) >= 0) {
|
|
91
|
+
values.splice(values.indexOf(val), 1);
|
|
92
|
+
}
|
|
93
|
+
this.set(key, values);
|
|
94
|
+
}
|
|
81
95
|
}
|
|
82
96
|
|
|
83
97
|
function parseDataFile(filePath, defaults) {
|
|
@@ -4,6 +4,7 @@ const defaultKeys = {
|
|
|
4
4
|
recentProjects: 'recentProjects',
|
|
5
5
|
untitledProject: 'untitledProject'
|
|
6
6
|
};
|
|
7
|
+
|
|
7
8
|
const MAX_NUM_RECENT_PROJECTS = 10;
|
|
8
9
|
|
|
9
10
|
const Store = require('./store');
|
|
@@ -25,6 +26,7 @@ class UserPreferenceStore extends Store {
|
|
|
25
26
|
MAX_NUM_RECENT_PROJECTS
|
|
26
27
|
);
|
|
27
28
|
}
|
|
29
|
+
|
|
28
30
|
getRecentProjects() {
|
|
29
31
|
return super.get(defaultKeys.recentProjects);
|
|
30
32
|
}
|