systemview 1.19.0 → 1.20.0

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/index.js CHANGED
@@ -2,18 +2,23 @@
2
2
 
3
3
  process.env.NODE_TLS_REJECT_UNAUTHORIZED = "0";
4
4
 
5
+ const fs = require("fs");
6
+ const path = require("path");
5
7
  const { input, flags, showHelp } = require("./utils/cli");
6
8
  const init = require("./utils/init");
7
9
  const log = require("./logger");
8
10
  const launchApp = require("./launchApp");
9
11
  const runTests = require("./runTests");
12
+ const listTests = require("./listTests");
10
13
  const appIsRunning = require("./appIsRunning");
11
14
  const openBrowser = require("./openBrowser");
12
15
  const connectService = require("./connectService");
13
16
  const probe = require("./probe");
14
17
  const { HttpClient } = require("systemlynx");
18
+ const { createCookieClient } = require("./cookieClient");
15
19
 
16
20
  const DEFAULT_PORT = 3000;
21
+ const VERSION = require("../package.json").version;
17
22
 
18
23
  async function startApp() {
19
24
  const port = isNaN(input[1]) ? DEFAULT_PORT : Number(input[1]);
@@ -35,6 +40,11 @@ async function startTest() {
35
40
  verbose: flags.verbose,
36
41
  manifest: flags.manifest,
37
42
  headers: flags.headers,
43
+ bail: flags.bail,
44
+ dryRun: flags.dryRun,
45
+ phase: flags.phase,
46
+ index: flags.index,
47
+ skip: flags.skip,
38
48
  });
39
49
  process.exit(exitCode);
40
50
  } catch (error) {
@@ -43,6 +53,18 @@ async function startTest() {
43
53
  }
44
54
  }
45
55
 
