Skip to content
This repository has been archived by the owner on Feb 22, 2023. It is now read-only.

Creating new function to push test results after executed with bst CLI #494

Merged
merged 6 commits into from
Dec 14, 2021
13 changes: 6 additions & 7 deletions lib/runner/CLI.js
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ const jestModule = require("jest");
const LoggingErrorHelper = require("../util/LoggingErrorHelper");
const path = require("path");
const uuid = require("uuid/v4");
const { ResultsPublisher } = require("../runner/ResultsPublisher");

const Logger = "bst-test";

Expand Down Expand Up @@ -64,20 +65,18 @@ class CLI {
|| invoker === CONSTANTS.INVOKER.virtualDeviceInvoker
|| invoker === CONSTANTS.INVOKER.SMAPIInvoker;

if (isRunningRemote){
if (isRunningRemote) {
jestConfig.collectCoverage = false;
}

jestConfig.globals = { overrides: configurationOverrides };
debug("JEST Config: " + JSON.stringify(jestConfig));

const config = JSON.stringify(jestConfig);
// Call Jest via API so we can stay in-process
const returnValue = await jestModule.runCLI({
config: JSON.stringify(jestConfig),
runInBand,
}, [process.cwd()]);
const success = returnValue.results ? returnValue.results.success : false;
return success;
return jestModule.runCLI({ config, runInBand }, [process.cwd()])
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this method has changed the return type of the function, before the change a boolean was the result of the function, now it is a promise. Make sure this change does not break how jest interact with our code

.then(jestResult => new ResultsPublisher().publishResults(jestResult).then(() => jestResult))
.then(jestResult => jestResult.results ? jestResult.results.success : false);
}

printVersion() {
Expand Down
25 changes: 18 additions & 7 deletions lib/runner/JestAdapter.js
Original file line number Diff line number Diff line change
Expand Up @@ -20,15 +20,26 @@ module.exports = async function testRunner(globalConfig, config, environment, ru
await Configuration.configure(undefined, undefined, _.get(config, "globals.overrides"));
const runner = new TestRunner(Configuration.instance().skillTestingConfig());

const bespokenExtensions = { bespokenResults: null };
let jestResults;
let passing = 0;
let failing = 0;
let pending = 0;

let doResultsHaveErrorMessages = false;

try {
const results = await runner.run(testPath);

bespokenExtensions.bespokenResults = Util.cloneWithoutCircularReference(
_.cloneDeep(results).map((e) => {
// clone some circular dependencies attributes before dropping them
_.set(e, "_test", _.cloneDeep(e._test));
_.set(e, "_test._testSuite", _.omit(_.cloneDeep(e._test._testSuite), ["tests"]));
_.set(e, "_test._testSuite._configuration", _.cloneDeep(e._test._testSuite._configuration));
return e;
})
);

jestResults = transformResults(results);

// Summarize the results
Expand Down Expand Up @@ -58,7 +69,7 @@ module.exports = async function testRunner(globalConfig, config, environment, ru
testPath,
);

return {
return _.assign({
console: null,
displayName: "Display name",
failureMessage,
Expand All @@ -82,7 +93,7 @@ module.exports = async function testRunner(globalConfig, config, environment, ru
testExecError: undefined,
testFilePath: testPath,
testResults: jestResults,
};
}, bespokenExtensions);
};

function transformResults(results) {
Expand Down Expand Up @@ -119,19 +130,19 @@ function addTimestampToError(errorMessage, timestamp) {
return error;
}

function getError(error){
function getError(error) {
let objectError = undefined;
try {
objectError = JSON.parse(error);
} catch (_e) {
objectError = error;
}

if (typeof(objectError) === "string") {
if (typeof (objectError) === "string") {
return objectError;
} else if (typeof(objectError) === "object") {
} else if (typeof (objectError) === "object") {
if (objectError.error) {
if (typeof(objectError.error) === "string") {
if (typeof (objectError.error) === "string") {
return objectError.error;
} else if (Array.isArray(objectError.error)) {
return objectError.error.join(", ");
Expand Down
169 changes: 169 additions & 0 deletions lib/runner/ResultsPublisher.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,169 @@
const _ = require("lodash");
const HTTP = require("../util/HTTP");
const LoggingErrorHelper = require("../util/LoggingErrorHelper");
const osPath = require("path");

const buildTestResult = (testResult) => {
const start_timestamp = _.get(testResult, "_stopWatch.start", null);
const end_timestamp = _.get(testResult, "_stopWatch.end", null);
const test_name = _.get(testResult, "_test._description", "<no _test._description provided>").toString();

const tags = _.castArray(_.get(testResult, "_test._tags", [])).map(s => s.toString());
const raw_response_json = _.get(testResult, "_interactionResults", [{ error: "<no [${i}].interactionResults>" }]).map(e => e._rawResponse);
const raw_config_json = _.omit(_.get(testResult, "_test._testSuite._configuration", { error: "<no _testSuite._configuration>" }), ["_yaml"]);

const raw_json = JSON.stringify(testResult);
const skipped = _.get(testResult, "_test._skip", false);

const project_id = _.get(testResult, "_test._testSuite._projectId");
const bespoken_project_id = _.get(testResult, "_test._testSuite._bespokenProjectId");
const customer_id = _.get(testResult, "_test._testSuite._customerId");
const test_run_id = _.get(testResult, "_test._testSuite._runId");

const origin = _.get(testResult, "_test._testSuite._origin");
const platform = _.get(testResult, "_test._testSuite._platform");

const locale = _.get(testResult, "_test._testSuite._configuration.locale");
const voice_id = _.get(testResult, "_test._testSuite._configuration.voiceId");
const test_suite_name = _.get(testResult, "_test._testSuite._description", "").toString();
const test_suite_filename = osPath.relative(process.cwd(), _.get(testResult, "_test._testSuite._fileName", ""));


return {
bespoken_project_id,
customer_id,
end_timestamp,
locale,
origin,
platform,
project_id,
raw_config_json,
raw_json,
raw_response_json,
skipped,
start_timestamp,
tags,
test_name,
test_run_id,
test_suite_filename,
test_suite_name,
voice_id,
};
};

const buildTestInteractionResult = testInteractionResult => (testInteraction, index) => {
const _interactionResult = _.get(testInteractionResult, `[${index}]`, {});
const interactionDto = _.get(_interactionResult, "interactionDto", {});

const message = _.get(testInteraction, "_utterance", "<no utterance>");
const transcript = _.get(_interactionResult, "_rawResponse.transcript", "<no rawResponse.transcript>");
const display = _.get(_interactionResult, "_rawResponse.display", "<no display provided>");
const assertion = _.get(interactionDto, "assertions", "<no utterance>");
const result = _.get(interactionDto, "result.passed", null) ? "PASSED" : "FAILED";
const raw_response = _.get(_interactionResult, "_rawResponse", null);

return {
assertion,
display,
message,
raw_response,
result,
transcript,
};
};

const transformIntoPayload = (data) => {
const test_results = _.get(data, "results.testResults", [])
// gather all bespoken results of every test suite execution
.map(e => e.bespokenResults)
// join all executed test from different test suites
.reduce((p, c) => p.concat(c), [])
// prepare test data
.map((e) => {
const testResultProperties = buildTestResult(e);
const test_interaction_results = _.get(e, "_test._interactions", [])
.map(buildTestInteractionResult(_.get(e, "_interactionResults", [])));
const result = !test_interaction_results
.filter(e => !e.skipped)
.reduce((previous, current, index, all) => all, [{}])
.some(e => e.result !== true) ? "PASSED" : "FAILED";

return _.assign({
result,
test_interaction_results,
}, testResultProperties);
});

const {
bespoken_project_id,
customer_id,
origin,
platform,
project_id,
test_run_id,
} = _.get(test_results, "[0]", {});

const { 0: start_timestamp, 1: end_timestamp } = test_results
.map(e => [e.start_timestamp, e.end_timestamp])
.reduce((p, c) => p.concat(c), [])
.map(e => new Date(e))
.sort()
.filter((_, i, a) => i === 0 || i === a.length - 1);


const raw_json = null;

const test_suites_passed = _.get(data, "results.numPassedTestSuites", 0);
const test_suites_failed = _.get(data, "results.numFailedTestSuites", 0);
const test_suites_skipped = _.get(data, "results.numPendingTestSuites", 0);
const tests_passed = _.get(data, "results.numPassedTests", 0);
const tests_failed = _.get(data, "results.numFailedTests", 0);
const tests_skipped = _.get(data, "results.numPendingTests", 0);
const result = _.get(data, "results.success", false) ? "PASSED" : "FAILED";

return {
bespoken_project_id,
customer_id,
end_timestamp,
origin,
platform,
project_id,
raw_json,
result,
start_timestamp,
test_results,
test_run_id,
test_suites_failed,
test_suites_passed,
test_suites_skipped,
tests_failed,
tests_passed,
tests_skipped,
};
};

const postDataIntoEndpoint = async (payload,
baseUrl = process.env.BESPOKEN_API_BASE_URL || "https://bespoken-api.bespoken.tools",
method = "POST",
headers = { "Content-Type": "application/json" }) => {
// eslint-disable-next-line
const { hostname: host, port, protocol, pathname: path } = new URL(osPath.join(baseUrl, "/source/testRunHistory"));
return HTTP.post({ headers, host, method, path, port, protocol }, payload);
};

class ResultsPublisher {
async publishResults(data) {
return Promise.resolve()
.then(() => transformIntoPayload(data))
.then(payload => postDataIntoEndpoint(payload))
.catch(err => LoggingErrorHelper.error("bst-test", `Error saving test results Raw Error: ${JSON.stringify(err)}`));
}
}

module.exports = {
ResultsPublisher,
buildTestInteractionResult,
buildTestResult,
postDataIntoEndpoint,
transformIntoPayload,
};
Loading