testbeats 2.2.5 → 2.2.6

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "testbeats",
3
- "version": "2.2.5",
3
+ "version": "2.2.6",
4
4
  "description": "Publish test results to Microsoft Teams, Google Chat, Slack and InfluxDB",
5
5
  "main": "src/index.js",
6
6
  "types": "./src/index.d.ts",
@@ -173,21 +173,40 @@ class PublishCommand {
173
173
  }
174
174
  }
175
175
  if (target.inputs) {
176
- const inputs = target.inputs;
177
- if (target.name === 'slack' || target.name === 'teams' || target.name === 'chat') {
178
- if (!inputs.url) {
179
- throw new Error(`missing url in ${target.name} target inputs`);
180
- }
181
- if (typeof inputs.url !== 'string') {
182
- throw new Error(`url in ${target.name} target inputs must be a string`);
183
- }
184
- if (!inputs.url.startsWith('http')) {
185
- throw new Error(`url in ${target.name} target inputs must start with 'http' or 'https'`);
176
+ this.#validateURL(target);
177
+ }
178
+ }
179
+ logger.debug("Validating targets - Successful!")
180
+ }
181
+
182
+ #validateURL(target) {
183
+ const inputs = target.inputs;
184
+ if (target.name === 'slack' || target.name === 'teams' || target.name === 'chat') {
185
+ if (inputs.token) {
186
+ if (!Array.isArray(inputs.channels)) {
187
+ throw new Error(`channels in ${target.name} target inputs must be an array`);
188
+ }
189
+ if (!inputs.channels.length) {
190
+ throw new Error(`at least one channel must be defined in ${target.name} target inputs`);
191
+ }
192
+ for (const channel of inputs.channels) {
193
+ if (typeof channel !== 'string') {
194
+ throw new Error(`channel in ${target.name} target inputs must be a string`);
186
195
  }
187
196
  }
197
+ return;
198
+ }
199
+
200
+ if (!inputs.url) {
201
+ throw new Error(`missing url in ${target.name} target inputs`);
202
+ }
203
+ if (typeof inputs.url !== 'string') {
204
+ throw new Error(`url in ${target.name} target inputs must be a string`);
205
+ }
206
+ if (!inputs.url.startsWith('http')) {
207
+ throw new Error(`url in ${target.name} target inputs must start with 'http' or 'https'`);
188
208
  }
189
209
  }
190
- logger.debug("Validating targets - Successful!")
191
210
  }
192
211
 
193
212
  #processResults() {
@@ -36,7 +36,7 @@ class AIFailureSummaryExtension extends BaseExtension {
36
36
  * @type {import('../beats/beats.types').IBeatExecutionMetric}
37
37
  */
38
38
  const execution_metrics = data.execution_metrics[0];
39
- this.text = execution_metrics.failure_summary;
39
+ this.text = this.platform.code(execution_metrics.failure_summary);
40
40
  }
41
41
  }
42
42
 
@@ -35,7 +35,7 @@ class BaseExtension {
35
35
  this.default_options = {};
36
36
 
37
37
  /**
38
- * @type {import('../platforms').BasePlatform}
38
+ * @type {import('../platforms/base.platform').BasePlatform}
39
39
  */
40
40
  this.platform = getPlatform(this.target.name);
41
41
  }
@@ -18,6 +18,7 @@ const TARGET = Object.freeze({
18
18
  CUSTOM: 'custom',
19
19
  DELAY: 'delay',
20
20
  INFLUX: 'influx',
21
+ HTTP: 'http',
21
22
  });
22
23
 
