Repository: test-results-reporter/reporter
Pull request #117 (2023-02-04 13:39:43)

feat: influx for test results


Patch:
diff --git a/src/index.d.ts b/src/index.d.ts
index f7b0504..2be43c8 100644
--- a/src/index.d.ts
+++ b/src/index.d.ts
@@ -174,6 +174,9 @@ export interface InfluxDBTargetInputs {
   password?: string;
   measurement_perf_run?: string;
   measurement_perf_transaction?: string;
+  measurement_test_run?: string;
+  measurement_test_suite?: string;
+  measurement_test_case?: string;
   tags?: object;
 }
 
diff --git a/src/targets/influx.js b/src/targets/influx.js
index 6e150b0..fdcc5b7 100644
--- a/src/targets/influx.js
+++ b/src/targets/influx.js
@@ -2,6 +2,9 @@ const influx_v1 = require('influxdb-v1');
 const Metric = require('performance-results-parser/src/models/Metric');
 const PerformanceTestResult = require('performance-results-parser/src/models/PerformanceTestResult');
 const Transaction = require('performance-results-parser/src/models/Transaction');
+const TestCase = require('test-results-parser/src/models/TestCase');
+const TestResult = require('test-results-parser/src/models/TestResult');
+const TestSuite = require('test-results-parser/src/models/TestSuite');
 
 
 const { STATUS } = require('../helpers/constants');
@@ -10,6 +13,7 @@ const { STATUS } = require('../helpers/constants');
  * 
  * @param {object} param0 
  * @param {PerformanceTestResult | TestResult} param0.result
+ * @param {import('..').Target} param0.target
  */
 async function run({ result, target }) {
   target.inputs = Object.assign({}, default_inputs, target.inputs);
@@ -25,42 +29,65 @@ async function run({ result, target }) {
   );
 }
 
+/**
+ * 
+ * @param {object} param0 
+ * @param {PerformanceTestResult | TestResult} param0.result
+ * @param {import('..').Target} param0.target
+ */
 function getMetrics({ result, target }) {
   const influx_metrics = [];
   if (result instanceof PerformanceTestResult) {
-    const tags = Object.assign({}, target.inputs.tags);
-    tags.Name = result.name;
-    tags.Status = result.status;
-
-    const fields = {};
-    fields.status = result.status === 'PASS' ? 0 : 1;
-    fields.transactions = result.transactions.length;
-    fields.transactions_passed = result.transactions.filter(_transaction => _transaction.status === "PASS").length;
-    fields.transactions_failed = result.transactions.filter(_transaction => _transaction.status === "FAIL").length;
-
-    for (const metric of result.metrics) {
-      setPerfMetrics(metric, fields);
-    }
-
-    influx_metrics.push({
-      measurement: target.inputs.measurement_perf_run,
-      tags,
-      fields
-    });
+    influx_metrics.push(getPerfRunInfluxMetric({ result, target }));
 
     for (const transaction of result.transactions) {
       influx_metrics.push(getTransactionInfluxMetric(transaction, target));
     }
-
+  } else if (result instanceof TestResult) {
+    influx_metrics.push(getTestInfluxMetric({ result, target }, target.inputs.measurement_test_run));
+    for (const suite of result.suites) {
+      influx_metrics.push(getTestInfluxMetric({ result: suite, target }, target.inputs.measurement_test_suite));
+      for (const test_case of suite.cases) {
+        influx_metrics.push(getTestCaseInfluxMetric({ result: test_case, target }));
+      }
+    }
   }
   return influx_metrics;
 }
 
+/**
+ * @param {object} param0 
+ * @param {PerformanceTestResult} param0.result
+ * @param {import('..').Target} param0.target
+ * @returns 
+ */
+function getPerfRunInfluxMetric({ result, target }) {
+  const tags = Object.assign({}, target.inputs.tags);
+  tags.Name = result.name;
+  tags.Status = result.status;
+
+  const fields = {};
+  fields.status = result.status === 'PASS' ? 0 : 1;
+  fields.transactions = result.transactions.length;
+  fields.transactions_passed = result.transactions.filter(_transaction => _transaction.status === "PASS").length;
+  fields.transactions_failed = result.transactions.filter(_transaction => _transaction.status === "FAIL").length;
+
+  for (const metric of result.metrics) {
+    setPerfInfluxMetricFields(metric, fields);
+  }
+
+  return {
+    measurement: target.inputs.measurement_perf_run,
+    tags,
+    fields
+  }
+}
+
 /**
  * 
  * @param {Metric} metric 
  */
