systemview 1.5.2 → 1.6.3

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.
@@ -0,0 +1,25 @@
1
+ const http = require("http");
2
+
3
+ module.exports = async function appIsRunning(appUrls) {
4
+ let allAppsRunning = true;
5
+ const pingApps = appUrls.map(
6
+ (url) =>
7
+ new Promise((resolve, reject) => {
8
+ http.get(url, resolve).on("error", reject);
9
+ })
10
+ );
11
+ try {
12
+ const responses = await Promise.all(pingApps);
13
+ responses.forEach((res, index) => {
14
+ if (res.statusCode !== 200) {
15
+ allAppsRunning = false;
16
+ console.log(`App ${index + 1} is not running`);
17
+ }
18
+ });
19
+ } catch (err) {
20
+ console.error("Error pinging apps:", err.message);
21
+ allAppsRunning = false;
22
+ }
23
+
24
+ return allAppsRunning;
25
+ };
package/cli/index.js CHANGED
@@ -10,58 +10,26 @@
10
10
  const init = require("./utils/init");
11
11
  const cli = require("./utils/cli");
12
12
  const log = require("./utils/log");
13
- const { spawn } = require("child_process");
14
- const path = require("path");
15
13
 
16
- const parentDirectory = path.resolve(__dirname, "..");
14
+ const launchApp = require("./launchApp");
15
+ const startLineReader = require("./startLineReader");
16
+ const runTests = require("./runTests");
17
17
 
18
18
  const input = cli.input;
19
19
  const flags = cli.flags;
20
20
  const { clear, debug } = flags;
21
21
 
22
- function startApp() {
23
- // Start React app
24
- const arg = parseInt();
25
- const port = isNaN(arg) ? 3000 : arg;
26
- const env = Object.create(process.env);
27
- env.PORT = port;
28
- log(`Launching SystemView UI @http://localhost:${port}/`);
29
- const AppProcess = spawn("node", ["server"], {
30
- stdio: ["inherit"],
31
- shell: true,
32
- env,
33
- cwd: parentDirectory,
34
- });
35
-
36
- AppProcess.on("close", (code) => {
37
- console.log(`React app exited with code ${code}`);
38
- });
39
- }
40
-
41
- function startApi() {
42
- log(`Launching SystemView API @http://localhost:${3300}/systemview/api`);
43
-
44
- const AppProcess = spawn("node", ["api"], {
45
- stdio: ["inherit"],
46
- shell: true,
47
- cwd: parentDirectory,
48
- });
49
-
50
- AppProcess.on("close", (code) => {
51
- console.log(`React app exited with code ${code}`);
52
- });
53
- }
54
22
  (async () => {
55
23
  init({ clear });
56
24
  if (input.includes(`help`)) {
57
25
  cli.showHelp(0);
58
- }
59
- if (input[0] === "start") {
60
- startApi();
61
- startApp();
62
26
  } else if (input.includes("test")) {
63
- // Run tests
27
+ if (await launchApp()) await runTests(input[1]);
28
+ } else {
29
+ const shutdown = await launchApp();
30
+ if (typeof shutdown === "function") startLineReader(shutdown);
64
31
  }
65
32
 
33
+ cli.input = [];
66
34
  debug && log(flags);
67
35
  })();
