UNPKG

gpii-universal

Version:

Cross platform, core components of the GPII personalization infrastructure.

312 lines (269 loc) 13.9 kB
/** GPII Testing (full lifecycle of FlowManager) Copyright 2013-2016 Raising the Floor International Copyright 2013 OCAD University Copyright 2014 Lucendo Development Ltd. Licensed under the New BSD license. You may not use this file except in compliance with this License. The research leading to these results has received funding from the European Union's Seventh Framework Programme (FP7/2007-2013) under grant agreement no. 289016. You may obtain a copy of the License at https://github.com/GPII/universal/blob/master/LICENSE.txt */ "use strict"; var fluid = fluid || require("infusion"), jqUnit = jqUnit || fluid.require("node-jqunit", require, "jqUnit"), kettle = fluid.registerNamespace("kettle"), gpii = fluid.registerNamespace("gpii"); // TODO: Move all of this file into a more specific namespace - "test" is too generic // Given a promise-containing structure, return a promise yielding the structure with all promises replaced by their // resolution, or else a rejection for the first to reject. The supplied structure will be modified. // TODO: This algorithm will go into FluidPromises.js in the end gpii.test.settleStructure = function (structure) { if (fluid.isPromise(structure)) { return structure; } var settleRec = { // This flag is an awkward consequence of our choice to allow synchronous promise resolution inSync: true, // Are we still in synchronous scanning - necessary to avoid double resolution on hitting unresolved === 0 on initial scan unresolved: 0, depth: 0, togo: fluid.promise(), structure: structure, resolve: function () { settleRec.togo.resolve(structure); } }; gpii.test.settleStructureRecurse(structure, settleRec); settleRec.inSync = false; if (settleRec.unresolved === 0) { // Case of 0 asynchronous promises found settleRec.resolve(); } return settleRec.togo; }; gpii.test.settleStructureRecurse = function (structure, settleRec) { ++settleRec.depth; if (settleRec.depth > fluid.strategyRecursionBailout) { fluid.fail("Recursion exceeded for value " + JSON.stringify(structure) + " overall structure " + JSON.stringify(settleRec.structure, null, 2)); } if (fluid.isPlainObject(structure)) { fluid.each(structure, function (value, key) { if (fluid.isPromise(value)) { gpii.test.settleStructurePush(settleRec, structure, value, key); } else { gpii.test.settleStructureRecurse(value, settleRec); } }); } --settleRec.depth; }; gpii.test.settleStructurePush = function (settleRec, holder, promise, key) { ++settleRec.unresolved; promise.then(function (value) { fluid.log("settleStructure received response " + fluid.prettyPrintJSON(value)); holder[key] = value; --settleRec.unresolved; if (settleRec.unresolved === 0 && !settleRec.inSync) { settleRec.resolve(); } }, function (err) { var errObj = { message: err }; settleRec.togo.reject(kettle.upgradeError(errObj, " while resolving promise with key " + key)); }); }; // END ALGORITHM gpii.test.settleStructure // TODO: Move this utility into KettleTestUtils.http.js /** Asserts that a successful response with a JSON payload body has been received, which will be tested using `jqUnit.assertLeftHand` * In addition to the core fields listed KettleTestUtils.http.js HTTP response assertion functions, options contains: * expected {Object} The expected response body as JSON, supplied as the `expected` argument to `jqUnit.assertLeftHand` */ gpii.test.assertJSONResponseSubset = function (options) { var data; try { data = kettle.JSON.parse(options.string); } catch (e) { throw kettle.upgradeError(e, "\nwhile parsing HTTP response as JSON"); } jqUnit.assertLeftHand(options.message, options.expected, data); kettle.test.assertResponseStatusCode(options, 200); }; /** Given a settingsHandler payload, return a promise which resolves to the current state of each * settingsHandler block within it. Called from `gpii.test.snapshotSettings`, `gpii.test.checkConfiguration` and `gpii.test.checkRestoredConfiguration` * @param {Object} settingsHandlers - A map of settings handler names to `settingsHandler` blocks as seen in the `settingsHandlers` * option of a `gpii.test.testCaseHolder`. * @param {NameResolver|Null} nameResolver - A `nameResolver` mapping global names onto alternatives for use in a testing context. * @return {Promise} A promise resolving to the fetched settings (a fatal error handler will be registered on reject). */ gpii.test.getSettings = function (settingsHandlers, nameResolver) { return gpii.test.operateSettings(settingsHandlers, nameResolver, "get"); }; /** As for `gpii.test.getSettings` but performing a `set` action for the settingsHandler payloads, and accepting an * `onSuccess` callback */ gpii.test.setSettings = function (settingsHandlers, nameResolver, onSuccess) { var promise = gpii.test.operateSettings(settingsHandlers, nameResolver, "set"); promise.then(onSuccess); return promise; }; /** Common utility forwarded to by `gpii.test.getSettings` and `gpii.test.setSettings`. * See documentation for all parameters other than: * @param {Array} settingsHandlers - An array of settings handlers. * @param {Object} nameResolver - The name resolver. * @param {String} method - Either `"get"` or `"set"` depending on whether the settingsHandler `get` or `set` action is to be invoked. * @return {Promise} A promise that will be resolved with the result or rejected on error. */ gpii.test.operateSettings = function (settingsHandlers, nameResolver, method) { var ret = {}; fluid.each(settingsHandlers, function (handlerBlock, handlerID) { var resolvedName = nameResolver ? nameResolver.resolveName(handlerID, "settingsHandler") : handlerID; var response = gpii.settingsHandlers.dispatchSettingsHandler(resolvedName, handlerBlock, method); ret[handlerID] = response; }); var togo = gpii.test.settleStructure(ret); togo.then(null, function (err) { fluid.fail("Error when operating settings handler: ", err); }); return togo; }; /* * Used for settings the initial settings of an application. This is among other used for tests * where an application needs to be running on user login */ gpii.test.setInitialSettingsState = function (settingsHandlers, nameResolver, onComplete) { var promise = gpii.test.setSettings(settingsHandlers, nameResolver); promise.then(onComplete); }; /** Snapshot the state of all settingsHandlers by stashing them in a member named `orig` on the supplied settingsStore * @param {Object} settingsHandlers - A map of settings handler names to `settingsHandler` blocks as seen in the `settingsHandlers` * option of a `gpii.test.testCaseHolder`. * @param {Object} settingsStore - The `settingsStore` member of a `gpii.test.testCaseHolder`. This will have a snapshot of * the state of the supplied settingsHandlers stored in a member named `orig`. * @param {NameResolver|Null} nameResolver - A `nameResolver` mapping global names onto alternatives for use in a testing context. * @param {Function} onComplete - A callback to be notified when the state of all the supplied settingsHandlers have been read. */ gpii.test.snapshotSettings = function (settingsHandlers, settingsStore, nameResolver, onComplete) { var origPromise = gpii.test.getSettings(settingsHandlers, nameResolver); origPromise.then(function (origSettings) { // any settings already defined in settingsStore.org overwrites the ones read by snapshotter settingsStore.orig = fluid.extend(true, origSettings, settingsStore.orig); onComplete(); }); }; gpii.test.loginRequestListen = function (data) { jqUnit.assertNotEquals("Successful login message returned " + data, -1, data.indexOf("was successfully logged in.")); }; gpii.test.extractSettingsBlocks = function (settingsHandlers) { return fluid.transform(settingsHandlers, gpii.settingsHandlers.extractSettingsBlocks); }; gpii.test.checkConfiguration = function (settingsHandlers, nameResolver, onComplete, customMsg) { var configPromise = gpii.test.getSettings(settingsHandlers, nameResolver); configPromise.then(function (config) { var noOptions = gpii.test.extractSettingsBlocks(settingsHandlers); jqUnit.assertDeepEq(customMsg || "Checking that settings are set", noOptions, config); onComplete(); }); }; gpii.test.logoutRequestListen = function (data) { jqUnit.assertNotEquals("Successful logout message returned " + data, -1, data.indexOf("was successfully logged out.")); }; gpii.test.checkRestoredConfiguration = function (settingsHandlers, settingsStore, nameResolver, onComplete) { var currentSettingsPromise = gpii.test.getSettings(settingsHandlers, nameResolver); currentSettingsPromise.then(function (currentSettings) { jqUnit.assertDeepEq("Checking that settings are properly reset", settingsStore.orig, currentSettings); onComplete(); }); }; gpii.test.checkRestoredInitialState = function (initialSettings, nameResolver, onComplete) { if (initialSettings === undefined) { jqUnit.assertTrue("No initial settings defined, so not checking", true); onComplete(); return; } var currentSettingsPromise = gpii.test.getSettings(initialSettings, nameResolver); currentSettingsPromise.then(function (currentSettings) { jqUnit.assertDeepEq("Checking that initial settings are also properly reset", gpii.test.extractSettingsBlocks(initialSettings), currentSettings); onComplete(); }); }; gpii.test.common.receiveVariableResolver = function (testCaseHolder, variableResolver) { testCaseHolder.variableResolver = variableResolver; }; fluid.defaults("gpii.test.common.loginRequestComponent", { // named oddly to avoid name conflicts with component whose member name is `loginRequest` gradeNames: "kettle.test.request.http", path: "/user/%gpiiKey/login", termMap: { gpiiKey: "{testCaseHolder}.options.gpiiKey" } }); fluid.defaults("gpii.test.common.logoutRequestComponent", { gradeNames: "kettle.test.request.http", path: "/user/%gpiiKey/logout", termMap: { gpiiKey: "{testCaseHolder}.options.gpiiKey" } }); /** mixin grades to hoist out the lifecycleManager, to a location where it can be * easily resolved by various test fixtures. This will become unnecessary after the FLUID-4892 rewrite of Infusion */ fluid.defaults("gpii.test.common.lifecycleManagerReceiver", { distributeOptions: { record: { funcName: "gpii.test.common.receiveComponent", args: ["{testCaseHolder}", "{arguments}.0", "lifecycleManager"] }, target: "{that lifecycleManager}.options.listeners.onCreate" } }); fluid.defaults("gpii.test.common.untrusted.lifecycleManagerReceiver", { distributeOptions: { record: { funcName: "gpii.test.common.receiveComponent", args: ["{testCaseHolder}", "{arguments}.0", "lifecycleManager"] }, target: "{that localConfig lifecycleManager}.options.listeners.onCreate" } }); gpii.test.common.receiveComponent = function (testCaseHolder, component, name) { if (!testCaseHolder[name]) { fluid.globalInstantiator.recordKnownComponent(testCaseHolder, component, name, false); } }; gpii.test.leakageCheckGrades = ["gpii.test.integration.mockSettingsHandler"]; gpii.test.sequenceNumberForComponent = function (component) { return Number(component.id.split("-").pop()); }; // Check for any leaks of components of particular grades that might have escaped cleanup from a previous test cycle. // This uses a strategy of inspecting the component ids of all components matching a particular grade, and asserting // that the id sequence numbers must not be earlier than those of the current testEnvironment. This has been // introduced to assure that, for example, cleanup is done as part of GPII-4039 gpii.test.assessComponentLeaks = function (testEnv) { var rootSequence = gpii.test.sequenceNumberForComponent(testEnv); gpii.test.leakageCheckGrades.forEach(function (checkGrade) { var checkComponents = fluid.queryIoCSelector(fluid.rootComponent, checkGrade); checkComponents.forEach(function (checkComponent) { var checkSequence = gpii.test.sequenceNumberForComponent(checkComponent); jqUnit.assertTrue("Component of grade " + checkGrade + " should not predate test environment", checkSequence > rootSequence); }); }); }; /** Expand material in a `gpii.test.testCaseHolder` fixture's options using the variableResolver fished out of the real implementation, * and place it at a public member for the assertions to use * @param {Component} testCaseHolder - An instance of gpii.test.testCaseHolder component with some expandable material in its options. * @param {String|Array} members - One or more paths in the options to be expanded. * @return {Component} *side-effect* - expanded material is placed at top-level in the supplied `testCaseHolder` at the paths given in the `members` argument. */ gpii.test.expandSettings = function (testCaseHolder, members) { members = fluid.makeArray(members); fluid.each(members, function (val) { testCaseHolder[val] = testCaseHolder.variableResolver.resolve(testCaseHolder.options[val]); }); }; gpii.test.unshift = function (array, elements) { array.unshift.apply(array, elements); }; gpii.test.push = function (array, elements) { array.push.apply(array, elements); };