-function setPerfMetrics(metric, fields) {
+function setPerfInfluxMetricFields(metric, fields) {
   let name = metric.name;
   name = name.toLowerCase();
   name = name.replace(' ', '_');
@@ -91,7 +118,7 @@ function getTransactionInfluxMetric(transaction, target) {
   fields.status = transaction.status === 'PASS' ? 0 : 1;
 
   for (const metric of transaction.metrics) {
-    setPerfMetrics(metric, fields);
+    setPerfInfluxMetricFields(metric, fields);
   }
 
   return {
@@ -101,6 +128,53 @@ function getTransactionInfluxMetric(transaction, target) {
   }
 }
 
+/**
+ * @param {object} param0 
+ * @param {TestResult | TestSuite} param0.result
+ * @param {import('..').Target} param0.target
+ * @returns 
+ */
+function getTestInfluxMetric({ result, target }, measurement) {
+  const tags = Object.assign({}, target.inputs.tags);
+  tags.Name = result.name;
+  tags.Status = result.status;
+
+  const fields = {};
+  fields.status = result.status === 'PASS' ? 0 : 1;
+  fields.total = result.total;
+  fields.passed = result.passed;
+  fields.failed = result.failed;
+  fields.duration = result.duration;
+
+  return {
+    measurement,
+    tags,
+    fields
+  }
+}
+
+/**
+ * @param {object} param0 
+ * @param {TestCase} param0.result
+ * @param {import('..').Target} param0.target
+ * @returns 
+ */
+function getTestCaseInfluxMetric({ result, target }) {
+  const tags = Object.assign({}, target.inputs.tags);
+  tags.Name = result.name;
+  tags.Status = result.status;
+
+  const fields = {};
+  fields.status = result.status === 'PASS' ? 0 : 1;
+  fields.duration = result.duration;
+
+  return {
+    measurement: target.inputs.measurement_test_case,
+    tags,
+    fields
+  }
+}
+
 
 const default_inputs = {
   url: '',
@@ -109,6 +183,9 @@ const default_inputs = {
   password: '',
   measurement_perf_run: 'PerfRun',
   measurement_perf_transaction: 'PerfTransaction',
+  measurement_test_run: 'TestRun',
+  measurement_test_suite: 'TestSuite',
+  measurement_test_case: 'TestCase',
   tags: {}
 }
 


Test patch:
diff --git a/test/mocks/influx.mock.js b/test/mocks/influx.mock.js
index b3e563f..d4a299d 100644
--- a/test/mocks/influx.mock.js
+++ b/test/mocks/influx.mock.js
@@ -36,4 +36,42 @@ addInteractionHandler('save perf results with custom tags', () => {
       status: 200
     }
   }
+});
+
+addInteractionHandler('save test results', () => {
+  return {
+    request: {
+      method: 'POST',
+      path: '/write',
+      headers: {
+        "authorization": "Basic dXNlcjpwYXNz"
+      },
+      queryParams: {
+        "db": "TestResults"
+      },
+      body: "TestRun,Name=Default\\ suite,Status=PASS status=0,total=4,passed=4,failed=0,duration=2000\nTestSuite,Name=Default\\ test,Status=PASS status=0,total=4,passed=4,failed=0,duration=2000\nTestCase,Name=c2,Status=PASS status=0,duration=0\nTestCase,Name=c3,Status=PASS status=0,duration=10\nTestCase,Name=c1,Status=PASS status=0,duration=0\nTestCase,Name=c4,Status=PASS status=0,duration=0"
+    },
+    response: {
+      status: 200
+    }
+  }
+});
+
+addInteractionHandler('save test results with custom tags', () => {
+  return {
+    request: {
+      method: 'POST',
+      path: '/write',
+      headers: {
+        "authorization": "Basic dXNlcjpwYXNz"
+      },
+      queryParams: {
+        "db": "TestResults"
+      },
+      body: "TestRun,Team=QA,App=PactumJS,Name=Staging\\ -\\ UI\\ Smoke\\ Test\\ Run,Status=FAIL status=1,total=2,passed=1,failed=1,duration=1883597\nTestSuite,Team=QA,App=PactumJS,Name=desktop-chrome,Status=PASS status=0,total=1,passed=1,failed=0,duration=1164451\nTestCase,Team=QA,App=PactumJS,Name=GU,Status=PASS status=0,duration=243789\nTestSuite,Team=QA,App=PactumJS,Name=mobile-andoid,Status=FAIL status=1,total=1,passed=0,failed=1,duration=714100\nTestCase,Team=QA,App=PactumJS,Name=GU,Status=FAIL status=1,duration=156900"
+    },
+    response: {
+      status: 200
+    }
+  }
 });
\ No newline at end of file
diff --git a/test/targets.influx.spec.js b/test/targets.influx.spec.js
index 0be6f36..0bfbf1c 100644
--- a/test/targets.influx.spec.js
+++ b/test/targets.influx.spec.js
@@ -76,4 +76,80 @@ describe('targets - influx - performance', () => {
     mock.clearInteractions();
   });
 
+});
+
+describe('targets - influx - functional', () => {
+
+  it('should save results', async () => {
+    const id = mock.addInteraction('save test results');
+    await publish({
+      config: {
+        "reports": [
+          {
+            "targets": [
+              {
+                "name": "influx",
+                "inputs": {
+                  "url": "http://localhost:9393",
+                  "db": "TestResults",
+                  "username": "user",
+                  "password": "pass"
+                }
+              }
+            ],
+            "results": [
+              {
+                "type": "testng",
+                "files": [
+                  "test/data/testng/single-suite.xml"
+                ]
+              }
+            ]
+          }
+        ]
+      }
+    });
+    assert.equal(mock.getInteraction(id).exercised, true);
+  });
+
+  it('should save results with custom tags', async () => {
+    const id = mock.addInteraction('save test results with custom tags');
+    await publish({
+      config: {
+        "reports": [
+          {
+            "targets": [
+              {
+                "name": "influx",
+                "inputs": {
+                  "url": "http://localhost:9393",
+                  "db": "TestResults",
+                  "username": "user",
+                  "password": "pass",
+                  "tags": {
+                    "Team": "QA",
+                    "App": "PactumJS"
+                  }
+                }
+              }
+            ],
+            "results": [
+              {
+                "type": "testng",
+                "files": [
+                  "test/data/testng/multiple-suites-failures.xml"
+                ]
+              }
+            ]
+          }
+        ]
+      }
+    });
+    assert.equal(mock.getInteraction(id).exercised, true);
+  });
+
+  afterEach(() => {
+    mock.clearInteractions();
+  });
+
 });
\ No newline at end of file