56
+ async function list() {
57
+ const project_code = input[1];
58
+ const namespace = input[2];
59
+ const url = `http://localhost:${DEFAULT_PORT}`;
60
+ const api = `${url}/systemview/api`;
61
+ if (!(await appIsRunning(api))) {
62
+ await launchApp(DEFAULT_PORT);
63
+ }
64
+ await listTests(url, project_code, namespace, { manifest: flags.manifest, verbose: flags.verbose });
65
+ process.exit(0);
66
+ }
67
+
46
68
  async function open() {
47
69
  const project_code = input[1];
48
70
  const namespace = input[2];
@@ -51,7 +73,24 @@ async function open() {
51
73
  if (!(await appIsRunning(api))) {
52
74
  await launchApp(DEFAULT_PORT);
53
75
  }
54
- openBrowser(ui, project_code, namespace);
76
+
77
+ let connectedServices = [];
78
+ if (namespace && project_code) {
79
+ const manifestFile = flags.manifest || path.join(process.cwd(), "systemview.manifest.json");
80
+ if (fs.existsSync(manifestFile)) {
81
+ try {
82
+ const raw = JSON.parse(fs.readFileSync(manifestFile, "utf8"));
83
+ connectedServices = raw.services ? raw.services : [raw];
84
+ } catch {}
85
+ } else {
86
+ try {
87
+ const { SystemView } = await createCookieClient().loadService(api);
88
+ connectedServices = (await SystemView.getServices(project_code)) || [];
89
+ } catch {}
90
+ }
91
+ }
92
+
93
+ openBrowser(ui, project_code, namespace, connectedServices);
55
94
  process.exit(0);
56
95
  }
57
96
 
@@ -85,8 +124,13 @@ async function quitApp() {
85
124
  init();
86
125
  const command = input[0];
87
126
 
88
- if (command === "open") {
127
+ if (process.argv.includes("--version")) {
128
+ console.log(VERSION);
129
+ process.exit(0);
130
+ } else if (command === "open") {
89
131
  await open();
132
+ } else if (command === "list") {
133
+ await list();
90
134
  } else if (command === "help" || input.includes("help")) {
91
135
  showHelp();
92
136
  } else if (command === "test") {
@@ -97,7 +141,11 @@ async function quitApp() {
97
141
  await connectService(input[1], input[2], { manifest: flags.manifest });
98
142
  process.exit(0);
99
143
  } else if (command === "probe") {
100
- const exitCode = await probe(input[1], input[2], { json: flags.json, manifest: flags.manifest, headers: flags.headers });
144
+ const exitCode = await probe(input[1], input[2], {
145
+ json: flags.json,
146
+ manifest: flags.manifest,
147
+ headers: flags.headers,
148
+ });
101
149
  process.exit(exitCode || 0);
102
150
  } else {
103
151
  await startApp();
@@ -0,0 +1,162 @@
1
+ const fs = require("fs");
2
+ const path = require("path");
3
+ const { createCookieClient } = require("./cookieClient");
4
+ const log = require("./logger");
5
+ const chalk = require("chalk");
6
+
7
+ module.exports = async function listTests(url, project_code, namespace, { manifest: manifestPath, verbose = false } = {}) {
8
+ const api = `${url}/systemview/api`;
9
+ const Client = createCookieClient();
10
+
11
+ if (!project_code) {
12
+ await listAllProjects(api, Client, verbose);
13
+ return;
14
+ }
15
+
16
+ const connectedServices = await loadServices(api, project_code, manifestPath, Client);
17
+ if (!connectedServices.length) {
18
+ log.warn("No connected services found for project: " + project_code);
19
+ return;
20
+ }
21
+
22
+ console.log("");
23
+ console.log(` ${chalk.bold(project_code)}`);
24
+
25
+ for (let si = 0; si < connectedServices.length; si++) {
26
+ const { serviceId, system } = connectedServices[si];
27
+ const isLastService = si === connectedServices.length - 1;
28
+ const servicePrefix = isLastService ? " └── " : " ├── ";
29
+ const childPad = isLastService ? " " : " │ ";
30
+
31
+ let testList = [];
32
+ try {
33
+ const svc = createCookieClient().createService(system.connectionData);
34
+ testList = await svc.Plugin.getTests();
35
+ } catch {
36
+ console.log(`${servicePrefix}${chalk.dim(serviceId)} ${chalk.dim("(could not load tests)")}`);
37
+ continue;
38
+ }
39
+
40
+ const filtered = namespace
41
+ ? testList.filter(({ namespace: n }) =>
42
+ `${n.serviceId}.${n.moduleName}.${n.methodName}`.includes(namespace)
43
+ )
44
+ : testList;
45
+
46
+ if (!filtered.length) {
47
+ console.log(`${servicePrefix}${chalk.dim(serviceId)} ${chalk.dim("(no matching tests)")}`);
48
+ continue;
49
+ }
50
+
51
+ const tree = {};
52
+ filtered.forEach(({ namespace: n, title }) => {
53
+ if (!tree[n.moduleName]) tree[n.moduleName] = {};
54
+ if (verbose) {
55
+ if (!tree[n.moduleName][n.methodName]) tree[n.moduleName][n.methodName] = [];
56
+ tree[n.moduleName][n.methodName].push(title);
57
+ } else {
58
+ tree[n.moduleName][n.methodName] = (tree[n.moduleName][n.methodName] || 0) + 1;
59
+ }
60
+ });
61
+
62
+ console.log(`${servicePrefix}${chalk.bold(serviceId)}`);
63
+
64
+ const modules = Object.keys(tree);
65
+ modules.forEach((moduleName, mi) => {
66
+ const isLastModule = mi === modules.length - 1;
67
+ const modPrefix = childPad + (isLastModule ? "└── " : "├── ");
68
+ const modPad = childPad + (isLastModule ? " " : "│ ");
69
+ console.log(`${modPrefix}${moduleName}`);
70
+
71
+ const methods = Object.keys(tree[moduleName]);
72
+ methods.forEach((methodName, mei) => {
73
+ const isLastMethod = mei === methods.length - 1;
74
+ const methodPrefix = modPad + (isLastMethod ? "└── " : "├── ");
75
+ const methodPad = modPad + (isLastMethod ? " " : "│ ");
76
+ if (verbose) {
77
+ const titles = tree[moduleName][methodName];
78
+ console.log(`${methodPrefix}${chalk.cyan(methodName)}`);
79
+ titles.forEach((title, ti) => {
80
+ const isLastTitle = ti === titles.length - 1;
81
+ const titlePrefix = methodPad + (isLastTitle ? "└── " : "├── ");
82
+ console.log(`${titlePrefix}${chalk.dim(title)}`);
83
+ });
84
+ } else {
85
+ const count = tree[moduleName][methodName];
86
+ const countStr = chalk.dim(`${count} ${count === 1 ? "test" : "tests"}`);
87
+ console.log(`${methodPrefix}${chalk.cyan(methodName)} ${countStr}`);
88
+ }
89
+ });
90
+ });
91
+ }
92
+
93
+ console.log("");
94
+ };
95
+
96
+ async function listAllProjects(api, Client, verbose = false) {
97
+ let projects;
98
+ try {
99
+ const { SystemView } = await Client.loadService(api);
100
+ projects = await SystemView.getProjects();
101
+ } catch {
102
+ log.error("Could not connect to SystemView. Is it running?");
103
+ return;
104
+ }
105
+
106
+ const codes = Object.keys(projects);
107
+ if (!codes.length) {
108
+ log.warn("No connected projects found.");
109
+ return;
110
+ }
111
+
112
+ const total = codes.reduce((n, c) => n + projects[c].length, 0);
113
+ console.log("");
114
+ console.log(chalk.dim(` ${codes.length} ${codes.length === 1 ? "project" : "projects"}, ${total} ${total === 1 ? "service" : "services"}`));
115
+ console.log("");
116
+ codes.forEach((projectCode, pi) => {
117
+ console.log(` ${chalk.bold(projectCode)}`);
118
+ const services = projects[projectCode];
119
+ services.forEach(({ serviceId, serviceUrl, connectionData }, si) => {
120
+ const isLastService = si === services.length - 1;
121
+ const servicePrefix = isLastService ? " └── " : " ├── ";
122
+ const childPad = isLastService ? " " : " │ ";
123
+
124
+ if (verbose && connectionData && connectionData.modules) {
125
+ console.log(`${servicePrefix}${chalk.bold(serviceId)} ${chalk.dim(serviceUrl)}`);
126
+ const mods = connectionData.modules.filter((m) => m.name !== "Plugin");
127
+ mods.forEach((mod, mi) => {
128
+ const isLastMod = mi === mods.length - 1;
129
+ const modPrefix = childPad + (isLastMod ? "└── " : "├── ");
130
+ const modPad = childPad + (isLastMod ? " " : "│ ");
131
+ console.log(`${modPrefix}${mod.name}`);
132
+ const methods = mod.methods || [];
133
+ methods.forEach((m, mei) => {
134
+ const isLastMethod = mei === methods.length - 1;
135
+ const methodPrefix = modPad + (isLastMethod ? "└── " : "├── ");
136
+ console.log(`${methodPrefix}${chalk.cyan(m.fn)}`);
137
+ });
138
+ });
139
+ } else {
140
+ console.log(`${servicePrefix}${serviceId} ${chalk.dim(serviceUrl)}`);
141
+ }
142
+ });
143
+ if (pi < codes.length - 1) console.log("");
144
+ });
145
+ console.log("");
146
+ }
147
+
148
+ async function loadServices(api, project_code, manifestPath, Client) {
149
+ const manifestFile = manifestPath || path.join(process.cwd(), "systemview.manifest.json");
150
+ if (fs.existsSync(manifestFile)) {
151
+ try {
152
+ const raw = JSON.parse(fs.readFileSync(manifestFile, "utf8"));
153
+ return raw.services ? raw.services : [raw];
154
+ } catch {}
155
+ }
156
+ try {
157
+ const { SystemView } = await Client.loadService(api);
158
+ return (await SystemView.getServices(project_code)) || [];
159
+ } catch {
160
+ return [];
161
+ }
162
+ }
@@ -1,6 +1,7 @@
1
1
  const { exec } = require("child_process");
2
+ const resolveNamespace = require("./utils/resolveNamespace");
2
3
 
3
- module.exports = function openBrowser(url, project_code, namespace) {
4
+ module.exports = function openBrowser(url, project_code, namespace, connectedServices) {
4
5
  if (!url || typeof url !== "string" || url.trim() === "") {
5
6
  console.error("Invalid URL provided");
6
7
  return;
@@ -8,15 +9,28 @@ module.exports = function openBrowser(url, project_code, namespace) {
8
9
 
9
10
  const browserCommand = process.platform === "win32" ? "start" : "open";
10
11
  const code = project_code ? "/" + project_code : "";
12
+
13
+ if (namespace && connectedServices && connectedServices.length) {
14
+ const matches = resolveNamespace(namespace, connectedServices);
15
+ if (matches.length) {
16
+ matches.forEach(({ serviceId, moduleName, methodName }) => {
17
+ const nsp = `/${serviceId}/${moduleName}/${methodName}`;
18
+ const fullUrl = `${url}${code}${nsp}`;
19
+ console.log(`opening... ${fullUrl}`);
20
+ exec(`${browserCommand} ${fullUrl}`, (error, stdout, stderr) => {
21
+ if (error) console.error(`Failed to open browser: ${error.message}`);
22
+ else if (stderr) console.error(`Failed to open browser: ${stderr}`);
23
+ });
24
+ });
25
+ return;
26
+ }
27
+ }
28
+
11
29
  const nsp = code && namespace ? "/" + namespace.replace(/\./g, "/") : "";
12
30
  console.log(`opening... ${url}${code}${nsp}`);
13
31
  exec(`${browserCommand} ${url}${code}${nsp}`, (error, stdout, stderr) => {
14
- if (error) {
15
- console.error(`Failed to open browser: ${error.message}`);
16
- } else if (stderr) {
17
- console.error(`Failed to open browser: ${stderr}`);
18
- } else {
19
- console.log("Browser opened successfully!");
20
- }
32
+ if (error) console.error(`Failed to open browser: ${error.message}`);
33
+ else if (stderr) console.error(`Failed to open browser: ${stderr}`);
34
+ else console.log("Browser opened successfully!");
21
35
  });
22
36
  };
package/cli/runTests.js CHANGED
@@ -5,12 +5,55 @@ const { initializeSavedTests } = require("../testing-utilities/transformTests");
5
5
  const FullTestController = require("../testing-utilities/FullTestController");
6
6
  const log = require("./logger");
7
7
  const validationMessages = require("../testing-utilities/validtionMessages");
8
+ const { truncate, TruncationMarker } = require("./utils/truncate");
9
+
10
+ function formatTruncated(value, indent = 0) {
11
+ const pad = " ".repeat(indent);
12
+ const inner = " ".repeat(indent + 1);
13
+ if (value instanceof TruncationMarker) return chalk.dim(value.message);
14
+ if (value === null) return "null";
15
+ if (typeof value !== "object") return JSON.stringify(value);
16
+ if (Array.isArray(value)) {
17
+ if (!value.length) return "[]";
18
+ const items = value.map((v) =>
19
+ v instanceof TruncationMarker
20
+ ? inner + chalk.dim(v.message)
21
+ : inner + formatTruncated(v, indent + 1)
22
+ );
23
+ return "[\n" + items.join(",\n") + "\n" + pad + "]";
24
+ }
25
+ const keys = Object.keys(value);
26
+ if (!keys.length) return "{}";
27
+ const entries = keys.map((k) => {
28
+ const v = value[k];
29
+ if (k === "__more__" && v instanceof TruncationMarker)
30
+ return inner + chalk.dim(v.message);
31
+ return inner + JSON.stringify(k) + ": " + formatTruncated(v, indent + 1);
32
+ });
33
+ return "{\n" + entries.join(",\n") + "\n" + pad + "}";
34
+ }
8
35
  const { runFullTest } = new FullTestController();
9
36
 
10
37
  const chalk = require("chalk");
11
38
  const DIVIDER = chalk.dim(" " + "─".repeat(60));
39
+ const SUITE_DIVIDER = chalk.cyan(" " + "─".repeat(60));
12
40
 
13
- module.exports = async function runTests(url, project_code, namespace, { json = false, verbose = false, manifest: manifestPath, headers: cliHeaders = {} } = {}) {
41
+ module.exports = async function runTests(
42
+ url,
43
+ project_code,
44
+ namespace,
45
+ {
46
+ json = false,
47
+ verbose = false,
48
+ manifest: manifestPath,
49
+ headers: cliHeaders = {},
50
+ bail = false,
51
+ dryRun = false,
52
+ phase: phaseFilter = null,
53
+ index: indexFilter = undefined,
54
+ skip: skipPatterns = [],
55
+ } = {}
56
+ ) {
14
57
  const api = `${url}/systemview/api`;
15
58
 
16
59
  if (!project_code) {
@@ -18,7 +61,11 @@ module.exports = async function runTests(url, project_code, namespace, { json =
18
61
  return 1;
19
62
  }
20
63
 
21
- const { services: connectedServices, probeHeaders: manifestHeaders } = await resolveServices(api, project_code, manifestPath);
64
+ const { services: connectedServices, probeHeaders: manifestHeaders } = await resolveServices(
65
+ api,
66
+ project_code,
67
+ manifestPath
68
+ );
22
69
  if (!connectedServices || !connectedServices.length) {
23
70
  log.warn("No connected services found for project: " + project_code);
24
71
  return 1;
@@ -36,11 +83,12 @@ module.exports = async function runTests(url, project_code, namespace, { json =
36
83
  const allTestLists = await getTests(connectedServices, Client);
37
84
  const testToRun = allTestLists
38
85
  .map((list) =>
39
- namespace
40
- ? list.filter(({ namespace: n }) =>
41
- `${n.serviceId}.${n.moduleName}.${n.methodName}`.includes(namespace)
42
- )
43
- : list
86
+ list.filter(({ namespace: n }) => {
87
+ const full = `${n.serviceId}.${n.moduleName}.${n.methodName}`;
88
+ const matchesInclude = !namespace || full.includes(namespace);
89
+ const matchesSkip = skipPatterns.length && skipPatterns.some((p) => full.includes(p));
90
+ return matchesInclude && !matchesSkip;
91
+ })
44
92
  )
45
93
  .filter((list) => list.length);
46
94
 
@@ -49,8 +97,24 @@ module.exports = async function runTests(url, project_code, namespace, { json =
49
97
  return 0;
50
98
  }
51
99
 
100
+ if (dryRun) {
101
+ const total = testToRun.reduce((n, list) => n + list.length, 0);
102
+ console.log("");
103
+ console.log(` Would run ${total} ${total === 1 ? "test" : "tests"}:`);
104
+ console.log("");
105
+ testToRun.forEach((list) => {
106
+ list.forEach(({ namespace: n, title }) => {
107
+ console.log(` ${chalk.cyan(`${n.serviceId}.${n.moduleName}.${n.methodName}`)} — "${title}"`);
108
+ });
109
+ });
110
+ console.log("");
111
+ return 0;
112
+ }
113
+
52
114
  const jsonOutput = json ? { projectCode: project_code, passed: 0, failed: 0, tests: [] } : null;
53
115
  let totalFailed = 0;
116
+ const serviceSummaries = [];
117
+ const allFailures = [];
54
118
 
55
119
  for (const testList of testToRun) {
56
120
  const { serviceId } = testList[0].namespace;
@@ -58,7 +122,14 @@ module.exports = async function runTests(url, project_code, namespace, { json =
58
122
 
59
123
  try {
60
124
  const initialized = initializeSavedTests(testList, connectedServices, Client);
61
- const { passed, failed, jsonTests } = await runAllTests(initialized, url, project_code, json, verbose);
125
+ const { passed, failed, jsonTests, failures } = await runAllTests(
126
+ initialized,
127
+ url,
128
+ project_code,
129
+ json,
130
+ verbose,
131
+ { bail, phaseFilter, indexFilter }
132
+ );
62
133
  totalFailed += failed;
63
134
 
64
135
  if (json) {
@@ -66,10 +137,11 @@ module.exports = async function runTests(url, project_code, namespace, { json =
66
137
  jsonOutput.failed += failed;
67
138
  jsonOutput.tests.push(...jsonTests);
68
139
  } else {
69
- log[failed ? "error" : "success"](
70
- `${serviceId}: ${passed + failed} tests, ${passed} passed, ${failed} failed`
71
- );
140
+ serviceSummaries.push({ serviceId, passed, failed });
141
+ allFailures.push(...failures);
72
142
  }
143
+
144
+ if (bail && failed > 0) break;
73
145
  } catch (error) {
74
146
  log.error(`Unexpected error for ${serviceId}: ${error.message}`);
75
147
  }
@@ -81,22 +153,67 @@ module.exports = async function runTests(url, project_code, namespace, { json =
81
153
  console.log("");
82
154
  log.info("TEST COMPLETE");
83
155
  console.log("");
156
+
157
+ serviceSummaries.forEach(({ serviceId, passed, failed }) => {
158
+ const total = passed + failed;
159
+ log[failed ? "error" : "success"](
160
+ `${serviceId}: ${total} tests, ${passed} passed, ${failed} failed`
161
+ );
162
+ });
163
+
164
+ if (allFailures.length) {
165
+ console.log("");
166
+ log.warn("FAILED:");
167
+ allFailures.forEach(({ serviceId, moduleName, methodName, title, failedPhases }) => {
168
+ console.log("");
169
+ log.error(` ${serviceId}.${moduleName}.${methodName} — "${title}"`);
170
+ failedPhases.forEach(({ phase, actions }) => {
171
+ actions.forEach(({ actionTitle, errors }) => {
172
+ console.log(` ${phase}: "${actionTitle}"`);
173
+ errors.forEach((err) => console.log(` → ${validationMessages(err)}`));
174
+ });
175
+ });
176
+ });
177
+ console.log("");
178
+ }
84
179
  }
85
180
 
86
181
  return totalFailed > 0 ? 1 : 0;
87
182
  };
88
183
 
89
- const runAllTests = async (savedTests, url, project_code, json, verbose) => {
90
- const sum = { passed: 0, failed: 0, jsonTests: [] };
184
+ const runAllTests = async (savedTests, url, project_code, json, verbose, opts = {}) => {
185
+ const { bail = false, phaseFilter = null, indexFilter = undefined } = opts;
186
+ const sum = { passed: 0, failed: 0, jsonTests: [], failures: [] };
187
+
188
+ for (const { Before: B, Main: M, Events: E, After: A, title, namespace } of savedTests) {
189
+ let Before = [...B];
190
+ let Main = [...M];
191
+ let Events = [...E];
192
+ let After = [...A];
193
+
194
+ if (phaseFilter) {
195
+ const allowed = phaseFilter.split(",").map((p) => p.trim().toLowerCase());
196
+ if (!allowed.includes("before")) Before = [];
197
+ if (!allowed.includes("main")) Main = [];
198
+ if (!allowed.includes("events")) Events = [];
199
+ if (!allowed.includes("after")) After = [];
200
+ }
201
+
202
+ if (indexFilter !== undefined) {
203
+ Before = Before.slice(indexFilter, indexFilter + 1);
204
+ Main = Main.slice(indexFilter, indexFilter + 1);
205
+ Events = Events.slice(indexFilter, indexFilter + 1);
206
+ After = After.slice(indexFilter, indexFilter + 1);
207
+ }
91
208
 
92
- for (const { Before, Main, Events, After, title, namespace } of savedTests) {
93
- const { moduleName, methodName, serviceId } = namespace;
94
209
  const fullTest = [Before, Main, Events, After];
210
+ const { moduleName, methodName, serviceId } = namespace;
95
211
 
96
212
  if (!json) {
97
- console.log("");
98
- console.log(" " + chalk.bold.cyan(`${serviceId}.${moduleName}.${methodName}(...)`));
99
- console.log(chalk.dim(` UI → ${url}/${project_code}/${serviceId}/${moduleName}/${methodName}`));
213
+ console.log(SUITE_DIVIDER);
214
+ console.log(" " + chalk.bold("Test: ") + chalk.bold.cyan(title));
215
+ console.log(SUITE_DIVIDER);
216
+ console.log(chalk.dim(` ${serviceId}.${moduleName}.${methodName} → ${url}/${project_code}/${serviceId}/${moduleName}/${methodName}`));
100
217
  console.log("");
101
218
  }
102
219
 
@@ -105,8 +222,24 @@ const runAllTests = async (savedTests, url, project_code, json, verbose) => {
105
222
  const hasFailed =
106
223
  countErrors(Before) + countErrors(Main) + countErrors(Events) + countErrors(After) > 0;
107
224
 
108
- if (hasFailed) sum.failed++;
109
- else sum.passed++;
225
+ if (hasFailed) {
226
+ sum.failed++;
227
+ const failedPhases = [];
228
+ [
229
+ { phase: "Before", tests: Before },
230
+ { phase: "Main", tests: Main },
231
+ { phase: "Events", tests: Events },
232
+ { phase: "After", tests: After },
233
+ ].forEach(({ phase, tests }) => {
234
+ const actions = tests
235
+ .filter((t) => t.errors && t.errors.length)
236
+ .map((t) => ({ actionTitle: t.title, errors: t.errors }));
237
+ if (actions.length) failedPhases.push({ phase, actions });
238
+ });
239
+ sum.failures.push({ serviceId, moduleName, methodName, title, failedPhases });
240
+ } else {
241
+ sum.passed++;
242
+ }
110
243
 
111
244
  if (json) {
112
245
  sum.jsonTests.push({
@@ -121,47 +254,65 @@ const runAllTests = async (savedTests, url, project_code, json, verbose) => {
121
254
  After: serializePhase(After),
122
255
  });
123
256
  } else {
124
- logPhase(Before, "Before", verbose, verbose);
125
- logPhase(Main, "Main", true, verbose);
126
- logPhase(Events, "Events", verbose, verbose);
127
- logPhase(After, "After", verbose, verbose);
257
+ const compact = !verbose;
258
+ logPhase(Before, "Before", verbose, compact, false);
259
+ logPhase(Main, "Main", verbose, false, true);
260
+ logPhase(Events, "Events", verbose, compact, false);
261
+ logPhase(After, "After", verbose, compact, false);
128
262
  }
263
+
264
+ if (bail && hasFailed) break;
129
265
  }
130
266
 
131
267
  return sum;
132
268
  };
133
269
 
134
- function countErrors(tests) {
135
- return tests.reduce((n, t) => n + (t.errors ? t.errors.length : 0), 0);
136
- }
137
-
138
- function logPhase(tests, section, logSuccess = false, verbose = false) {
270
+ function logPhase(tests, section, verbose = false, compact = false, isMain = false) {
139
271
  tests.forEach(({ errors, results, namespace, title, args }) => {
140
272
  const { serviceId, moduleName, methodName } = namespace;
141
273
  const failed = errors && errors.length;
142
- if (!failed && !logSuccess) return;
143
- console.log(chalk.dim(` ${section}: ${title}`));
144
- failed
145
- ? log.error(` ${serviceId}.${moduleName}.${methodName}(...)`)
146
- : log.success(` ${serviceId}.${moduleName}.${methodName}(...)`);
274
+
275
+ const label = isMain ? chalk.bold.green(section) : chalk.blue(section);
276
+ console.log(` ${label}: ${chalk.dim(title)}`);
277
+ if (failed) {
278
+ console.log(" " + chalk.bold.red(`${serviceId}.${moduleName}.${methodName}(...)`));
279
+ } else if (isMain) {
280
+ console.log(" " + chalk.bold.green(`${serviceId}.${moduleName}.${methodName}(...)`));
281
+ } else {
282
+ log.success(` ${serviceId}.${moduleName}.${methodName}(...)`);
283
+ }
284
+
147
285
  if (verbose && args && args.length) {
148
286
  console.log("");
149
287
  args.forEach(({ name, input }) => {
150
288
  console.log(` ${chalk.dim(name)}`, JSON.stringify(input));
151
289
  });
152
290
  }
291
+
153
292
  console.log("");
154
- console.log(" results:", JSON.stringify(results, null, 2).replace(/\n/g, "\n "));
293
+ if (compact) {
294
+ console.log(" results:", JSON.stringify(truncate(results)));
295
+ } else if (verbose) {
296
+ console.log(" results:", JSON.stringify(results, null, 2).replace(/\n/g, "\n "));
297
+ } else {
298
+ console.log(" results:", formatTruncated(truncate(results)).replace(/\n/g, "\n "));
299
+ }
155
300
  console.log("");
301
+
156
302
  if (failed) {
157
303
  log.warn(` ${errors.length} validation error(s)`);
158
304
  errors.forEach((err) => console.log(` → ${validationMessages(err)}`));
159
305
  console.log("");
160
306
  }
307
+
161
308
  console.log(DIVIDER);
162
309
  });
163
310
  }
164
311
 
312
+ function countErrors(tests) {
313
+ return tests.reduce((n, t) => n + (t.errors ? t.errors.length : 0), 0);
314
+ }
315
+
165
316
  function serializePhase(tests) {
166
317
  return tests.map((test) => {
167
318
  const { title, namespace, args, results, errors, evaluations } = test;
@@ -214,7 +365,9 @@ async function resolveServices(api, project_code, manifestPath) {
214
365
  : [{ serviceId: raw.serviceId, system: raw.system, specList: raw.specList }];
215
366
 
216
367
  if (project_code && raw.projectCode && raw.projectCode !== project_code) {
217
- log.warn(`Manifest projectCode "${raw.projectCode}" doesn't match "${project_code}", falling back to API`);
368
+ log.warn(
369
+ `Manifest projectCode "${raw.projectCode}" doesn't match "${project_code}", falling back to API`
370
+ );
218
371
  } else {
219
372
  return { services, probeHeaders: raw.probeHeaders || {} };
220
373
  }