From 5fb50a1759b5b0ab043b30ed7d08b24b2de7029c Mon Sep 17 00:00:00 2001 From: d98762625 Date: Thu, 3 May 2018 10:20:13 +0100 Subject: [PATCH] OperationErrors now bubble up to the top of the API. Added test functionality for node api refactor TestRegister into class --- .gitignore | 1 + src/node/apiUtils.mjs | 31 +++++++++++--- test/TestRegister.mjs | 71 +++++++++++++++++++++++---------- test/index.mjs | 13 ++++-- test/tests/assertionHandler.mjs | 45 +++++++++++++++++++++ test/tests/nodeApi/nodeApi.mjs | 63 +++++++++++++++++++++++++++++ 6 files changed, 194 insertions(+), 30 deletions(-) create mode 100644 test/tests/assertionHandler.mjs create mode 100644 test/tests/nodeApi/nodeApi.mjs diff --git a/.gitignore b/.gitignore index 9ea869e3..2c7700d4 100755 --- a/.gitignore +++ b/.gitignore @@ -10,3 +10,4 @@ src/core/config/modules/* src/core/config/OperationConfig.json src/core/operations/index.mjs +**/*.DS_Store \ No newline at end of file diff --git a/src/node/apiUtils.mjs b/src/node/apiUtils.mjs index a4154be2..f2f8db1f 100644 --- a/src/node/apiUtils.mjs +++ b/src/node/apiUtils.mjs @@ -7,7 +7,6 @@ */ import Dish from "../core/Dish"; -import log from "loglevel"; /** * Extract default arg value from operation argument @@ -33,13 +32,21 @@ export function wrap(Operation) { /** * Wrapped operation run function */ - return async (input, args=null) => { + return async (input, args=null, callback) => { + + if (callback && typeof callback !== "function") { + throw TypeError("Expected callback to be a function"); + } + + if (!callback && typeof args === "function") { + callback = args; + args = null; + } + const operation = new Operation(); const dish = new Dish(); - // Stolen from Recipe. Only works there as raw input is one - // of these types. consider a mapping for all use cases like below. - const type = input instanceof ArrayBuffer ? Dish.ARRAY_BUFFER : Dish.STRING; + const type = Dish.typeEnum(input.constructor.name); dish.set(input, type); if (!args) { @@ -51,10 +58,22 @@ export function wrap(Operation) { } } const transformedInput = await dish.get(operation.inputType); - return operation.run(transformedInput, args); + + // Allow callback or promsise / async-await + if (callback) { + try { + const out = operation.run(transformedInput, args); + callback(null, out); + } catch (e) { + callback(e); + } + } else { + return operation.run(transformedInput, args); + } }; } + /** * First draft * @param input diff --git a/test/TestRegister.mjs b/test/TestRegister.mjs index c4b8553d..9ed37422 100644 --- a/test/TestRegister.mjs +++ b/test/TestRegister.mjs @@ -5,37 +5,49 @@ * ensure that they will get run by the frontend. * * @author tlwr [toby@toby.codes] - * @copyright Crown Copyright 2017 + * @author d98762625 [d98762625@gmail.com] + * @copyright Crown Copyright 2018 * @license Apache-2.0 */ import Chef from "../src/core/Chef"; -(function() { - /** - * Object to store and run the list of tests. - * - * @class - * @constructor - */ - function TestRegister() { - this.tests = []; - } +/** + * Object to store and run the list of tests. + * + * @class + * @constructor + */ +class TestRegister { + /** + * initialise with no tests + */ + constructor() { + this.tests = []; + this.apiTests = []; + } /** * Add a list of tests to the register. * * @param {Object[]} tests */ - TestRegister.prototype.addTests = function(tests) { + addTests(tests) { this.tests = this.tests.concat(tests); - }; + } + /** + * Add a list of api tests to the register + * @param {Object[]} tests + */ + addApiTests(tests) { + this.apiTests = this.apiTests.concat(tests); + } /** * Runs all the tests in the register. */ - TestRegister.prototype.runTests = function() { + runTests () { return Promise.all( this.tests.map(function(test, i) { const chef = new Chef(); @@ -81,12 +93,29 @@ import Chef from "../src/core/Chef"; }); }) ); - }; + } + /** + * Run all api related tests and wrap results in report format + */ + runApiTests() { + return Promise.all(this.apiTests.map(async function(test, i) { + const result = { + test: test, + status: null, + output: null, + }; + try { + await test.run(); + result.status = "passing"; + } catch (e) { + result.status = "erroring"; + result.output = e.message; + } + return result; + })); + } +} - // Singleton TestRegister, keeping things simple and obvious. - global.TestRegister = global.TestRegister || new TestRegister(); -})(); - -export default global.TestRegister; - +// Export an instance to make a singleton +export default new TestRegister(); diff --git a/test/index.mjs b/test/index.mjs index dd592f6f..c3896602 100644 --- a/test/index.mjs +++ b/test/index.mjs @@ -55,6 +55,9 @@ import "./tests/operations/SymmetricDifference"; import "./tests/operations/CartesianProduct"; import "./tests/operations/PowerSet"; + +import "./tests/nodeApi/nodeApi"; + let allTestsPassing = true; const testStatusCounts = { total: 0, @@ -112,9 +115,12 @@ setTimeout(function() { process.exit(1); }, 10 * 1000); - -TestRegister.runTests() - .then(function(results) { +Promise.all([ + TestRegister.runTests(), + TestRegister.runApiTests() +]) + .then(function(resultsPair) { + const results = resultsPair[0].concat(resultsPair[1]); results.forEach(handleTestResult); console.log("\n"); @@ -132,3 +138,4 @@ TestRegister.runTests() process.exit(allTestsPassing ? 0 : 1); }); + diff --git a/test/tests/assertionHandler.mjs b/test/tests/assertionHandler.mjs new file mode 100644 index 00000000..6bf236c6 --- /dev/null +++ b/test/tests/assertionHandler.mjs @@ -0,0 +1,45 @@ +/** + * assertionHandler.mjs + * + * Pair native node assertions with a description for + * the benefit of the TestRegister. + * + * @author d98762625 [d98762625@gmail.com] + * @copyright Crown Copyright 2018 + * @license Apache-2.0 + */ + +import assert from "assert"; + +/** + * it - wrapper for assertions to provide a helpful description + * to the TestRegister + * @param {String} description - The description of the test + * @param {Function} assertion - The test + * + * @example + * // One assertion + * it("should run one assertion", () => assert.equal(1,1)) + * + * @example + * // multiple assertions + * it("should handle multiple assertions", () => { + * assert.equal(1,1) + * assert.notEqual(3,4) + * }) + * + * @example + * // async assertions + * it("should handle async", async () => { + * let r = await asyncFunc() + * assert(r) + * }) + */ +export function it(name, run) { + return { + name, + run + }; +} + +export default it; diff --git a/test/tests/nodeApi/nodeApi.mjs b/test/tests/nodeApi/nodeApi.mjs new file mode 100644 index 00000000..b1d92350 --- /dev/null +++ b/test/tests/nodeApi/nodeApi.mjs @@ -0,0 +1,63 @@ +/* eslint no-console: 0 */ + +/** + * nodeApi.js + * + * Test node api utilities + * + * @author d98762625 [d98762625@gmail.com] + * @copyright Crown Copyright 2018 + * @license Apache-2.0 + */ + +import assert from "assert"; +import it from "../assertionHandler"; +import chef from "../../../src/node/index"; +import TestRegister from "../../TestRegister"; + +TestRegister.addApiTests([ + it("should have some operations", () => { + assert(chef); + assert(chef.toBase32); + assert(chef.setUnion); + assert(!chef.randomFunction); + }), + + it("should have an async/await api", async () => { + try { + const result = await chef.toBase32("input"); + assert.notEqual("something", result); + } catch (e) { + // shouldnt reach here + assert(false); + } + + try { + const fail = chef.setUnion("1"); + // shouldnt get here + assert(false); + } catch (e) { + assert(true); + } + }), + + it("should have a callback API", async () => { + await chef.toBase32("something", (err, result) => { + if (err) { + assert(false); + } else { + assert.equal("ONXW2ZLUNBUW4ZY=", result); + } + }); + }), + + it("should handle errors in callback API", async () => { + await chef.setUnion("1", (err, result) => { + if (err) { + assert(true); + return; + } + assert(false); + }); + }) +]);