package/cli/launchApp.js CHANGED
@@ -0,0 +1,48 @@
1
+ const log = require("./utils/log");
2
+ const { spawn } = require("child_process");
3
+ const path = require("path");
4
+ const appIsRunning = require("./appIsRunning");
5
+ const parentDirectory = path.resolve(__dirname, "..");
6
+ const delay = (ms) => new Promise((resolve) => setTimeout(resolve, ms));
7
+
8
+ function logConnection(api, app) {
9
+ log("connected!", "success");
10
+ console.log(`SystemView API running @${api}`);
11
+ console.log(`SystemView UI running @${app}`);
12
+ }
13
+ module.exports = async function launchApp(_port) {
14
+ const port = isNaN(_port) ? 3000 : _port;
15
+ const env = Object.create(process.env);
16
+ const api = `http://localhost:${3300}/systemview/api`;
17
+ const app = `http://localhost:${port}/`;
18
+ env.PORT = port;
19
+
20
+ if (await appIsRunning([api, app])) {
21
+ log("SystemView is running from another terminal", "info", "info");
22
+ logConnection(api, app);
23
+ return true;
24
+ } else {
25
+ log("Launching...");
26
+
27
+ const appProcess = spawn("node", ["api & node server"], {
28
+ stdio: ["inherit"],
29
+ shell: true,
30
+ env,
31
+ cwd: parentDirectory,
32
+ });
33
+
34
+ appProcess.on("close", (code) => {
35
+ console.log(`SystemView APP exited with code ${code}`);
36
+ });
37
+ const shutdown = () => {
38
+ if (appProcess) appProcess.kill();
39
+ };
40
+
41
+ await delay(2000);
42
+ const isRunning = await appIsRunning([api, app]);
43
+ if (isRunning) {
44
+ logConnection(api, app);
45
+ return shutdown;
46
+ }
47
+ }
48
+ };
@@ -0,0 +1,99 @@
1
+ const { Client } = require("systemlynx");
2
+ const { initializeSavedTests } = require("../testing-utilities/transformTests");
3
+ const FullTestController = require("../testing-utilities/FullTestController");
4
+ const log = require("./utils/log");
5
+ const validationMessages = require("../testing-utilities/validtionMessages");
6
+ module.exports = async function runTests(project_code) {
7
+ if (!project_code) {
8
+ return log("project_code or service_url are required", "warning", "warning");
9
+ }
10
+ // get connected services from the systemview api
11
+ const connectedServices = await getConnectedServices(project_code);
12
+ //get all the test for each service or the targeted services
13
+ if (!connectedServices.length) {
14
+ return log("No connected services found!", "warning");
15
+ } else {
16
+ connectedServices.forEach(({ serviceId, system }) => {
17
+ log(`connected @${system.connectionData.serviceUrl}`, "success", serviceId);
18
+ });
19
+ }
20
+
21
+ const Tests = await getTests(connectedServices);
22
+ //transform the test from the saved format to the testing format
23
+
24
+ const executeTest = async (savedTests) => {
25
+ const tests = initializeSavedTests(savedTests, connectedServices);
26
+ await runAllTests(tests);
27
+ };
28
+ (
29
+ await new Promise((resolve) => {
30
+ async function recursiveExecuteTest(i = 0) {
31
+ const { serviceId } = connectedServices[i];
32
+ log(`Initializing Tests...`, "info", serviceId);
33
+ if (i === Tests.length) resolve();
34
+ else executeTest(Tests[0]).then(() => recursiveExecuteTest(i + 1));
35
+ }
36
+ recursiveExecuteTest();
37
+ })
38
+ )();
39
+ };
40
+ const Logger = function (trackTime) {
41
+ this.start = (test) => {
42
+ if (trackTime) {
43
+ }
44
+ };
45
+
46
+ this.end = ({ errors }) => {
47
+ errors.forEach((err) => {
48
+ const msg = validationMessages(err);
49
+ log(msg, "error", err.namespace);
50
+ });
51
+ };
52
+ };
53
+ const { runFullTest } = new FullTestController();
54
+ const runAllTests = async (savedTest) => {
55
+ const runTest = async ({ Before, Main, Events, After }) => {
56
+ const fullTest = [Before, Main, Events, After];
57
+ const [B, M, E, A] = await runFullTest(fullTest, new Logger());
58
+ const { title, namespace } = Main[0];
59
+ return { Before: B, Main: M, Events: E, After: A, title, namespace };
60
+ };
61
+
62
+ await new Promise((resolve) => {
63
+ function recursiveRunTest(i = 0) {
64
+ if (i === savedTest.length) resolve();
65
+ else runTest(savedTest[i]).then(() => recursiveRunTest(i + 1));
66
+ }
67
+ recursiveRunTest();
68
+ });
69
+ };
70
+ async function getTests(connectedServices) {
71
+ return await new Promise(async (resolve) => {
72
+ const results = [];
73
+
74
+ return (async function recursiveGetTests(i = 0) {
75
+ if (i < connectedServices.length) {
76
+ const { connectionData } = connectedServices[1].system;
77
+ try {
78
+ results.push(await Client.createService(connectionData).Plugin.getTests());
79
+ } catch (error) {
80
+ console.log(`Failed to retrieve test from:${connectionData.serviceUrl}`);
81
+ }
82
+
83
+ await recursiveGetTests(i + 1);
84
+ } else resolve(results);
85
+ })();
86
+ });
87
+ }
88
+ async function getConnectedServices(project_code) {
89
+ try {
90
+ const { SystemView } = await Client.loadService(
91
+ "http://localhost:3300/systemview/api"
92
+ );
93
+ try {
94
+ return SystemView.getServices(project_code);
95
+ } catch (error) {}
96
+ } catch (error) {
97
+ console.log("Failed to connect to systemview");
98
+ }
99
+ }
@@ -0,0 +1,27 @@
1
+ const runTests = require("./runTests");
2
+ const cli = require("./utils/cli");
3
+
4
+ const readline = require("readline");
5
+
6
+ module.exports = function startLineReader(shutdown) {
7
+ const lineReader = readline.createInterface({
8
+ input: process.stdin,
9
+ output: process.stdout,
10
+ });
11
+ const handleInput = (input = "") => {
12
+ const [command, argument] = input.split(" ").map((s) => s.trim());
13
+ if (["exit", "q"].includes(command)) {
14
+ shutdown();
15
+ lineReader.close();
16
+ } else if (command === "test") {
17
+ runTests(argument);
18
+ } else if (command === "help") {
19
+ cli.showHelp(0);
20
+ }
21
+ lineReader.prompt();
22
+ };
23
+
24
+ lineReader.prompt();
25
+ lineReader.on("line", handleInput);
26
+ lineReader.on("close", shutdown);
27
+ };
package/cli/utils/log.js CHANGED
@@ -1,11 +1,5 @@
1
1
  const alert = require("cli-alerts");
