Repository: test-results-reporter/reporter
Pull request #145 (2024-01-20 13:17:12)

added beats


Patch:
diff --git a/package-lock.json b/package-lock.json
index cdc2273..27c0bab 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -1,12 +1,12 @@
 {
   "name": "test-results-reporter",
-  "version": "1.0.19",
+  "version": "1.1.0",
   "lockfileVersion": 2,
   "requires": true,
   "packages": {
     "": {
       "name": "test-results-reporter",
-      "version": "1.0.19",
+      "version": "1.1.0",
       "license": "ISC",
       "dependencies": {
         "async-retry": "^1.3.3",
diff --git a/package.json b/package.json
index dd08428..bc52f3d 100644
--- a/package.json
+++ b/package.json
@@ -1,6 +1,6 @@
 {
   "name": "test-results-reporter",
-  "version": "1.0.19",
+  "version": "1.1.0",
   "description": "Publish test results to Microsoft Teams, Google Chat, Slack and InfluxDB",
   "main": "src/index.js",
   "types": "./src/index.d.ts",
diff --git a/src/beats/index.js b/src/beats/index.js
new file mode 100644
index 0000000..59fa7d9
--- /dev/null
+++ b/src/beats/index.js
@@ -0,0 +1,82 @@
+const request = require('phin-retry');
+const TestResult = require('test-results-parser/src/models/TestResult');
+
+const BASE_URL = "http://localhost:9393";
+
+/**
+ * @param {import('../index').PublishReport} config
+ * @param {TestResult} result
+ */
+async function run(config, result) {
+  if (config.project && config.build && config.api_key) {
+    const run_id = await publishTestResults(config, result);
+    if (run_id) {
+      attachTestBeatsReportHyperLink(config, run_id);
+    }
+  }
+}
+
+/**
+ * @param {import('../index').PublishReport} config 
+ * @param {TestResult} result
+ */
+async function publishTestResults(config, result) {
+  try {
+    const response = await request.post({
+      url: `${BASE_URL}/api/core/v1/test-runs`,
+      headers: {
+        'x-api-key': config.api_key
+      },
+      body: {
+        project: config.project,
+        build: config.build,
+        ...result
+      }
+    });
+    return response.id;
+  } catch (error) {
+    console.log("Unable to publish results to TestBeats");
+    console.log(error);
+  }
+}
+
+/**
+ * @param {import('../index').PublishReport} config
+ * @param {string} run_id
+ */
+function attachTestBeatsReportHyperLink(config, run_id) {
+  const hyperlink_to_test_beats = getTestBeatsReportHyperLink(run_id);
+  for (const target of config.targets) {
+    if (target.name === 'chat' || target.name === 'teams' || target.name === 'slack') {
+      target.extensions = target.extensions || [];
+      if (target.extensions.length > 0) {
+        if (target.extensions[0].name === 'hyperlinks' && target.extensions[0].inputs.links[0].name === 'Test Beats Report') {
+          target.extensions[0].inputs.links[0].url = `${BASE_URL}/reports/${run_id}`;
+          continue;
+        }
+      }
+      target.extensions = [hyperlink_to_test_beats, ...target.extensions];
+    }
+  }
+}
+
+/**
+ * 
+ * @param {string} run_id 
+ * @returns 
+ */
+function getTestBeatsReportHyperLink(run_id) {
+  return {
+    "name": "hyperlinks",
+    "inputs": {
+      "links": [
+        {
+          "text": "Test Beats Report",
+          "url": `${BASE_URL}/reports/${run_id}`
+        }
+      ]
+    }
+  }
+}
+
+module.exports = { run }
\ No newline at end of file
diff --git a/src/commands/publish.js b/src/commands/publish.js
index dcdd7f7..d2a5168 100644
--- a/src/commands/publish.js
+++ b/src/commands/publish.js
@@ -3,6 +3,7 @@ const trp = require('test-results-parser');
 const prp = require('performance-results-parser');
 
 const { processData } = require('../helpers/helper');
+const beats = require('../beats');
 const target_manager = require('../targets');
 
 /**
@@ -14,22 +15,36 @@ async function run(opts) {
     opts.config = require(path.join(cwd, opts.config));
   }
   const config = processData(opts.config);
-  for (const report of config.reports) {
-    const parsed_results = [];
-    for (const result_options of report.results) {
-      if (result_options.type === 'custom') {
-        parsed_results.push(result_options.result);
-      } else if (result_options.type === 'jmeter') {
-        parsed_results.push(prp.parse(result_options));
-      } else {
-        parsed_results.push(trp.parse(result_options));
-      }
+  if (config.reports) {
+    for (const report of config.reports) {
+      await processReport(report);
     }
-    for (let i = 0; i < parsed_results.length; i++) {
-      const result = parsed_results[i];
-      for (const target of report.targets) {
-        await target_manager.run(target, result);
-      }
+  } else {
+    await processReport(config);
+  }
+}
+
+/**
+ * 
+ * @param {import('../index').PublishReport} report 
+ */
+async function processReport(report) {
+  const parsed_results = [];
+  for (const result_options of report.results) {
+    if (result_options.type === 'custom') {
+      parsed_results.push(result_options.result);
+    } else if (result_options.type === 'jmeter') {
+      parsed_results.push(prp.parse(result_options));
+    } else {
+      parsed_results.push(trp.parse(result_options));
+    }
+  }
+
+  for (let i = 0; i < parsed_results.length; i++) {
+    const result = parsed_results[i];
+    await beats.run(report, result);
+    for (const target of report.targets) {
+      await target_manager.run(target, result);
     }
   }
 }
diff --git a/src/index.d.ts b/src/index.d.ts
index edf755c..1974109 100644
--- a/src/index.d.ts
+++ b/src/index.d.ts
@@ -210,12 +210,20 @@ export interface CustomResultOptions {
 }
 
 export interface PublishReport {
-  targets: Target[];
-  results: ParseOptions[] | PerformanceParseOptions[] | CustomResultOptions[];
+  api_key?: string;
+  project?: string;
+  build?: string;
+  targets?: Target[];
+  results?: ParseOptions[] | PerformanceParseOptions[] | CustomResultOptions[];
 }
 
 export interface PublishConfig {
-  reports: PublishReport[];
+  api_key?: string;
+  project?: string;
+  build?: string;
+  targets?: Target[];
+  results?: ParseOptions[] | PerformanceParseOptions[] | CustomResultOptions[];
+  reports?: PublishReport[];
 }
 
 export interface PublishOptions {


Test patch:
diff --git a/test/beats.spec.js b/test/beats.spec.js
new file mode 100644
index 0000000..81b4f99
--- /dev/null
+++ b/test/beats.spec.js
@@ -0,0 +1,96 @@
+const { mock } = require('pactum');
+const assert = require('assert');
+const { publish } = require("../src");
+
+describe('TestBeats', () => {
+
+  it('should send results to beats', async () => {
+    const id1 = mock.addInteraction('post test results to testbeats');
+    const id2 = mock.addInteraction('post test-summary with testbeats to teams');
+    await publish({
+      config: {
+        api_key: 'api-key',
+        project: 'project-name',
+        build: 'build-name',
+        targets: [
+          {
+            name: 'teams',
+            inputs: {
+              url: 'http://localhost:9393/message'
+            }
+          }
+        ],
+        results: [
+          {
+            type: 'testng',
+            files: [
+              'test/data/testng/single-suite.xml'
+            ]
+          }
+        ]
+      }
+    });
+    assert.equal(mock.getInteraction(id1).exercised, true);
+    assert.equal(mock.getInteraction(id2).exercised, true);
+  });
+
+  it('should send results to beats with extensions', async () => {
+    const id1 = mock.addInteraction('post test results to testbeats');
+    const id2 = mock.addInteraction('post test-summary with extensions and testbeats to teams');
+    await publish({
+      config: {
+        api_key: 'api-key',
+        project: 'project-name',
+        build: 'build-name',
+        targets: [
+          {
+            name: 'teams',
+            inputs: {
+              url: 'http://localhost:9393/message'
+            },
+            "extensions": [
+              {
+                "name": "metadata",
+                "inputs": {
+                  "data": [
+                    {
+                      "key": "Browser",
+                      "value": "Chrome"
+                    },
+                    {
+                      "value": "1920*1080"
+                    },
+                    {
+                      "value": "1920*1080",
+                      "condition": "never"
+                    },
+                    {
+                      "key": "Pipeline",
+                      "value": "some-url",
+                      "type": "hyperlink"
+                    },
+                  ]
+                }
+              }
+            ]
+          }
+        ],
+        results: [
+          {
+            type: 'testng',
+            files: [
+              'test/data/testng/single-suite.xml'
+            ]
+          }
+        ]
+      }
+    });
+    assert.equal(mock.getInteraction(id1).exercised, true);
+    assert.equal(mock.getInteraction(id2).exercised, true);
+  });
+
+  afterEach(() => {
+    mock.clearInteractions();
+  });
+
+});
\ No newline at end of file
diff --git a/test/mocks/beats.mock.js b/test/mocks/beats.mock.js
new file mode 100644
index 0000000..7bcb0ad
--- /dev/null
+++ b/test/mocks/beats.mock.js
@@ -0,0 +1,17 @@
+const { addInteractionHandler } = require('pactum').handler;
+
+addInteractionHandler('post test results to testbeats', () => {
+  return {
+    strict: false,
+    request: {
+      method: 'POST',
+      path: '/api/core/v1/test-runs'
+    },
+    response: {
+      status: 200,
+      body: {
+        id: 'test-run-id'
+      }
+    }
+  }
+});
\ No newline at end of file
diff --git a/test/mocks/index.js b/test/mocks/index.js
index 32983eb..6a7fe3e 100644
--- a/test/mocks/index.js
+++ b/test/mocks/index.js
@@ -1,3 +1,4 @@
+require('./beats.mock');
 require('./custom.mock');
 require('./rp.mock');
 require('./slack.mock');
diff --git a/test/mocks/teams.mock.js b/test/mocks/teams.mock.js
index 93f25c7..00b30b0 100644
--- a/test/mocks/teams.mock.js
+++ b/test/mocks/teams.mock.js
@@ -1445,4 +1445,90 @@ addInteractionHandler('post test-summary with metadata to teams', () => {
       status: 200
     }
   }
+});
+
+addInteractionHandler('post test-summary with testbeats to teams', () => {
+  return {
+    request: {
+      method: 'POST',
+      path: '/message',
+      body: {
+        "type": "message",
+        "attachments": [
+          {
+            "contentType": "application/vnd.microsoft.card.adaptive",
+            "content": {
+              "$schema": "http://adaptivecards.io/schemas/adaptive-card.json",
+              "type": "AdaptiveCard",
+              "version": "1.0",
+              "body": [
+                {
+                  "@DATA:TEMPLATE@": "TEAMS_ROOT_TITLE_SINGLE_SUITE"
+                },
+                {
+                  "@DATA:TEMPLATE@": "TEAMS_ROOT_RESULTS_SINGLE_SUITE",
+                },
+                {
+                  "type": "TextBlock",
+                  "text": "[Test Beats Report](http://localhost:9393/reports/test-run-id)",
+                  "wrap": true,
+                  "separator": true
+                }
+              ],
+              "actions": []
+            }
+          }
+        ]
+      }
+    },
+    response: {
+      status: 200
+    }
+  }
+});
+
+addInteractionHandler('post test-summary with extensions and testbeats to teams', () => {
+  return {
+    request: {
+      method: 'POST',
+      path: '/message',
+      body: {
+        "type": "message",
+        "attachments": [
+          {
+            "contentType": "application/vnd.microsoft.card.adaptive",
+            "content": {
+              "$schema": "http://adaptivecards.io/schemas/adaptive-card.json",
+              "type": "AdaptiveCard",
+              "version": "1.0",
+              "body": [
+                {
+                  "@DATA:TEMPLATE@": "TEAMS_ROOT_TITLE_SINGLE_SUITE"
+                },
+                {
+                  "@DATA:TEMPLATE@": "TEAMS_ROOT_RESULTS_SINGLE_SUITE",
+                },
+                {
+                  "type": "TextBlock",
+                  "text": "[Test Beats Report](http://localhost:9393/reports/test-run-id)",
+                  "wrap": true,
+                  "separator": true
+                },
+                {
+                  "type": "TextBlock",
+                  "text": "**Browser:** Chrome ｜ 1920*1080 ｜ [Pipeline](some-url)",
+                  "wrap": true,
+                  "separator": true
+                }
+              ],
+              "actions": []
+            }
+          }
+        ]
+      }
+    },
+    response: {
+      status: 200
+    }
+  }
 });
\ No newline at end of file