23
24
  const EXTENSION = Object.freeze({
package/src/index.d.ts CHANGED
@@ -10,7 +10,7 @@ export interface ITarget {
10
10
  name: TargetName;
11
11
  enable?: string | boolean;
12
12
  condition?: Condition;
13
- inputs?: SlackInputs | TeamsInputs | ChatInputs | GitHubInputs | CustomTargetInputs | InfluxDBTargetInputs;
13
+ inputs?: SlackInputs | TeamsInputs | ChatInputs | GitHubInputs | ICustomTargetInputs | InfluxDBTargetInputs;
14
14
  extensions?: IExtension[];
15
15
  }
16
16
 
@@ -234,6 +234,8 @@ export interface TargetInputs {
234
234
 
235
235
  export interface SlackInputs extends TargetInputs {
236
236
  message_format?: 'blocks' | 'attachments';
237
+ token?: string;
238
+ channels?: string[];
237
239
  }
238
240
 
239
241
  export interface TeamsInputs extends TargetInputs {
@@ -275,11 +277,19 @@ export interface CustomTargetFunctionContext {
275
277
 
276
278
  export type CustomTargetFunction = (ctx: CustomTargetFunctionContext) => void | Promise<void>;
277
279
 
278
- export interface CustomTargetInputs {
280
+ export interface ICustomTargetInputs {
279
281
  load: string | CustomTargetFunction;
280
282
  }
281
283
 
284
+ export interface IDelayTargetInputs {
285
+ seconds: number;
286
+ }
282
287
 
288
+ export interface IHttpTargetInputs {
289
+ url: string;
290
+ method: string;
291
+ headers: object;
292
+ }
283
293
 
284
294
  export interface CustomResultOptions {
285
295
  type: string;
@@ -337,5 +347,9 @@ export type IExtensionDefaultOptions = {
337
347
  condition: Condition
338
348
  }
339
349
 
350
+ export type ITargetDefaultOptions = {
351
+ condition: Condition
352
+ }
353
+
340
354
  export function publish(options: PublishOptions): Promise<any>
341
355
  export function defineConfig(config: PublishConfig): PublishConfig
@@ -31,6 +31,16 @@ class BasePlatform {
31
31
  return this.merge(items.map(item => `- ${item}`));
32
32
  }
33
33
 
34
+ /**
35
+ * @param {string} text
36
+ * @returns {string}
37
+ */
38
+ code(text) {
39
+ return text;
40
+ }
41
+
42
+
43
+
34
44
  /**
35
45
  *
36
46
  * @param {import('..').ITarget} target
@@ -3,6 +3,7 @@ const { SlackPlatform } = require('./slack.platform');
3
3
  const { TeamsPlatform } = require('./teams.platform');
4
4
  const { ChatPlatform } = require('./chat.platform');
5
5
  const { GitHubPlatform } = require('./github.platform');
6
+ const { BasePlatform } = require('./base.platform');
6
7
 
7
8
  /**
8
9
  *
@@ -19,7 +20,7 @@ function getPlatform(name) {
19
20
  case TARGET.GITHUB:
20
21
  return new GitHubPlatform();
21
22
  default:
22
- throw new Error('Invalid Platform');
23
+ return new BasePlatform();
23
24
  }
24
25
  }
25
26
 
@@ -19,6 +19,10 @@ class SlackPlatform extends BasePlatform {
19
19
  }
20
20
  return this.merge(items.map(item => `• ${item}`));
21
21
  }
22
+
23
+ code(text) {
24
+ return `\`\`\`${text}\`\`\``;
25
+ }
22
26
  }
23
27
 
24
28
  module.exports = { SlackPlatform }
@@ -0,0 +1,45 @@
1
+ const { getPlatform } = require('../platforms');
2
+ const { STATUS } = require('../helpers/constants');
3
+
4
+ class BaseTarget {
5
+
6
+ constructor({ target }) {
7
+
8
+ /**
9
+ * @type {import('../index').ITarget}
10
+ */
11
+ this.target = target;
12
+
13
+ /**
14
+ * @type {string}
15
+ */
16
+ this.name = target.name;
17
+
18
+ /**
19
+ * @type {string | boolean}
20
+ */
21
+ this.enable = target.enable;
22
+
23
+ /**
24
+ * @type {import('../index').Condition}
25
+ */
26
+ this.condition = target.condition || STATUS.PASS_OR_FAIL;
27
+
28
+ /**
29
+ * @type {import('../index').IExtension[]}
30
+ */
31
+ this.extensions = target.extensions || [];
32
+
33
+ /**
34
+ * @type {import('../platforms/base.platform').BasePlatform}
35
+ */
36
+ this.platform = getPlatform(this.name);
37
+ }
38
+
39
+ async run({ result }) {
40
+ // throw new Error('Not implemented');
41
+ }
42
+
43
+ }
44
+
45
+ module.exports = { BaseTarget};
@@ -0,0 +1,31 @@
1
+ const { BaseTarget } = require('./base.target');
2
+ const path = require('path');
3
+
4
+ const DEFAULT_INPUTS = {};
5
+
6
+ class CustomTarget extends BaseTarget {
7
+
8
+ constructor({ target }) {
9
+ super({ target });
10
+
11
+ /**
12
+ * @type {import('../index').ICustomTargetInputs}
13
+ */
14
+ this.inputs = Object.assign({}, DEFAULT_INPUTS, target.inputs);
15
+ }
16
+
17
+ async run({ result }) {
18
+ if (typeof this.inputs.load === 'string') {
19
+ const cwd = process.cwd();
20
+ const target_runner = require(path.join(cwd, this.inputs.load));
21
+ await target_runner.run({ target: this.target, result });
22
+ } else if (typeof this.inputs.load === 'function') {
23
+ await this.inputs.load({ target: this.target, result });
24
+ } else {
25
+ throw `Invalid 'load' input in custom target - ${this.inputs.load}`;
26
+ }
27
+ }
28
+
29
+ }
30
+
31
+ module.exports = { CustomTarget };
@@ -0,0 +1,24 @@
1
+ const { BaseTarget } = require('./base.target');
2
+
3
+ const DEFAULT_INPUTS = {
4
+ seconds: 5
5
+ };
6
+
7
+ class DelayTarget extends BaseTarget {
8
+
9
+ constructor({ target }) {
10
+ super({ target });
11
+
12
+ /**
13
+ * @type {import('../index').IDelayTargetInputs}
14
+ */
15
+ this.inputs = Object.assign({}, DEFAULT_INPUTS, target.inputs);
16
+ }
17
+
18
+ async run() {
19
+ await new Promise(resolve => setTimeout(resolve, this.inputs.seconds * 1000));
20
+ }
21
+
22
+ }
23
+
24
+ module.exports = { DelayTarget };
@@ -0,0 +1,36 @@
1
+ const { BaseTarget } = require('./base.target');
2
+ const request = require('phin-retry');
3
+
4
+ const DEFAULT_INPUTS = {
5
+ url: '',
6
+ method: 'POST',
7
+ headers: {}
8
+ };
9
+
10
+ class HttpTarget extends BaseTarget {
11
+
12
+ constructor({ target }) {
13
+ super({ target });
14
+
15
+ /**
16
+ * @type {import('../index').IHttpTargetInputs}
17
+ */
18
+ this.inputs = Object.assign({}, DEFAULT_INPUTS, target.inputs);
19
+ }
20
+
21
+ async run({ result }) {
22
+ const { url, method, headers } = this.inputs;
23
+ await request.__fetch({
24
+ url,
25
+ method,
26
+ headers: {
27
+ 'Content-Type': 'application/json',
28
+ ...headers
29
+ },
30
+ body: { result }
31
+ });
32
+ }
33
+
34
+ }
35
+
36
+ module.exports = { HttpTarget };
@@ -2,8 +2,9 @@ const teams = require('./teams');
2
2
  const slack = require('./slack');
3
3
  const chat = require('./chat');
4
4
  const github = require('./github');
5
- const custom = require('./custom');
6
- const delay = require('./delay');
5
+ const { CustomTarget } = require('./custom.target');
6
+ const { DelayTarget } = require('./delay.target');
7
+ const { HttpTarget } = require('./http.target');
7
8
  const influx = require('./influx');
8
9
  const { TARGET } = require('../helpers/constants');
9
10
  const { checkCondition } = require('../helpers/helper');
@@ -19,11 +20,13 @@ function getTargetRunner(target) {
19
20
  case TARGET.GITHUB:
20
21
  return github;
21
22
  case TARGET.CUSTOM:
22
- return custom;
23
+ return new CustomTarget({ target });
23
24
  case TARGET.DELAY:
24
- return delay;
25
+ return new DelayTarget({ target });
25
26
  case TARGET.INFLUX:
26
27
  return influx;
28
+ case TARGET.HTTP:
29
+ return new HttpTarget({ target });
27
30
  default:
28
31
  return require(target.name);
29
32
  }
@@ -31,8 +34,9 @@ function getTargetRunner(target) {
31
34
 
32
35
  async function run(target, result) {
33
36
  const target_runner = getTargetRunner(target);
34
- const target_options = Object.assign({}, target_runner.default_options, target);
35
- if (await checkCondition({ condition: target_options.condition, result, target })) {
37
+ // const target_options = Object.assign({}, target_runner.default_options, target);
38
+ const condition = target.condition || target_runner.default_options?.condition || target_runner.condition;
39
+ if (await checkCondition({ condition, result, target })) {
36
40
  await target_runner.run({result, target});
37
41
  }
38
42
  }
@@ -9,6 +9,8 @@ const { getValidMetrics, getMetricValuesText } = require('../helpers/performance
9
9
  const TestResult = require('test-results-parser/src/models/TestResult');
10
10
  const { getPlatform } = require('../platforms');
11
11
 
12
+ const SLACK_BASE_URL = 'https://slack.com';
13
+
12
14
  const STATUSES = {
13
15
  GOOD: ':white_check_mark:',
14
16
  WARNING: ':warning:',
@@ -31,10 +33,7 @@ async function run({ result, target }) {
31
33
  }
32
34
  const message = getRootPayload({ result, target, payload });
33
35
  logger.info(`🔔 Publishing results to Slack...`);
34
- return request.post({
35
- url: target.inputs.url,
36
- body: message
37
- });
36
+ return publish({ inputs: target.inputs, message });
38
37
  }
39
38
 
40
39
  async function setFunctionalPayload({ result, target, payload }) {
@@ -325,6 +324,28 @@ async function handleErrors({ target, errors }) {
325
324
  });
326
325
  }
327
326
 
327
+ async function publish({ inputs, message}) {
328
+ const { url, token, channels } = inputs;
329
+ if (token) {
330
+ for (let i = 0; i < channels.length; i++) {
331
+ message.channel = channels[i];
332
+ return request.post({
333
+ url: url ? url : `${SLACK_BASE_URL}/api/chat.postMessage`,
334
+ headers: {
335
+ 'Authorization': `Bearer ${token}`
336
+ },
337
+ body: message
338
+ });
339
+ }
340
+
341
+ } else {
342
+ return request.post({
343
+ url,
344
+ body: message
345
+ });
346
+ }
347
+ }
348
+
328
349
  module.exports = {
329
350
  run,
330
351
  handleErrors,
@@ -1,28 +0,0 @@
1
- const path = require('path');
2
- const { STATUS } = require('../helpers/constants');
3
-
4
- /**
5
- *
6
- * @param {object} param0
7
- * @param {import('../index').ITarget} param0.target
8
- */
9
- async function run({result, target}) {
10
- if (typeof target.inputs.load === 'string') {
11
- const cwd = process.cwd();
12
- const target_runner = require(path.join(cwd, target.inputs.load));
13
- await target_runner.run({ target, result });
14
- } else if (typeof target.inputs.load === 'function') {
15
- await target.inputs.load({ target, result });
16
- } else {
17
- throw `Invalid 'load' input in custom target - ${target.inputs.load}`;
18
- }
19
- }
20
-
21
- const default_options = {
22
- condition: STATUS.PASS_OR_FAIL
23
- }
24
-
25
- module.exports = {
26
- run,
27
- default_options
28
- }
@@ -1,19 +0,0 @@
1
- const { STATUS } = require("../helpers/constants");
2
-
3
- async function run({ target }) {
4
- target.inputs = Object.assign({}, default_inputs, target.inputs);
5
- await new Promise(resolve => setTimeout(resolve, target.inputs.seconds * 1000));
6
- }
7
-
8
- const default_options = {
9
- condition: STATUS.PASS_OR_FAIL
10
- }
11
-
12
- const default_inputs = {
13
- seconds: 5
14
- }
15
-
16
- module.exports = {
17
- run,
18
- default_options
19
- }