2
2
 
3
- module.exports = (info) => {
4
- alert({
5
- type: `info`,
6
- name: `SystemView`,
7
- msg: ``,
8
- });
9
-
10
- console.log(info);
3
+ module.exports = (msg, type = "info", name = "SystemView") => {
4
+ alert({ type, name, msg });
11
5
  };
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "systemview",
3
3
  "description": "A documentation and testing suite for SystemLynx",
4
- "version": "1.5.2",
4
+ "version": "1.6.3",
5
5
  "license": "UNLICENSED",
6
6
  "bin": {
7
7
  "systemview": "cli/index.js"
@@ -28,6 +28,7 @@
28
28
  "react-router-dom": "^5.2.0",
29
29
  "react-scripts": "^4.0.0",
30
30
  "react-syntax-highlighter": "^15.5.0",
31
+ "readline": "^1.3.0",
31
32
  "remark-gfm": "^3.0.1",
32
33
  "systemlynx": "^1.8.3",
33
34
  "web-vitals": "^0.2.4"
@@ -81,8 +82,8 @@
81
82
  "files": [
82
83
  "cli/",
83
84
  "api/",
84
- "plugin/",
85
85
  "build/",
86
+ "testing-utilities",
86
87
  "server.js"
87
88
  ]
88
89
  }
@@ -0,0 +1,90 @@
1
+ const {
2
+ isTargetValueFn,
3
+ isTargetNamespace,
4
+ targetValueFnRegex,
5
+ obj,
6
+ isEqualArrays,
7
+ isFunction,
8
+ strFn,
9
+ } = require("./test-helpers");
10
+
11
+ function TargetValue(target_namespace, source_map = [], source_index = 0) {
12
+ this.target_namespace = target_namespace;
13
+ this.source_map = source_map;
14
+ this.source_index = source_index;
15
+ }
16
+ function Argument(name, FullTest, input_type = "undefined", input, targetValues = []) {
17
+ this.name = name;
18
+ this.input = input;
19
+ this.input_type = input_type;
20
+ this.data_type = "";
21
+ this.targetValues = targetValues;
22
+
23
+ this.value = () => {
24
+ return this.targetValues.reduce((arg, { source_map, target_namespace: nsp }) => {
25
+ const [value, placeholder, key] = obj(arg).parse(source_map);
26
+
27
+ if (isTargetValueFn(nsp)) {
28
+ placeholder[key] = value
29
+ .trim()
30
+ .replace(nsp, getTargetValue(nsp.substring(3, nsp.length - 1)));
31
+ } else if (isTargetNamespace(nsp)) {
32
+ placeholder[key] = getTargetValue(nsp);
33
+ } else {
34
+ placeholder[key] = strFn(nsp);
35
+ }
36
+
37
+ return arg;
38
+ //creating a deep copy in order to lose refs to original
39
+ }, obj(this).clone()).input;
40
+ };
41
+
42
+ this.parseTargetValues = (input, source_map) => {
43
+ //extract one or more target replacer text from string (i.e. "tv(beforeTest.Action1.error)")
44
+ Array.from(input.matchAll(targetValueFnRegex)).forEach((match) => {
45
+ this.addTargetValue(match[0], source_map, match.index);
46
+ });
47
+ if (isTargetNamespace(input) || isFunction(input))
48
+ this.addTargetValue(input, source_map, 0);
49
+
50
+ return this;
51
+ };
52
+
53
+ this.checkTargetNamespaces = () => {
54
+ // check target namespaces against current input for deletion
55
+ //keep if the target value string still exist on this.input...
56
+ this.targetValues = this.targetValues.filter(
57
+ ({ target_namespace, source_map, source_index }) => {
58
+ const value = obj(this).valueAt(source_map);
59
+ return (
60
+ typeof value === "string" &&
61
+ value.indexOf(target_namespace, source_index) === source_index
62
+ );
63
+ }
64
+ );
65
+ return this;
66
+ };
67
+
68
+ this.addTargetValue = (target_namespace, source_map = [], source_index) => {
69
+ //check to see if target value already exists first
70
+ this.targetValues.findIndex(
71
+ (tv) =>
72
+ tv.target_namespace === target_namespace &&
73
+ isEqualArrays(tv.source_map, source_map) &&
74
+ tv.source_index === source_index
75
+ ) === -1 &&
76
+ this.targetValues.push(new TargetValue(target_namespace, source_map, source_index));
77
+ return this;
78
+ };
79
+
80
+ const getTargetValue = (input) => {
81
+ const [test, action] = input.split(".");
82
+ const nsp = input
83
+ .replace(test, { beforeTest: 0, mainTest: 1, Events: 2, afterTest: 3 }[test])
84
+ .replace(action, parseInt(action.replace("Action", "")) - 1)
85
+ .replace("error", "results");
86
+ return obj(FullTest).valueAtNsp(nsp);
87
+ };
88
+ }
89
+
90
+ module.exports = { Argument, TargetValue, default: Argument };
@@ -0,0 +1,78 @@
1
+ const Test = require("./Test.class");
2
+
3
+ const sections = ["Before", "Main", "Events", "After"];
4
+
5
+ module.exports = function FullTestController({ FullTest, connectedServices } = {}) {
6
+ this.runFullTest = async ([Before, Main, Events, After] = FullTest || [], Logger) => {
7
+ Events.forEach((test) => test.runTest());
8
+
9
+ await new Promise((resolve) => {
10
+ function recursiveRunTest(tests, i = 0) {
11
+ if (i === tests.length) resolve();
12
+ else tests[i].runTest(Logger).then(() => recursiveRunTest(tests, i + 1));
13
+ }
14
+ recursiveRunTest([...Before, ...Main, ...After]);
15
+ });
16
+
17
+ return [Before, Main, Events, After];
18
+ };
19
+
20
+ function validateTest({ title, evaluations, shouldValidate }, section, index) {
21
+ if (!title)
22
+ return {
23
+ message: `${sections[section]}: Action ${index + 1} description is required`,
24
+ error: true,
25
+ };
26
+ if (shouldValidate && !evaluations.filter((e) => e.save).length)
27
+ return {
28
+ message: `${sections[section]}: Action ${index + 1} validations required`,
29
+ error: true,
30
+ };
31
+
32
+ return { error: false };
33
+ }
34
+ this.saveTests = async (Tests = FullTest) => {
35
+ const { title, getConnection, namespace, index } = Tests[1][0];
36
+
37
+ for (let i = 0; i < Tests.length; i++) {
38
+ for (let x = 0; x < Tests.length; x++) {
39
+ const res = Tests[i][x] ? validateTest(Tests[i][x], i, x) : {};
40
+ if (res.error) return res;
41
+ }
42
+ }
43
+
44
+ const { connection } = getConnection(connectedServices);
45
+
46
+ const { Plugin } = connection[namespace.serviceId];
47
+
48
+ if (Plugin) {
49
+ const [Before, Main, Events, After] = Tests.map((testSection) =>
50
+ testSection.map((test) => {
51
+ const { args, evaluations, namespace, title } = test;
52
+ //resetting scope of test
53
+ Object.assign(test, new Test(test));
54
+ return {
55
+ args,
56
+ namespace,
57
+ title,
58
+ savedEvaluations: evaluations
59
+ .filter((e) => e.save)
60
+ .map(({ namespace, expected_type, validations, save, indexed }) => ({
61
+ namespace,
62
+ expected_type,
63
+ validations,
64
+ save,
65
+ indexed,
66
+ })),
67
+ };
68
+ })
69
+ );
70
+
71
+ const testIndex = await Plugin.saveTest(
72
+ { Before, Main, Events, After, title, namespace },
73
+ index
74
+ );
75
+ return { message: "Test Saved!", error: false, testIndex };
76
+ } else return { message: "Plugin Plugin not connected!", error: true };
77
+ };
78
+ };
@@ -0,0 +1,162 @@
1
+ const { validateResults } = require("./validators");
2
+ const { Client } = require("systemlynx");
3
+ const moment = require("moment");
4
+ const { getArrayNamespaces, getLastArrayNamespace, obj } = require("./test-helpers");
5
+
6
+ module.exports = function Test({
7
+ namespace,
8
+ args,
9
+ title,
10
+ shouldValidate = false,
11
+ savedEvaluations = [],
12
+ index,
13
+ editMode = true,
14
+ }) {
15
+ this.index = index;
16
+ this.connection = {};
17
+ this.title = title;
18
+ this.args = args || [];
19
+ this.editMode = editMode;
20
+ this.shouldValidate = shouldValidate || !!savedEvaluations.length;
21
+ this.namespace = namespace || {
22
+ serviceId: "",
23
+ moduleName: "",
24
+ methodName: "",
25
+ };
26
+ this.clearResults = () => {
27
+ this.results = null;
28
+ this.response_type = "";
29
+ this.test_start = null;
30
+ this.test_end = null;
31
+ this.evaluations = [];
32
+ this.savedEvaluations = obj(savedEvaluations).clone();
33
+ this.errors = [];
34
+ return this;
35
+ };
36
+
37
+ this.clearResults();
38
+
39
+ this.getErrors = () => {
40
+ this.errors = this.evaluations
41
+ .filter(({ save }) => save)
42
+ .reduce(
43
+ (sum, { errors, namespace }) =>
44
+ sum.concat(errors.map((e) => ({ ...e, namespace }))),
45
+ []
46
+ );
47
+ return this.errors;
48
+ };
49
+
50
+ this.validate = validateResults.bind(this);
51
+
52
+ this.runTest = async (_logger) => {
53
+ const logger = _logger || new TestLogger(this);
54
+ const { serviceId, moduleName, methodName } = this.namespace;
55
+ const args = this.args.map((arg) => arg.value());
56
+
57
+ this.test_start = moment().toJSON();
58
+ const Module = this.connection[serviceId][moduleName];
59
+ if (methodName === "on") {
60
+ const eventTest = (e) => {
61
+ this.results = e;
62
+ this.test_end = moment().toJSON();
63
+ this.response_type = "event";
64
+ this.shouldValidate && this.validate();
65
+ logger.end(this);
66
+ Module.$clearEvent(args[0], "eventTest");
67
+ };
68
+ logger.start(args);
69
+ Module.on(args[0], eventTest);
70
+ } else {
71
+ try {
72
+ logger.start(args);
73
+ this.results = await Module[methodName](...args);
74
+ this.test_end = moment().toJSON();
75
+ this.response_type = "results";
76
+ this.shouldValidate && this.validate();
77
+ logger.end(this);
78
+ } catch (error) {
79
+ this.test_end = moment().toJSON();
80
+ this.results = error;
81
+ this.response_type = "error";
82
+ this.shouldValidate && this.validate();
83
+ logger.end(this);
84
+ }
85
+ }
86
+ return this;
87
+ };
88
+
89
+ this.getConnection = (connectedServices) => {
90
+ const { serviceId } = this.namespace;
91
+
92
+ if (connectedServices.length > 0) {
93
+ const service = connectedServices.find(
94
+ (service) => service.serviceId === serviceId
95
+ );
96
+ if (!service) {
97
+ console.warn("connection data not found");
98
+ return this;
99
+ }
100
+ const { connectionData } = service.system;
101
+
102
+ this.connection[serviceId] = Client.createService(connectionData);
103
+ }
104
+
105
+ return this;
106
+ };
107
+
108
+ this.addEvaluation = (evaluation) => {
109
+ const savedEval = this.savedEvaluations.find(
110
+ ({ namespace }) => namespace === evaluation.namespace
111
+ );
112
+ if (savedEval) Object.assign(savedEval, evaluation);
113
+ else this.savedEvaluations.push(evaluation);
114
+ };
115
+ this.removeEvaluation = (namespace) => {
116
+ const index = this.savedEvaluations.findIndex((e) => e.namespace === namespace);
117
+ if (index > -1) return this.savedEvaluations.splice(index, 1)[0];
118
+ else return {};
119
+ };
120
+
121
+ this.addSavedIndices = (arrayNamespace, newArrayNamespace) => {
122
+ //break namespace into multiple array namespaces
123
+ const nspList = getArrayNamespaces(arrayNamespace);
124
+ this.evaluations.forEach((e) => {
125
+ if (nspList.includes(getLastArrayNamespace(e.namespace))) {
126
+ e.namespace = e.namespace.replace(arrayNamespace, newArrayNamespace);
127
+ e.indexed = true;
128
+ e.expected_type = undefined;
129
+ this.addEvaluation(e);
130
+ }
131
+ });
132
+ };
133
+ this.removeSavedIndices = (namespace) => {
134
+ this.savedEvaluations = this.savedEvaluations.filter(
135
+ (e) => !e.namespace.includes(namespace) //|| !e.indexed
136
+ );
137
+ };
138
+ };
139
+
140
+ function TestLogger(test) {
141
+ this.start = (args) => {
142
+ const { serviceId, moduleName, methodName } = test.namespace;
143
+
144
+ console.log(
145
+ `[${moment(this.test_start).format(
146
+ "L LTS"
147
+ )}]> [invoking]:${serviceId}.${moduleName}.${methodName}()`
148
+ );
149
+ console.log.apply({}, ["args:"].concat(args));
150
+ };
151
+ this.end = () => {
152
+ const { serviceId, moduleName, methodName } = test.namespace;
153
+ const { results, response_type } = test;
154
+ console.log(
155
+ `[${moment(this.test_end).format(
156
+ "L LTS"
157
+ )}]> [${response_type}]:${serviceId}.${moduleName}.${methodName}()`,
158
+ `${response_type}:`,
159
+ results
160
+ );
161
+ };
162
+ }