From 3b4c3a57a71e7e88ecce4f3e05273fdeff578573 Mon Sep 17 00:00:00 2001 From: Joseph Scheuhammer Date: Fri, 17 Jun 2016 15:52:43 -0400 Subject: [PATCH 1/8] GPII-1839: Acceptance tests should use the dynamic device reporter. Modified "basic" configuration files such that the device reporter is configured/loaded in production mode as opposed to development mode. --- gpii/configs/gpii.config.cloudBased.development.all.local.json | 2 +- tests/configs/gpii.tests.acceptance.localInstall.config.json | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/gpii/configs/gpii.config.cloudBased.development.all.local.json b/gpii/configs/gpii.config.cloudBased.development.all.local.json index 9e696d993..213717110 100644 --- a/gpii/configs/gpii.config.cloudBased.development.all.local.json +++ b/gpii/configs/gpii.config.cloudBased.development.all.local.json @@ -22,6 +22,6 @@ "%preferencesServer/configs/gpii.preferencesServer.config.development.json", "%flatMatchMaker/configs/gpii.flatMatchMaker.config.development.json", "%rawPreferencesServer/configs/gpii.rawPreferencesServer.config.development.json", - "%deviceReporter/configs/gpii.deviceReporter.config.development.json" + "%deviceReporter/configs/gpii.deviceReporter.config.production.json" ] } diff --git a/tests/configs/gpii.tests.acceptance.localInstall.config.json b/tests/configs/gpii.tests.acceptance.localInstall.config.json index 748674456..9d0d4dce9 100644 --- a/tests/configs/gpii.tests.acceptance.localInstall.config.json +++ b/tests/configs/gpii.tests.acceptance.localInstall.config.json @@ -9,5 +9,5 @@ } } }, - "mergeConfigs": "%universal/gpii/configs/gpii.config.development.all.local.json" + "mergeConfigs": "%universal/gpii/configs/gpii.config.all.development.dr.production.json" } From 3290972960c78392ed4a71248804ba789c724bcb Mon Sep 17 00:00:00 2001 From: Joseph Scheuhammer Date: Tue, 21 Jun 2016 14:22:14 +0000 Subject: [PATCH 2/8] GPII-1839: Linux acceptance tests use the dynamic device reporter. Added a "deviceReporter" block to test definitions for the built-in, and orca test specs. --- .../platform/linux/linux-builtIn-testSpec.js | 28 ++++++++++++++++--- tests/platform/linux/linux-orca-testSpec.js | 21 ++++++++++++-- 2 files changed, 42 insertions(+), 7 deletions(-) diff --git a/tests/platform/linux/linux-builtIn-testSpec.js b/tests/platform/linux/linux-builtIn-testSpec.js index cf1071cd5..869ffe350 100644 --- a/tests/platform/linux/linux-builtIn-testSpec.js +++ b/tests/platform/linux/linux-builtIn-testSpec.js @@ -58,7 +58,12 @@ gpii.tests.linux.builtIn.testDefs = fluid.freezeRecursive([ "expectConfigured": "true", "expectRestored": "false" } - ] + ], + deviceReporters: { + "gpii.packageKit.find": { + "expectInstalled": ["gnome-shell"] + } + } }, { name: "Testing os_common2 using Flat matchmaker", @@ -76,7 +81,12 @@ gpii.tests.linux.builtIn.testDefs = fluid.freezeRecursive([ }] } }, - processes: [] + processes: [], + deviceReporters: { + "gpii.packageKit.find": { + "expectInstalled": ["gsettings-desktop-schemas"] + } + } }, { name: "Testing os_gnome using Flat matchmaker", @@ -115,7 +125,12 @@ gpii.tests.linux.builtIn.testDefs = fluid.freezeRecursive([ "expectConfigured": "true", "expectRestored": "false" } - ] + ], + deviceReporters: { + "gpii.packageKit.find": { + "expectInstalled": ["gnome-shell"] + } + } }, { name: "Testing os_win7 using Flat matchmaker", @@ -151,7 +166,12 @@ gpii.tests.linux.builtIn.testDefs = fluid.freezeRecursive([ "expectConfigured": "true", "expectRestored": "false" } - ] + ], + deviceReporters: { + "gpii.packageKit.find": { + "expectInstalled": ["gnome-shell"] + } + } } ]); diff --git a/tests/platform/linux/linux-orca-testSpec.js b/tests/platform/linux/linux-orca-testSpec.js index 21707a2c1..9a5a0bb18 100644 --- a/tests/platform/linux/linux-orca-testSpec.js +++ b/tests/platform/linux/linux-orca-testSpec.js @@ -58,7 +58,12 @@ gpii.tests.linux.orca.testDefs = [ "expectConfigured": "true", "expectRestored": "false" } - ] + ], + deviceReporters: { + "gpii.packageKit.find": { + "expectInstalled": ["orca"] + } + } }, { name: "Testing screenreader_orca using Flat matchmaker", @@ -94,7 +99,12 @@ gpii.tests.linux.orca.testDefs = [ "expectConfigured": "true", "expectRestored": "false" } - ] + ], + deviceReporters: { + "gpii.packageKit.find": { + "expectInstalled": ["orca"] + } + } }, { name: "Testing screenreader_nvda using Flat matchmaker", @@ -129,7 +139,12 @@ gpii.tests.linux.orca.testDefs = [ "expectConfigured": "true", "expectRestored": "false" } - ] + ], + deviceReporters: { + "gpii.packageKit.find": { + "expectInstalled": ["orca"] + } + } } ]; From a61644e1bdc29af3d775d2ace0cbba28471f66ba Mon Sep 17 00:00:00 2001 From: Joseph Scheuhammer Date: Wed, 22 Jun 2016 08:49:54 -0700 Subject: [PATCH 3/8] GPII-1839: Windows acceptance tests use the dynamic device reporter. Added a "deviceReporter" block to test definitions for the built-in, jaws, maavis, and nvda test specs. --- .../windows/windows-builtIn-testSpec.js | 15 +++++++-- .../platform/windows/windows-jaws-testSpec.js | 22 +++++++++++-- .../windows/windows-maavis-testSpec.js | 10 ++++-- .../platform/windows/windows-nvda-testSpec.js | 33 +++++++++++++++++-- 4 files changed, 70 insertions(+), 10 deletions(-) diff --git a/tests/platform/windows/windows-builtIn-testSpec.js b/tests/platform/windows/windows-builtIn-testSpec.js index 8f7abd604..e86e9b913 100644 --- a/tests/platform/windows/windows-builtIn-testSpec.js +++ b/tests/platform/windows/windows-builtIn-testSpec.js @@ -132,7 +132,10 @@ gpii.tests.windows.builtIn = [ "expectConfigured": "1", "expectRestored": "0" } - ] + ], + deviceReporters: { + "gpii.deviceReporter.alwaysInstalled" : { } + } }, { name: "Testing os_common using Flat matchmaker", userToken: "os_common", @@ -242,7 +245,10 @@ gpii.tests.windows.builtIn = [ "expectConfigured": "1", "expectRestored": "0" } - ] + ], + deviceReporters: { + "gpii.deviceReporter.alwaysInstalled" : { } + } }, { name: "Testing os_gnome using Flat matchmaker", userToken: "os_gnome", @@ -309,7 +315,10 @@ gpii.tests.windows.builtIn = [ "expectConfigured": "1", "expectRestored": "0" } - ] + ], + deviceReporters: { + "gpii.deviceReporter.alwaysInstalled" : { } + } } ]; diff --git a/tests/platform/windows/windows-jaws-testSpec.js b/tests/platform/windows/windows-jaws-testSpec.js index 9db6e5ed1..2867835d6 100644 --- a/tests/platform/windows/windows-jaws-testSpec.js +++ b/tests/platform/windows/windows-jaws-testSpec.js @@ -46,7 +46,16 @@ gpii.tests.windows.jaws = [ "expectConfigured": "1", "expectRestored": "0" } - ] + ], + deviceReporters: { + "gpii.deviceReporter.registryKeyExists": { + "expectInstalled": [{ + "hKey": "HKEY_LOCAL_MACHINE", + "path": "Software\\Microsoft\\Windows\\CurrentVersion\\App Paths\\JAWS15.exe", + "subPath": "" + }] + } + } }, { name: "Testing NP set \"jaws_common\" using Flat matchmaker", userToken: "jaws_common", @@ -71,7 +80,16 @@ gpii.tests.windows.jaws = [ "expectConfigured": "1", "expectRestored": "0" } - ] + ], + deviceReporters: { + "gpii.deviceReporter.registryKeyExists": { + "expectInstalled": [{ + "hKey": "HKEY_LOCAL_MACHINE", + "path": "Software\\Microsoft\\Windows\\CurrentVersion\\App Paths\\JAWS15.exe", + "subPath": "" + }] + } + } } ]; diff --git a/tests/platform/windows/windows-maavis-testSpec.js b/tests/platform/windows/windows-maavis-testSpec.js index 769bfea75..27e8a539e 100644 --- a/tests/platform/windows/windows-maavis-testSpec.js +++ b/tests/platform/windows/windows-maavis-testSpec.js @@ -48,7 +48,10 @@ gpii.tests.windows.maavis = [ "expectConfigured": "1", "expectRestored": "0" } - ] + ], + deviceReporters: { + "gpii.deviceReporter.alwaysInstalled" : { } + } }, { name: "Testing maavis_selfvoicing using Flat matchmaker", userToken: "maavis_selfvoicing", @@ -75,7 +78,10 @@ gpii.tests.windows.maavis = [ "expectConfigured": "1", "expectRestored": "0" } - ] + ], + deviceReporters: { + "gpii.deviceReporter.alwaysInstalled" : { } + } } ]; diff --git a/tests/platform/windows/windows-nvda-testSpec.js b/tests/platform/windows/windows-nvda-testSpec.js index b6af2d032..077836b78 100644 --- a/tests/platform/windows/windows-nvda-testSpec.js +++ b/tests/platform/windows/windows-nvda-testSpec.js @@ -61,7 +61,16 @@ gpii.tests.windows.nvda = [ "expectConfigured": "1", "expectRestored": "0" } - ] + ], + deviceReporters: { + "gpii.deviceReporter.registryKeyExists": { + "expectInstalled": [{ + "hKey": "HKEY_LOCAL_MACHINE", + "path": "Software\\Microsoft\\Windows\\CurrentVersion\\App Paths\\nvda.exe", + "subPath": "" + }] + } + } }, { name: "Testing screenreader_common using Flat matchmaker", userToken: "screenreader_common", @@ -99,7 +108,16 @@ gpii.tests.windows.nvda = [ "expectConfigured": "1", "expectRestored": "0" } - ] + ], + deviceReporters: { + "gpii.deviceReporter.registryKeyExists": { + "expectInstalled": [{ + "hKey": "HKEY_LOCAL_MACHINE", + "path": "Software\\Microsoft\\Windows\\CurrentVersion\\App Paths\\nvda.exe", + "subPath": "" + }] + } + } }, { name: "Testing screenreader_orca using Flat matchmaker", userToken: "screenreader_orca", @@ -131,7 +149,16 @@ gpii.tests.windows.nvda = [ "expectConfigured": "1", "expectRestored": "0" } - ] + ], + deviceReporters: { + "gpii.deviceReporter.registryKeyExists": { + "expectInstalled": [{ + "hKey": "HKEY_LOCAL_MACHINE", + "path": "Software\\Microsoft\\Windows\\CurrentVersion\\App Paths\\nvda.exe", + "subPath": "" + }] + } + } } ]; From ee2bc6b6a99eadc35ad14f45a443f09654f50d98 Mon Sep 17 00:00:00 2001 From: Joseph Scheuhammer Date: Wed, 22 Jun 2016 16:00:52 -0400 Subject: [PATCH 4/8] GPII-1839: Use dynamic device reporter when running acceptance tests. Fixed lint errors. --- tests/platform/windows/windows-builtIn-testSpec.js | 2 +- tests/platform/windows/windows-maavis-testSpec.js | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/tests/platform/windows/windows-builtIn-testSpec.js b/tests/platform/windows/windows-builtIn-testSpec.js index e86e9b913..064145281 100644 --- a/tests/platform/windows/windows-builtIn-testSpec.js +++ b/tests/platform/windows/windows-builtIn-testSpec.js @@ -135,7 +135,7 @@ gpii.tests.windows.builtIn = [ ], deviceReporters: { "gpii.deviceReporter.alwaysInstalled" : { } - } + } }, { name: "Testing os_common using Flat matchmaker", userToken: "os_common", diff --git a/tests/platform/windows/windows-maavis-testSpec.js b/tests/platform/windows/windows-maavis-testSpec.js index 27e8a539e..cce8d2c04 100644 --- a/tests/platform/windows/windows-maavis-testSpec.js +++ b/tests/platform/windows/windows-maavis-testSpec.js @@ -51,7 +51,7 @@ gpii.tests.windows.maavis = [ ], deviceReporters: { "gpii.deviceReporter.alwaysInstalled" : { } - } + } }, { name: "Testing maavis_selfvoicing using Flat matchmaker", userToken: "maavis_selfvoicing", @@ -81,7 +81,7 @@ gpii.tests.windows.maavis = [ ], deviceReporters: { "gpii.deviceReporter.alwaysInstalled" : { } - } + } } ]; From 378a06a781f5e74cc971341cc14b30341acbf8ba Mon Sep 17 00:00:00 2001 From: Joseph Scheuhammer Date: Thu, 29 Sep 2016 16:56:45 -0400 Subject: [PATCH 5/8] GPII-1839: Gracefully handle writing to non-existent file. Don't explode on writing to non-existent settings file. --- .../src/settingsHandlerUtilities.js | 3 ++ .../settingsHandlers/test/data/basicOut.json | 1 + .../test/settingsHandlerUtilitiesTests.js | 29 +++++++++++++++++++ 3 files changed, 33 insertions(+) create mode 100644 gpii/node_modules/settingsHandlers/test/data/basicOut.json diff --git a/gpii/node_modules/settingsHandlers/src/settingsHandlerUtilities.js b/gpii/node_modules/settingsHandlers/src/settingsHandlerUtilities.js index dc9bb85be..9d065d959 100644 --- a/gpii/node_modules/settingsHandlers/src/settingsHandlerUtilities.js +++ b/gpii/node_modules/settingsHandlers/src/settingsHandlerUtilities.js @@ -335,6 +335,9 @@ gpii.settingsHandlers.readFile = function (options) { gpii.settingsHandlers.writeFile = function (content, options) { if (!options || !options.filename) { fluid.fail("writeFile: expected an options block defining filename and encoding"); + } else if (!fs.existsSync(options.filename)) { + fluid.log("writeFile: No settingsfile '", options.filename, "'' exists. Nothing written."); + return; } // TODO check for and handle write errors. fs.writeFileSync(options.filename, content, options.encoding || "utf-8"); diff --git a/gpii/node_modules/settingsHandlers/test/data/basicOut.json b/gpii/node_modules/settingsHandlers/test/data/basicOut.json new file mode 100644 index 000000000..97106d9ed --- /dev/null +++ b/gpii/node_modules/settingsHandlers/test/data/basicOut.json @@ -0,0 +1 @@ +{ "Good": "buy" } \ No newline at end of file diff --git a/gpii/node_modules/settingsHandlers/test/settingsHandlerUtilitiesTests.js b/gpii/node_modules/settingsHandlers/test/settingsHandlerUtilitiesTests.js index 5ba4427e9..5c671e2a9 100644 --- a/gpii/node_modules/settingsHandlers/test/settingsHandlerUtilitiesTests.js +++ b/gpii/node_modules/settingsHandlers/test/settingsHandlerUtilitiesTests.js @@ -41,6 +41,31 @@ gpii.tests.settingsHandlers.readFile = function () { jqUnit.assertEquals("Expecting undefined on reading non-existing file", undefined, result); }; +gpii.tests.settingsHandlers.writeFile = function () { + // Check writing existing file by writing and reading it back. + var dataOut = "{ \"Good\": \"buy\" }"; + gpii.settingsHandlers.writeFile(dataOut, { + filename: __dirname + "/data/basicOut.json" + }); + var dataIn = gpii.settingsHandlers.readFile({ + filename: __dirname + "/data/basicOut.json" + }); + jqUnit.assertDeepEq( + "Writing to existing file", + JSON.parse(dataOut), + JSON.parse(dataIn) + ); + + // Check writing non-existing file + gpii.settingsHandlers.writeFile(dataOut, { + filename: __dirname + "/data/bogus.json" + }); + dataIn = gpii.settingsHandlers.readFile({ + filename: __dirname + "/data/bogus.json" + }); + jqUnit.assertEquals("Writing to non-existing file", undefined, dataIn); +}; + var handleFileSolutionEntryData = { existingFileSolutionEntry: { options: { @@ -97,6 +122,10 @@ fluid.defaults("gpii.tests.settingsHandlers.caseHolder", { expect: 2, name: "gpii.settingsHandlers.readFile tests", func: "gpii.tests.settingsHandlers.readFile" + }, { + expect: 2, + name: "gpii.settingsHandlers.writeFile tests", + func: "gpii.tests.settingsHandlers.writeFile" }, { expect: 2, name: "gpii.settingsHandlers.handleFileSolutionEntry tests", From 73226b61d81e3bf91537a1ef8cac700db84573b6 Mon Sep 17 00:00:00 2001 From: Joseph Scheuhammer Date: Fri, 21 Oct 2016 10:11:59 -0400 Subject: [PATCH 6/8] GPII-1839: Merge master GPII branch into GPII-1839. --- .eslintrc.json | 6 + Gruntfile.js | 2 +- README.md | 18 +- documentation/FlowManager.md | 7 +- .../contextManager/src/ContextManager.js | 15 +- gpii/node_modules/deviceReporter/package.json | 28 +- gpii/node_modules/flatMatchMaker/package.json | 25 +- .../gpii.flowManager.config.development.json | 2 +- .../gpii.flowManager.config.local.base.json | 14 + .../gpii.flowManager.config.production.json | 2 +- gpii/node_modules/flowManager/index.js | 2 +- gpii/node_modules/flowManager/package.json | 28 +- .../flowManager/src/CloudBasedFlowManager.js | 14 - .../flowManager/src/FlowManager.js | 31 +- ...gerUtilities.js => FlowManagerRequests.js} | 25 +- .../flowManager/src/UntrustedFlowManager.js | 52 +- .../flowManager/src/UserLogonStateChange.js | 35 +- .../flowManager/src/UserUpdate.js | 10 +- .../flowManager/test/UpdateTests.js | 7 +- gpii/node_modules/journal/README.md | 14 + gpii/node_modules/journal/index.js | 10 + gpii/node_modules/journal/package.json | 13 + gpii/node_modules/journal/src/Journal.js | 268 ++++++++++ .../journal/src/JournalIdParser.js | 108 ++++ gpii/node_modules/journal/src/SettingsDir.js | 101 ++++ .../test/html/JournalIdParserTests.html | 34 ++ .../journal/test/js/JournalIdParserTests.js | 155 ++++++ .../lifecycleActions/package.json | 28 +- gpii/node_modules/lifecycleManager/index.js | 3 + .../lifecycleManager/package.json | 28 +- .../src/DynamicComponentIndexer.js | 59 +++ .../lifecycleManager/src/LifecycleManager.js | 496 +++++++++--------- .../src/LifecycleManagerSession.js | 103 ++++ .../lifecycleManager/src/Resolvers.js | 140 +++++ .../html/DynamicComponentIndexerTest.html | 35 ++ .../test/html/LifecycleManagerTest.html | 5 +- .../test/js/DynamicComponentIndexerTests.js | 73 +++ .../test/js/LifecycleManagerTests.js | 46 +- .../matchMakerFramework/package.json | 28 +- .../node_modules/ontologyHandler/package.json | 28 +- .../preferencesServer/package.json | 28 +- .../rawPreferencesServer/package.json | 28 +- .../settingsHandlers/package.json | 29 +- .../src/settingsHandlerUtilities.js | 117 ++++- .../test/settingsHandlerUtilitiesTests.js | 4 +- gpii/node_modules/testing/package.json | 28 +- gpii/node_modules/testing/src/Integration.js | 19 +- gpii/node_modules/testing/src/Testing.js | 166 +++++- gpii/node_modules/transformer/package.json | 28 +- index.js | 6 + package.json | 12 +- .../acceptanceTests/win7_builtIn.json | 13 + .../deviceReporter/installedSolutions.json | 12 + .../preferences/acceptanceTests/README.txt | 5 +- .../acceptanceTests/os_common.json | 8 +- .../preferences/acceptanceTests/os_win7.json | 30 ++ testData/solutions/win32.json | 279 ++++++++++ tests/ContextIntegrationTests.js | 34 +- tests/JournalIntegrationTests.js | 424 +++++++++++++++ tests/MultiSettingsHandlerTests.js | 2 +- tests/all-tests.js | 1 + .../windows/windows-builtIn-testSpec.js | 112 ++++ tests/web/html/all-tests.html | 2 + 63 files changed, 2809 insertions(+), 676 deletions(-) create mode 100644 gpii/node_modules/flowManager/configs/gpii.flowManager.config.local.base.json rename gpii/node_modules/flowManager/src/{FlowManagerUtilities.js => FlowManagerRequests.js} (92%) create mode 100644 gpii/node_modules/journal/README.md create mode 100644 gpii/node_modules/journal/index.js create mode 100644 gpii/node_modules/journal/package.json create mode 100644 gpii/node_modules/journal/src/Journal.js create mode 100644 gpii/node_modules/journal/src/JournalIdParser.js create mode 100644 gpii/node_modules/journal/src/SettingsDir.js create mode 100644 gpii/node_modules/journal/test/html/JournalIdParserTests.html create mode 100644 gpii/node_modules/journal/test/js/JournalIdParserTests.js create mode 100644 gpii/node_modules/lifecycleManager/src/DynamicComponentIndexer.js create mode 100644 gpii/node_modules/lifecycleManager/src/LifecycleManagerSession.js create mode 100644 gpii/node_modules/lifecycleManager/src/Resolvers.js create mode 100644 gpii/node_modules/lifecycleManager/test/html/DynamicComponentIndexerTest.html create mode 100644 gpii/node_modules/lifecycleManager/test/js/DynamicComponentIndexerTests.js create mode 100644 tests/JournalIntegrationTests.js diff --git a/.eslintrc.json b/.eslintrc.json index 76d98377d..44a670c94 100644 --- a/.eslintrc.json +++ b/.eslintrc.json @@ -4,6 +4,10 @@ }, "rules": { "block-scoped-var": "error", + "comma-dangle": [ + "error", + "never" + ], "comma-style": [ "error", "last" @@ -24,6 +28,7 @@ "allow-null" ], "indent": ["error", 4], + "keyword-spacing": "error", "new-cap": ["error", { "properties": false }], "no-caller": "error", "no-cond-assign": [ @@ -40,6 +45,7 @@ "no-multi-str": "error", "no-new": "error", "no-proto": "error", + "no-redeclare": "error", "no-script-url": "error", "no-sequences": "error", "no-trailing-spaces": "error", diff --git a/Gruntfile.js b/Gruntfile.js index 7a87928c5..ffccd7f9a 100644 --- a/Gruntfile.js +++ b/Gruntfile.js @@ -19,7 +19,7 @@ module.exports = function (grunt) { grunt.initConfig({ eslint: { - src: ["gpii/**/*.js", "tests/**/*.js", "examples/**/*.js", "*.js"], + src: ["gpii/**/*.js", "tests/**/*.js", "examples/**/*.js", "*.js"] }, jsonlint: { src: ["gpii/**/*.json", "tests/**/*.json", "testData/**/*.json", "*.json"] diff --git a/README.md b/README.md index c2095e231..1509b5458 100644 --- a/README.md +++ b/README.md @@ -30,6 +30,16 @@ If all is well, you will see a message like Note that this installation will not include any OS-specific features, but can be used to verify system function with basic preference sets which only start solutions which require filesystem-based configuration (XML, JSON or .INI files). +Recovering From System Corruption Using the Journal +--------------------------------------------------- + +Either when operating the live GPII system or running test cases, you may end up corrupting your desktop settings in the +case there is a system crash. In this case, you can navigate to + + http://localhost:8081/journal/journals.html + +to browse a set of journal recovery snapshots. Clicking on the first link on this page will restore your system +to the state it was in prior to the most recent GPII login. Testing ------- @@ -50,7 +60,11 @@ From the root of the `universal` folder, run the following command: #### Running tests using a VM A VM can be automatically created using tools provided by the [Prosperity4All Quality Infrastructure](https://github.com/GPII/qi-development-environments/). Please ensure the [requirements](https://github.com/GPII/qi-development-environments/#requirements) have been met. The ``vagrant up`` command can then be used to provision a new VM. -Following provisioning, tests can be run in the VM from the host system as follows: +Within the VM, the application will be installed in the directory specified by the nodejs_app_install_dir variable, which is defined in the provisioning/vagrant-vars.yml configuration file in this repository. By default it is set to "/home/vagrant/sync/node_modules/universal". + +Following the provisioning phase, tests can be run in the VM directly from the host system, without the need to log into the VM or interact with its console. + +From your project's top-level directory (where the Vagrantfile and Gruntfile.js files reside), run: - node-based tests: `grunt node-tests` - browser-based tests: `grunt browser-tests` @@ -58,6 +72,8 @@ Following provisioning, tests can be run in the VM from the host system as follo The ``grunt tests`` command will run the browser and Node based tests. +These Grunt tasks will run the correct Vagrant commands to connect to the VM and run the tests within the isolated environment. You can also run `vagrant ssh` to connect to the VM (or open the VirtualBox console and interface with the desktop environment) and run the tests manually if you wish. + Usage ----- diff --git a/documentation/FlowManager.md b/documentation/FlowManager.md index 00a0cfcbc..fc65f30cc 100644 --- a/documentation/FlowManager.md +++ b/documentation/FlowManager.md @@ -1,9 +1,12 @@ ## FlowManager -The flowmanager is the component in the system that is largely responsible for managing the flow. For example, the steps involved in logging in requires retrieving preferences, solutions, device data, etc. and passing this to the [MatchMaker Framework](MatchMakerFramework.md). And following that sending this via the [Context Manager](ContextManager.md) and then to the [Lifecycle Manager](LifecycleManager.md). +The flowmanager is the central point of coordination in the system for managing flow between different architecture components. +For example, it coordinates the steps involved during logging in which require retrieving preferences, solutions, device data, etc. and passing these to the [MatchMaker Framework](MatchMakerFramework.md). +Following those steps, the payload is sent via the [Context Manager](ContextManager.md) and then to the [Lifecycle Manager](LifecycleManager.md). ###Important flows -Depending on what the usage of the system is, the flows will be different. For example user login, user log off, and retrieving settings from the system in "cloud based flowmanager" mode are all different. Each "flow" is managed in a different file, with the common events, functions, etc., located in `FlowManager.js` and `FlowManagerUtitilies.js`. The different kinds of flows are: +Depending on what the usage of the system is, the flows will be different. For example user login, user log off, and retrieving settings from the system in "cloud based flowmanager" mode are all different. +Each "flow" is managed in a different file, with the common events, functions, etc., located in `FlowManager.js` and `FlowManagerUtitilies.js`. The different kinds of flows are: * **User Login** (`UserLogin.js`) - the flow for a user keying in to the system. The flow is described in details in the [loginAndLogoutFlow](LoginAndLogoutFlow.md) document * **User Logout** (`UserLogout.js`) - the flow for a user keying out of the system * **Retrieving Settings** (`Settings.js`) - used to retrieve the settings when the system is running in cloud-based mode. See [CloudBasedFlow](CloudBasedFlow.md) for more details diff --git a/gpii/node_modules/contextManager/src/ContextManager.js b/gpii/node_modules/contextManager/src/ContextManager.js index 64713a4aa..1b6f4d5c2 100644 --- a/gpii/node_modules/contextManager/src/ContextManager.js +++ b/gpii/node_modules/contextManager/src/ContextManager.js @@ -160,21 +160,21 @@ gpii.contextManager.stopTemporalEnvironmentReporter = function (that) { gpii.contextManager.evaluateConditions = function (that, lifecycleManager, context) { // find logged in users var activeSessions = lifecycleManager.getActiveSessionTokens(); - // if none is logged in, do nothing + // if noone is logged in, do nothing if (activeSessions.length === 0) { return; } var activeSession = lifecycleManager.getSession(activeSessions); - var newActiveContexts = gpii.contextManager.utils.findActiveContexts(context, activeSession.matchMakerOutput); + var newActiveContexts = gpii.contextManager.utils.findActiveContexts(context, activeSession.model.matchMakerOutput); fluid.log("contextManager: New active contexts: " + newActiveContexts); if (newActiveContexts[0] === activeSession.activeContextName) { - fluid.log("contextManager: Same context as before (" + activeSession.appliedContext + ") so doing nothing"); + fluid.log("contextManager: Same context as before (" + activeSession.model.appliedContext + ") so doing nothing"); return; } - var activeConfiguration = activeSession.matchMakerOutput.inferredConfiguration[newActiveContexts[0]]; - var lifecycleInstructions = that.transformer.configurationToSettings(activeConfiguration, activeSession.solutionsRegistryEntries); + var activeConfiguration = activeSession.model.matchMakerOutput.inferredConfiguration[newActiveContexts[0]]; + var lifecycleInstructions = that.transformer.configurationToSettings(activeConfiguration, activeSession.model.solutionsRegistryEntries); var finalPayload = { - userToken: activeSession.userToken, + userToken: activeSession.model.userToken, lifecycleInstructions: lifecycleInstructions, activeContextName: newActiveContexts[0], activeConfiguration: activeConfiguration @@ -187,7 +187,8 @@ gpii.contextManager.evaluateConditions = function (that, lifecycleManager, conte // "finalPayload" as instructions on how to configure the system.. once configured, // the applied settings are stored in 'activeConfiguration' delete finalPayload.lifecycleInstructions; - fluid.extend(true, activeSession, finalPayload); + // TODO: This is pretty rubbish, why isn't the natural action of lifecycleManager.update good enough here? + activeSession.applier.change("", finalPayload); // TODO: this will need to be a "MERGE" in time } fluid.log("contextManager: Successfully updated configuration triggered by context changes"); }); diff --git a/gpii/node_modules/deviceReporter/package.json b/gpii/node_modules/deviceReporter/package.json index d03208c71..a116611cd 100644 --- a/gpii/node_modules/deviceReporter/package.json +++ b/gpii/node_modules/deviceReporter/package.json @@ -1,19 +1,13 @@ { - "name": "deviceReporter", - "description": "The Device Reporter module provides information about the platform and its installed software in order to guide the choice to a solution fitting the user's profile", - "version": "0.1.0", - "author": "GPII", - "bugs": "http://issues.gpii.net/browse/GPII", - "homepage": "http://gpii.net/", - "dependencies": {}, - "licenses": [ - { - "type": "BSD-3-Clause", - "url": "http://www.opensource.org/licenses/BSD-3-Clause" - } - ], - "keywords": ["gpii", "accessibility", "settings", "fluid", "IoC", "Inversion of Control", "configuration", "evented"], - "repository": "git://github.com/GPII/universal.git", - "main": "./index.js", - "engines": { "node" : ">=0.8" } + "name": "deviceReporter", + "description": "The Device Reporter module provides information about the platform and its installed software in order to guide the choice to a solution fitting the user's profile", + "version": "0.3.0", + "author": "GPII", + "bugs": "http://issues.gpii.net/browse/GPII", + "homepage": "http://gpii.net/", + "dependencies": {}, + "license" : "BSD-3-Clause", + "repository": "git://github.com/GPII/universal.git", + "main": "./index.js", + "engines": { "node" : ">=4.2.1" } } \ No newline at end of file diff --git a/gpii/node_modules/flatMatchMaker/package.json b/gpii/node_modules/flatMatchMaker/package.json index 64591a9ef..314df4300 100644 --- a/gpii/node_modules/flatMatchMaker/package.json +++ b/gpii/node_modules/flatMatchMaker/package.json @@ -1,16 +1,13 @@ { - "name": "flatMatchMaker", - "description": "The Flat Match Maker is a simple lightweight Matchmaker", - "version": "0.1", - "author": "GPII", - "bugs": "http://issues.gpii.net/browse/GPII", - "homepage": "http://gpii.net/", - "dependencies": {}, - "license" : "BSD-3-Clause", - "keywords": ["infusion", "framework", "application", "fluid", "IoC", "Inversion of Control", "MVC", "evented"], - "repository": "git://github.com/GPII/universal.git", - "main": "./index.js", - "engines": { - "node": ">=4.2.1" - } + "name": "flatMatchMaker", + "description": "The Flat Match Maker is a simple lightweight Matchmaker", + "version": "0.3.0", + "author": "GPII", + "bugs": "http://issues.gpii.net/browse/GPII", + "homepage": "http://gpii.net/", + "dependencies": {}, + "license" : "BSD-3-Clause", + "repository": "git://github.com/GPII/universal.git", + "main": "./index.js", + "engines": { "node" : ">=4.2.1" } } \ No newline at end of file diff --git a/gpii/node_modules/flowManager/configs/gpii.flowManager.config.development.json b/gpii/node_modules/flowManager/configs/gpii.flowManager.config.development.json index 14fc4849e..e7b9df8a3 100644 --- a/gpii/node_modules/flowManager/configs/gpii.flowManager.config.development.json +++ b/gpii/node_modules/flowManager/configs/gpii.flowManager.config.development.json @@ -21,6 +21,6 @@ }, "require": "%universal/testData/security/TestOAuth2DataStore.js", "mergeConfigs": [ - "./gpii.flowManager.config.base.json" + "./gpii.flowManager.config.local.base.json" ] } diff --git a/gpii/node_modules/flowManager/configs/gpii.flowManager.config.local.base.json b/gpii/node_modules/flowManager/configs/gpii.flowManager.config.local.base.json new file mode 100644 index 000000000..662f9599b --- /dev/null +++ b/gpii/node_modules/flowManager/configs/gpii.flowManager.config.local.base.json @@ -0,0 +1,14 @@ +{ + "type": "gpii.flowManager.config.local.base", + "options": { + "distributeOptions": { + "flowManager.local": { + "record": "gpii.flowManager.local", + "target": "{that flowManager}.options.gradeNames" + } + } + }, + "mergeConfigs": [ + "./gpii.flowManager.config.base.json" + ] +} \ No newline at end of file diff --git a/gpii/node_modules/flowManager/configs/gpii.flowManager.config.production.json b/gpii/node_modules/flowManager/configs/gpii.flowManager.config.production.json index 4a39ef0e8..2790f9d25 100644 --- a/gpii/node_modules/flowManager/configs/gpii.flowManager.config.production.json +++ b/gpii/node_modules/flowManager/configs/gpii.flowManager.config.production.json @@ -15,6 +15,6 @@ } }, "mergeConfigs": [ - "./gpii.flowManager.config.base.json" + "./gpii.flowManager.config.local.base.json" ] } \ No newline at end of file diff --git a/gpii/node_modules/flowManager/index.js b/gpii/node_modules/flowManager/index.js index 581a8ab7f..2ac9c307c 100644 --- a/gpii/node_modules/flowManager/index.js +++ b/gpii/node_modules/flowManager/index.js @@ -4,6 +4,6 @@ var fluid = require("infusion"); fluid.module.register("flowManager", __dirname, require); -require("./src/FlowManagerUtilities.js"); +require("./src/FlowManagerRequests.js"); require("./src/FlowManager.js"); diff --git a/gpii/node_modules/flowManager/package.json b/gpii/node_modules/flowManager/package.json index f32ddebea..0d9d906c6 100644 --- a/gpii/node_modules/flowManager/package.json +++ b/gpii/node_modules/flowManager/package.json @@ -1,19 +1,13 @@ { - "name": "flowManager", - "description": "Flow Manager handles communication between various data sources: user preferences, device reporter, configuration manager, etc.", - "version": "0.1", - "author": "GPII", - "bugs": "http://issues.gpii.net/browse/GPII", - "homepage": "http://gpii.net/", - "dependencies": {}, - "licenses": [ - { - "type": "BSD-3-Clause", - "url": "http://www.opensource.org/licenses/BSD-3-Clause" - } - ], - "keywords": ["gpii", "accessibility", "settings", "fluid", "IoC", "Inversion of Control", "configuration", "evented"], - "repository": "git://github.com/GPII/universal.git", - "main": "./index.js", - "engines": { "node" : ">=0.8" } + "name": "flowManager", + "description": "The GPII Flow Manager handles communication between various data sources: user preferences, device reporter, configuration manager, etc.", + "version": "0.3.0", + "author": "GPII", + "bugs": "http://issues.gpii.net/browse/GPII", + "homepage": "http://gpii.net/", + "dependencies": {}, + "license" : "BSD-3-Clause", + "repository": "git://github.com/GPII/universal.git", + "main": "./index.js", + "engines": { "node" : ">=4.2.1" } } \ No newline at end of file diff --git a/gpii/node_modules/flowManager/src/CloudBasedFlowManager.js b/gpii/node_modules/flowManager/src/CloudBasedFlowManager.js index 80ad5ca4a..83ae62b39 100644 --- a/gpii/node_modules/flowManager/src/CloudBasedFlowManager.js +++ b/gpii/node_modules/flowManager/src/CloudBasedFlowManager.js @@ -28,14 +28,6 @@ require("gpii-oauth2"); */ fluid.defaults("gpii.flowManager.cloudBased", { - components: { - deviceReporterDataSource: { - type: "fluid.emptySubcomponent" - } - }, - urls: { - deviceReporter: "" - }, requestHandlers: { settings: { route: "/:userToken/settings/:device", @@ -131,14 +123,8 @@ fluid.defaults("gpii.flowManager.cloudBased.oauth2", { dataStore: "{gpii.oauth2.dataStore}" } } - }, - deviceReporterDataSource: { - type: "fluid.emptySubcomponent" } }, - urls: { - deviceReporter: "" - }, requestHandlers: { oauth2Settings: { route: "/settings", diff --git a/gpii/node_modules/flowManager/src/FlowManager.js b/gpii/node_modules/flowManager/src/FlowManager.js index 8f7f75479..a60da9c2c 100644 --- a/gpii/node_modules/flowManager/src/FlowManager.js +++ b/gpii/node_modules/flowManager/src/FlowManager.js @@ -31,6 +31,7 @@ require("./UntrustedFlowManager.js"); require("lifecycleManager"); require("transformer"); +require("journal"); fluid.defaults("gpii.flowManager", { gradeNames: ["kettle.app"], @@ -53,9 +54,6 @@ fluid.defaults("gpii.flowManager", { } } }, - deviceReporterDataSource: { - type: "kettle.dataSource" - }, lifecycleManager: { type: "gpii.lifecycleManager" }, @@ -74,12 +72,30 @@ fluid.defaults("gpii.flowManager", { ontologyHandler: { type: "gpii.ontologyHandler" } + } +}); + + +// Mixin grades for the FlowManager + +fluid.defaults("gpii.flowManager.local", { + components: { + deviceReporterDataSource: { + type: "kettle.dataSource" + }, + journal: { + type: "gpii.journal", + options: { + gradeNames: ["gpii.journalLifecycleManager", "gpii.journalApp"] + } + } }, requestHandlers: { userLogin: { route: "/user/:userToken/login", method: "get", - type: "gpii.flowManager.userLogin.handler" + type: "gpii.flowManager.userLogin.handler", + gradeNames: "gpii.flowManager.userLogonStateChange.matchMakingStateChangeHandler" }, userLogout: { route: "/user/:userToken/logout", @@ -89,7 +105,8 @@ fluid.defaults("gpii.flowManager", { userLogonStateChange: { route: "/user/:userToken/logonChange", method: "get", - type: "gpii.flowManager.userLogonStateChange.handler" + type: "gpii.flowManager.userLogonStateChange.handler", + gradeNames: "gpii.flowManager.userLogonStateChange.matchMakingStateChangeHandler" }, getUserToken: { route: "/userToken", @@ -99,10 +116,6 @@ fluid.defaults("gpii.flowManager", { } }); - - -// Mixin grades for the FlowManager - fluid.defaults("gpii.flowManager.save", { requestHandlers: { userSavePost: { diff --git a/gpii/node_modules/flowManager/src/FlowManagerUtilities.js b/gpii/node_modules/flowManager/src/FlowManagerRequests.js similarity index 92% rename from gpii/node_modules/flowManager/src/FlowManagerUtilities.js rename to gpii/node_modules/flowManager/src/FlowManagerRequests.js index de868bac6..7b5cadaaa 100644 --- a/gpii/node_modules/flowManager/src/FlowManagerUtilities.js +++ b/gpii/node_modules/flowManager/src/FlowManagerRequests.js @@ -1,5 +1,5 @@ /** - * GPII Flow Manager Utilities. + * GPII Flow Manager Requests * * Copyright 2012 OCAD University * Copyright 2015 Raising the Floor - International @@ -74,25 +74,6 @@ }, onError.fire); }; - - gpii.flowManager.getDeviceContext = function (deviceReporterDataSource, event, errorEvent) { - fluid.log("getDeviceContext for dataSource with options ", deviceReporterDataSource.options); - var promise = deviceReporterDataSource.get(); - promise.then(function (deviceData) { - fluid.log("getDeviceContext got deviceData ", deviceData); - if (deviceData.isError) { - errorEvent.fire({message: "Error in device reporter data: " + deviceData.message}); - } else { - event.fire(deviceData); - } - }, function (err) { - var error = fluid.extend(err, { - message: "Rejected deviceReporter promise: " + err.message - }); - errorEvent.fire(error); - }); - }; - gpii.flowManager.transformLifecycle = function (transformer, contextPayload) { var lifecycleInstructions = transformer.configurationToSettings(contextPayload.activeConfiguration, contextPayload.solutionsRegistryEntries); fluid.log("transformer got lifecycleInstructions " + JSON.stringify(lifecycleInstructions, null, 2) + " from contextPayload " + JSON.stringify(contextPayload, null, 2)); @@ -150,10 +131,6 @@ getSolutions: { funcName: "gpii.flowManager.getSolutions", args: [ "{flowManager}.solutionsRegistryDataSource", "{arguments}.0", "{that}.events.onSolutions", "{request}.events.onError"] - }, - getDeviceContext: { - funcName: "gpii.flowManager.getDeviceContext", - args: ["{flowManager}.deviceReporterDataSource", "{that}.events.onDeviceContext", "{request}.events.onError"] } }, events: { diff --git a/gpii/node_modules/flowManager/src/UntrustedFlowManager.js b/gpii/node_modules/flowManager/src/UntrustedFlowManager.js index 64a5fbcbd..dc9e3c188 100644 --- a/gpii/node_modules/flowManager/src/UntrustedFlowManager.js +++ b/gpii/node_modules/flowManager/src/UntrustedFlowManager.js @@ -15,10 +15,15 @@ var fluid = require("infusion"); var gpii = fluid.registerNamespace("gpii"); -// TODO: For now, follow the approach of the cloudBased Flow Manager -// of extending flowManager and disabling/overriding pieces. In the -// future, refactor flowManager so that the base is smaller and use -// composition rather than overriding. +// The Untrusted or Hybrid (Local) FlowManager is a Local FlowManager which defers to the cloud for the matchMaking +// and Preferences fetch process. The architectural goal of the untrusted FlowManager is that the unfiltered +// user preferences never reach the local device. Therefore the userLogon stages of the "gpii.flowManager.matchMakingRequest" +// are all abridged, and instead we simply receive a final settings payload from a cloudBased flowManager which are +// then directly applied to the local device. + +// Described at https://issues.gpii.net/browse/GPII-1224 + +// Overrides request handlers in "gpii.flowManager.local" fluid.defaults("gpii.flowManager.untrusted", { components: { @@ -34,17 +39,12 @@ fluid.defaults("gpii.flowManager.untrusted", { } }, requestHandlers: { - userLogin: null, - userLogonStateChanged: null, - untrustedUserLogin: { - route: "/user/:userToken/login", - method: "get", - type: "gpii.flowManager.untrusted.userLogin.handler" + userLogin: { + // NOTE that these gradesNames do not merge as with standard components + gradeNames: "gpii.flowManager.untrusted.stateChangeHandler" }, - untrustedUserLogonStateChange: { - route: "/user/:userToken/logonChange", - method: "get", - type: "gpii.flowManager.untrusted.userLogonStateChange.handler" + userLogonStateChange: { + gradeNames: "gpii.flowManager.untrusted.stateChangeHandler" } } }); @@ -71,10 +71,6 @@ fluid.defaults("gpii.flowManager.untrusted.stateChangeHandler", { "{that}.events.onSettings", "{that}.events.onError" ] - }, - getDeviceContext: { // TODO GPII-1770 factoring - duplicates a method from "gpii.flowManager.matchMakingRequest" - funcName: "gpii.flowManager.getDeviceContext", - args: ["{flowManager}.deviceReporterDataSource", "{that}.events.onDeviceContext", "{request}.events.onError"] } }, events: { @@ -95,23 +91,3 @@ fluid.defaults("gpii.flowManager.untrusted.stateChangeHandler", { "onSettings.startLifecycle": "{that}.startLifecycle" } }); - -fluid.defaults("gpii.flowManager.untrusted.userLogin.handler", { // TODO: GPII-1770 Duplicate other than parent grade - gradeNames: ["kettle.request.http", "gpii.flowManager.untrusted.stateChangeHandler"], - invokers: { - handleRequest: { - funcName: "gpii.flowManager.userLogin.handleRequest", - args: ["{request}.req.params.userToken", "{flowManager}.lifecycleManager", "{that}"] - } - } -}); - -fluid.defaults("gpii.flowManager.untrusted.userLogonStateChange.handler", { - gradeNames: ["kettle.request.http", "gpii.flowManager.untrusted.stateChangeHandler"], - invokers: { - handleRequest: { - funcName: "gpii.flowManager.userLogonStateChange.handleRequest", - args: ["{request}.req.params.userToken", "{flowManager}.lifecycleManager", "{that}"] - } - } -}); diff --git a/gpii/node_modules/flowManager/src/UserLogonStateChange.js b/gpii/node_modules/flowManager/src/UserLogonStateChange.js index 203deda94..7c9a3168d 100644 --- a/gpii/node_modules/flowManager/src/UserLogonStateChange.js +++ b/gpii/node_modules/flowManager/src/UserLogonStateChange.js @@ -60,8 +60,32 @@ gpii.flowManager.userLogonStateChange.loginUser = function (that, userToken) { that.events.onUserToken.fire(userToken); }; + +gpii.flowManager.getDeviceContext = function (deviceReporterDataSource, event, errorEvent) { + fluid.log("getDeviceContext for dataSource with options ", deviceReporterDataSource.options); + var promise = deviceReporterDataSource.get(); + promise.then(function (deviceData) { + fluid.log("getDeviceContext got deviceData ", deviceData); + if (deviceData.isError) { + errorEvent.fire({message: "Error in device reporter data: " + deviceData.message}); + } else { + event.fire(deviceData); + } + }, function (err) { + var error = fluid.extend(err, { + message: "Rejected deviceReporter promise: " + err.message + }); + errorEvent.fire(error); + }); +}; + +// A mixin grade for a matchMakingRequest request handler, supporting local user logon fluid.defaults("gpii.flowManager.userLogonStateChange.stateChangeHandler", { invokers: { + getDeviceContext: { // TODO: remember to close GPII-1770 now this is correctly factored + funcName: "gpii.flowManager.getDeviceContext", + args: ["{flowManager}.deviceReporterDataSource", "{that}.events.onDeviceContext", "{request}.events.onError"] + }, startLifecycle: { funcName: "gpii.flowManager.userLogonStateChange.startLifecycle", args: [ "{flowManager}.lifecycleManager", "{arguments}.0", "{request}.events.onSuccess" ] @@ -91,8 +115,12 @@ fluid.defaults("gpii.flowManager.userLogonStateChange.matchMakingStateChangeHand } }); + +// Incomplete grade - local FlowManager adds "gpii.flowManager.userLogonStateChange.matchMakingStateChangeHandler", +// whereas untrusted adds "gpii.flowManager.untrusted.stateChangeHandler" + fluid.defaults("gpii.flowManager.userLogonStateChange.handler", { - gradeNames: ["kettle.request.http", "gpii.flowManager.userLogonStateChange.matchMakingStateChangeHandler"], + gradeNames: ["kettle.request.http"], invokers: { handleRequest: { funcName: "gpii.flowManager.userLogonStateChange.handleRequest", @@ -130,8 +158,11 @@ gpii.flowManager.userLogonStateChange.handleRequest = function (userToken, lifec } }; +// Incomplete grade - local FlowManager adds "gpii.flowManager.userLogonStateChange.matchMakingStateChangeHandler", +// whereas untrusted adds "gpii.flowManager.untrusted.stateChangeHandler" + fluid.defaults("gpii.flowManager.userLogin.handler", { - gradeNames: ["kettle.request.http", "gpii.flowManager.userLogonStateChange.matchMakingStateChangeHandler"], + gradeNames: ["kettle.request.http"], invokers: { handleRequest: { funcName: "gpii.flowManager.userLogin.handleRequest", diff --git a/gpii/node_modules/flowManager/src/UserUpdate.js b/gpii/node_modules/flowManager/src/UserUpdate.js index 8cf2392fb..17e30e9e1 100644 --- a/gpii/node_modules/flowManager/src/UserUpdate.js +++ b/gpii/node_modules/flowManager/src/UserUpdate.js @@ -41,18 +41,20 @@ fluid.defaults("gpii.flowManager.userUpdate.handler", { gpii.flowManager.userUpdate.handleUpdate = function (that, data, transformer, lifecycleManager) { fluid.log("userUpdate.handleUpdate with data ", data); that.withSession(function (session) { - var updatedConfig = fluid.extend(true, {}, session.activeConfiguration, { applications: data }); - var lifecycleInstructions = transformer.configurationToSettings(updatedConfig, session.solutionsRegistryEntries); + var updatedConfig = fluid.extend(true, {}, session.model.activeConfiguration, { applications: data }); + var lifecycleInstructions = transformer.configurationToSettings(updatedConfig, session.model.solutionsRegistryEntries); var finalPayload = { lifecycleInstructions: lifecycleInstructions, - userToken: session.userToken + userToken: session.model.userToken }; lifecycleManager.update(finalPayload, function (response) { fluid.log("userUpdate.handleUpdate Sending response ", response); if (response.success) { // Update the session. - session.activeConfiguration = updatedConfig; + // TODO: This presumably should be an "ADD" and clear the existing activeConfiguration + // But given we have no tests, who cares, right? + session.applier.change("activeConfiguration", updatedConfig); } that.sendMessage(response); }); diff --git a/gpii/node_modules/flowManager/test/UpdateTests.js b/gpii/node_modules/flowManager/test/UpdateTests.js index e24eda362..202a3c075 100644 --- a/gpii/node_modules/flowManager/test/UpdateTests.js +++ b/gpii/node_modules/flowManager/test/UpdateTests.js @@ -76,8 +76,9 @@ gpii.tests.flowManager.update.testSecondUpdateRequest = function (data) { testUpdateRequest(gpii.tests.flowManager.update.tokenPrefsUpdate, data); }; -gpii.tests.flowManager.update.clearActiveSessions = function (activeSessions) { - delete activeSessions.testUser1; +gpii.tests.flowManager.update.clearActiveSessions = function (lifecycleManager) { + var session = lifecycleManager.getSession("testUser1"); + session.destroy(); }; gpii.tests.flowManager.update.testUpdateRequestFailure = function (data) { @@ -170,7 +171,7 @@ gpii.tests.flowManager.update.testDefs = [{ listener: "gpii.tests.flowManager.update.testLoginResponse" }, { func: "gpii.tests.flowManager.update.clearActiveSessions", - args: "{tests}.configuration.server.flowManager.lifecycleManager.activeSessions" + args: "{tests}.configuration.server.flowManager.lifecycleManager" }, { func: "{updateRequest}.send", args: gpii.tests.flowManager.update.payload1 diff --git a/gpii/node_modules/journal/README.md b/gpii/node_modules/journal/README.md new file mode 100644 index 000000000..c3e2fd512 --- /dev/null +++ b/gpii/node_modules/journal/README.md @@ -0,0 +1,14 @@ +Journal +=== + +The Journal component is responsible for Journalling the state of the system's settings +in order to be able to recover from system crashes or corruption of the settings state. + +The Journal determines a GPII base directory to write the journal in, which is a subdirectory +of the user's settings directory named "gpii". The user's settings directory is determined +by a system dependent algorithm - + +* On Windows, the directory is determined by the environment variable APPDATA, as recommended +by Microsoft at https://blogs.msdn.microsoft.com/patricka/2010/03/18/where-should-i-store-my-data-and-configuration-files-if-i-target-multiple-os-versions/ +* On every other platform, it uses the value of the node standard API [`os.homedir`](https://nodejs.org/api/os.html#os_os_homedir) + diff --git a/gpii/node_modules/journal/index.js b/gpii/node_modules/journal/index.js new file mode 100644 index 000000000..8dc4f3a30 --- /dev/null +++ b/gpii/node_modules/journal/index.js @@ -0,0 +1,10 @@ +"use strict"; + +var fluid = require("infusion"); + +fluid.module.register("journal", __dirname, require); + +require("./src/SettingsDir.js"); +require("./src/JournalIdParser.js"); +require("./src/Journal.js"); + diff --git a/gpii/node_modules/journal/package.json b/gpii/node_modules/journal/package.json new file mode 100644 index 000000000..39f2bcb78 --- /dev/null +++ b/gpii/node_modules/journal/package.json @@ -0,0 +1,13 @@ +{ + "name": "journal", + "description": "Responsible for journalling the state of the system's settings in order to be able to recover from system crashes or corruption of the settings state", + "version": "0.3.0", + "author": "GPII", + "bugs": "http://issues.gpii.net/browse/GPII", + "homepage": "http://gpii.net/", + "dependencies": {}, + "license" : "BSD-3-Clause", + "repository": "git://github.com/GPII/universal.git", + "main": "./index.js", + "engines": { "node" : ">=4.2.1" } +} \ No newline at end of file diff --git a/gpii/node_modules/journal/src/Journal.js b/gpii/node_modules/journal/src/Journal.js new file mode 100644 index 000000000..5a26445f3 --- /dev/null +++ b/gpii/node_modules/journal/src/Journal.js @@ -0,0 +1,268 @@ +/** + * GPII Journal.js + * + * Responsible for journalling the state of the system's settings in order to be able to recover from system crashes or corruption of the settings state + * + * Copyright 2016 Raising the Floor - International + * + * Licensed under the New BSD license. You may not use this file except in + * compliance with this License. + * + */ + +"use strict"; + +var fluid = require("infusion"), + kettle = fluid.registerNamespace("kettle"), + gpii = fluid.registerNamespace("gpii"), + fs = require("fs"), + writeFileAtomic = require("write-file-atomic"), + glob = require("glob"); + +/** Manages reading and writing of journal files held within the "GPII settings directory", + * managed in a suitable platform-specific area of the user's settings files. + */ + +fluid.defaults("gpii.journal", { + gradeNames: "fluid.component", + maxOldJournals: 20, + components: { + settingsDir: { + type: "gpii.settingsDir" + } + }, + events: { + onUpdateSnapshot: null + }, + listeners: { + "onCreate.readJournals": "{that}.readJournals()", + "onCreate.trimJournals": { + funcName: "gpii.journal.trimJournals", + args: "{that}", + priority: "after:readJournals" + }, + "onCreate.findCrashed": { + funcName: "gpii.journal.findCrashed", + args: "{that}", + priority: "after:trimJournals" + } + }, + invokers: { + readJournals: "gpii.journal.readJournals({that})", + // args: {that}, {session}, {originalSettings}, closed + writeJournal: "gpii.journal.writeJournal" + } +}); + +/** A mixin grade supplied to gpii.journal accounting for its interaction with a lifecycleManager. + * In the main architecture this is supplied with the journal's definition in gpii.flowManager.local, + * which also incidentally assures that this component will definitely be constructed after the + * lifecycleManager. + */ + +fluid.defaults("gpii.journalLifecycleManager", { + gradeNames: "fluid.component", + listeners: { + "{lifecycleManager}.events.onSessionSnapshotUpdate": { + namespace: "writeJournal", + func: "{that}.writeJournal", + args: ["{that}", "{arguments}.1", "{arguments}.1.model.originalSettings", false] + }, + "{lifecycleManager}.events.onSessionStop": { + namespace: "writeJournal", + func: "{that}.writeJournal", + args: ["{that}", "{arguments}.1", "{arguments}.1.model.originalSettings", true] + } + }, + invokers: { + restoreJournal: { + funcName: "gpii.journal.restoreJournal", + args: ["{that}", "{lifecycleManager}", "{arguments}.0"] // journalId + } + } +}); + + +gpii.journal.restoreJournal = function (journalThat, lifecycleManager, journalId) { + var parsed = gpii.journal.parseId(journalId); + if (parsed.isError) { + fluid.fail(parsed.message); + } else { + fluid.log("Restoring journal with id " + journalId); + var journal = gpii.journal.fetchJournal(journalThat.journalFiles, parsed); + if (!journal) { + return fluid.promise().reject({ + isError: true, + message: "journal Id " + journalId + " could not be looked up to a journal" + }); + } else { + console.log("Restoring journal snapshot with contents " + JSON.stringify(journal, null, 2)); + return lifecycleManager.restoreSnapshot(journal.originalSettings); + } + } +}; + +/** A mixin grade supplied to gpii.journal which converts it into a kettle app hosting an HTTP endpoint + * currently just exposing the single function of restoring a particular journal + */ + +fluid.defaults("gpii.journalApp", { + gradeNames: "kettle.app", + invokers: { + dateToRestoreUrl: "gpii.journalApp.dateToRestoreUrl({that}, {kettle.server}, {arguments}.0)" + }, + serverUrl: "http://localhost", // TODO: provide a scheme either in Kettle or in universal for discovering this + listeners: { + "{lifecycleManager}.events.onSessionStart": { + namespace: "logRestoreUrl", + func: "gpii.journalApp.logRestoreUrl", + args: ["{that}", "{arguments}.0"] + } + }, + templates: { + listJournals: "Journals List

Click on a journal link to restore the system to its state at that time

%journals", + journalLink: "

System snapshot taken at %journalTime%crashedString

" + }, + requestHandlers: { + restore: { + route: "/journal/restore/:journalId", + method: "get", + type: "gpii.journal.restoreJournal.handler" + }, + journals: { + route: "/journal/journals.html", + method: "get", + type: "gpii.journal.journals.handler" + } + } +}); + +gpii.journalApp.dateToRestoreUrl = function (that, server, date) { + var baseUrl = that.options.serverUrl + ":" + server.options.port; + var pathTemplate = that.options.requestHandlers.restore.route.replace(":", "%"); + var journalId = ">" + new Date(date).toISOString(); + var pathReplaced = kettle.dataSource.URL.resolveUrl(pathTemplate, {journalId: journalId}); + var fullUrl = baseUrl + pathReplaced; + return fullUrl; +}; + +gpii.journalApp.logRestoreUrl = function (that, gradeName) { + if (gradeName === "gpii.lifecycleManager.userSession") { + var fullUrl = that.dateToRestoreUrl(Date.now()); + fluid.log(fluid.logLevel.WARN, "New user session starting: In future, you can visit the URL " + + fullUrl + " to restore the system's settings as they were at this point"); + } +}; + +fluid.defaults("gpii.journal.restoreJournal.handler", { + gradeNames: ["kettle.request.http"], + invokers: { + handleRequest: { + funcName: "gpii.journal.restoreJournal.handleRequest", + args: [ "{gpii.journal}", "{that}"] + } + } +}); + +gpii.journal.restoreJournal.handleRequest = function (journal, request) { + var journalId = request.req.params.journalId; + var restorePromise = journal.restoreJournal(journalId); + fluid.promise.follow(restorePromise, request.handlerPromise); +}; + +fluid.defaults("gpii.journal.journals.handler", { + gradeNames: ["kettle.request.http"], + invokers: { + handleRequest: { + funcName: "gpii.journal.journals.handleRequest", + args: [ "{gpii.journal}", "{that}"] + } + } +}); + +gpii.journal.journals.handleRequest = function (journal, request) { + journal.readJournals(); + var links = fluid.transform(journal.journalFiles, function (journalFile) { + var terms = { + journalUrl: journal.dateToRestoreUrl(journalFile.createTime), + journalTime: new Date(journalFile.createTime).toLocaleString(), + crashedString: journalFile.closed ? "" : ": crashed session" + }; + return fluid.stringTemplate(journal.options.templates.journalLink, terms); + }); + var linkBlock = links.join("\n"); + var page = fluid.stringTemplate(journal.options.templates.listJournals, { + journals: linkBlock + }); + request.res.header("Content-Type", "text/html"); + request.events.onSuccess.fire(page); +}; + +gpii.journal.writeJournal = function (that, session, snapshot, closed) { + var dir = that.settingsDir.getGpiiSettingsDir(); + var filename = "journal-" + gpii.journal.formatTimestamp(session.createTime) + ".json"; + var payload = { + userToken: session.options.userToken, + closed: closed, + createTime: session.createTime, + originalSettings: snapshot + }; + var formatted = JSON.stringify(payload, null, 4); + var fullPath = dir + "/" + filename; + fluid.log("Writing journal file to path " + fullPath); + writeFileAtomic.sync(fullPath, formatted); +}; + +// A comparator to sort journal entries in increasing order of age (newest first) +gpii.journal.dateComparator = function (journalA, journalB) { + return journalB.createTime - journalA.createTime; +}; + +gpii.journal.readJournals = function (that) { + var dir = that.settingsDir.getGpiiSettingsDir(); + var journalFileNames = glob.sync(dir + "/journal-*.json"); + console.log("Got journalFileNames ", journalFileNames); + var journalFiles = fluid.transform(journalFileNames, function (journalFileName) { + var readPromise = kettle.JSON.readFileSync(journalFileName, "reading journal file " + journalFileName); + var togo; + readPromise.then(function (read) { + togo = read; + }, function (err) { + fluid.log(fluid.logLevel.WARN, "Error reading journal file " + journalFileName + ": " + err.message); + togo = err; + }); + togo.journalFileName = journalFileName; + return togo; + }); + journalFiles.sort(gpii.journal.dateComparator); + that.journalFiles = journalFiles; + console.log("Got journalFiles ", journalFiles); +}; + +gpii.journal.trimJournals = function (that) { + if (that.journalFiles.length > that.options.maxOldJournals) { + for (var i = that.options.maxOldJournals; i < that.journalFiles.length; ++i) { + var journal = that.journalFiles[i]; + var fileName = journal.journalFileName; + fluid.log("Removing old journal file " + fileName + " since maximum number of old journals is " + that.options.maxOldJournals); + try { + fs.unlinkSync(fileName); + } catch (e) { + fluid.log(fluid.logLevel.WARN, "Error deleting old journal " + fileName + ": " + e); + } + } + that.journalFiles.length = that.options.maxOldJournals; + } +}; + +gpii.journal.findCrashed = function (that) { + var firstCrashed = fluid.find_if(that.journalFiles, function (journalFile) { + return journalFile.closed === false; + }); + if (firstCrashed) { + that.crashedJournal = firstCrashed; + fluid.log(fluid.logLevel.WARN, "Found crashed journal file on startup: " + JSON.stringify(firstCrashed, null, 2)); + } else { + fluid.log("Found no crashed journals on startup"); + } +}; diff --git a/gpii/node_modules/journal/src/JournalIdParser.js b/gpii/node_modules/journal/src/JournalIdParser.js new file mode 100644 index 000000000..a8acaeeff --- /dev/null +++ b/gpii/node_modules/journal/src/JournalIdParser.js @@ -0,0 +1,108 @@ +/** + * GPII JournalIdParser.js + * + * Responsible for journalling the state of the system's settings in order to be able to recover from system crashes or corruption of the settings state + * + * Copyright 2016 Raising the Floor - International + * + * Licensed under the New BSD license. You may not use this file except in + * compliance with this License. + * + */ + +"use strict"; + +var fluid = fluid || require("infusion"); + +var gpii = fluid.registerNamespace("gpii"); + +fluid.registerNamespace("gpii.journal"); + + +gpii.journal.idFormatError = fluid.freezeRecursive({ + isError: true, + message: "The only supported formats for journalId are HEAD, HEAD~, HEAD~2, etc. or <1995-10-9T10:11:12, >2009-11-29T01:02:59, etc." +}); + +gpii.journal.parseHeadId = function (journalId) { + var index = journalId.substring("HEAD".length), depth = NaN; + if (index.length > 0) { + if (index.charAt(0) === "~") { + var depthString = index.substring(1); + depth = depthString.length === 0 ? 1 : fluid.parseInteger(depthString); + } else { + return gpii.journal.idFormatError; + } + } else { + depth = 0; + } + if (Number.isNaN(depth)) { + return gpii.journal.idFormatError; + } else { + return { + type: "HEAD", + depth: depth + }; + } +}; + +gpii.journal.parseDateId = function (journalId) { + var date = Date.parse(journalId.substring(1)); + return Number.isNaN(date) ? gpii.journal.idFormatError : { + type: "DATE", + before: journalId.charAt(0) === "<" ? 1 : -1, + date: date + }; +}; + +gpii.journal.idParsers = { + "HEAD": gpii.journal.parseHeadId, + ">": gpii.journal.parseDateId, + "<": gpii.journal.parseDateId +}; + +/** Parse a journalId specification string into a JSON form + * @param journalId {String} A journalId string beginning with HEAD, < or > + * @return {Object} A {ParsedJournalId} holding a field `type` (either "HEAD" or "DATE") and other type-specific fields + */ + +gpii.journal.parseId = function (journalId) { + var togo = gpii.journal.idFormatError; + fluid.each(gpii.journal.idParsers, function (func, start) { + if (journalId.startsWith(start)) { + togo = func(journalId); + } + }); + return togo; +}; + +/** Dereference a journalId given an array of journal files + * @param journalFiles {Array of JournalFile} An array of JournalFile objects, sorted into increasing order of age + * @param parsed {Object} A {ParsedJournalId} as returned from `gpii.journal.parseId` which will be used to index into `journalFiles` + * @return {JournalFile|Undefined} One of the JournalFile entries supplied, or undefined if the supplied journalId does not match + */ +gpii.journal.fetchJournal = function (journalFiles, parsed) { + if (parsed.type === "HEAD") { + return journalFiles[parsed.depth]; + } else { + var before = parsed.before; + var toSearch = before === 1 ? journalFiles : fluid.makeArray(journalFiles).reverse(); + // For "before" - stop on the first one in reverse order (original) which is before + // For "after" - stop on the first one in forward order (reversed) which is after + return fluid.find_if(toSearch, function (journal) { + return journal.createTime * before <= parsed.date * before; + }); + } +}; + +/** Formats a millisecond timestamp into an ISO-8601 date string which is safe to + * appear in a filename + * @param {Number} A millisecond timestamp, as dispensed from `Date.now()` + * @return {String} An ISO-8601 date stamp without colon characters, e.g. 2016-07-05T220712.549Z + */ +gpii.journal.formatTimestamp = function (time) { + var date = new Date(time); + var stamp = date.toISOString(); + var safeStamp = stamp.replace(/:/g, ""); // This is still a valid ISO-8601 date string, but Date.parse() will no longer parse it + return safeStamp; +}; diff --git a/gpii/node_modules/journal/src/SettingsDir.js b/gpii/node_modules/journal/src/SettingsDir.js new file mode 100644 index 000000000..8614f5890 --- /dev/null +++ b/gpii/node_modules/journal/src/SettingsDir.js @@ -0,0 +1,101 @@ +/** + * GPII SettingsDir.js + * + * Determines the user's settings directory on a variety of platforms, + * and manages establishment of a subdirectory "/gpii" to hold writable files + * for the system's journal and other purposes + * + * Copyright 2016 Raising the Floor - International + * + * Licensed under the New BSD license. You may not use this file except in + * compliance with this License. + * + */ + +"use strict"; + +var fluid = require("infusion"), + gpii = fluid.registerNamespace("gpii"), + os = require("os"), + fs = require("fs"); + +fluid.defaults("gpii.settingsDir", { + gradeNames: ["fluid.component", "fluid.contextAware"], + contextAwareness: { + platform: { + checks: { + test: { + contextValue: "{gpii.contexts.test}", + gradeNames: "gpii.settingsDir.temp", + priority: "first" + }, + windows: { + contextValue: "{gpii.contexts.windows}", + gradeNames: "gpii.settingsDir.windows" + }, + linux: { + contextValue: "{gpii.contexts.linux}", + gradeNames: "gpii.settingsDir.standard" + } + }, + defaultGradeNames: "gpii.settingsDir.temp" + } + }, + members: { + gpiiSettingsDir: "@expand:{that}.getGpiiSettingsDir()" + }, + invokers: { + getGpiiSettingsDir: "gpii.settingsDir.gpii({that}.getBaseSettingsDir)", + getBaseSettingsDir: "fluid.notImplemented" + }, + listeners: { + "onCreate.createGpiiDir": "gpii.settingsDir.createGpii" + } +}); + +fluid.defaults("gpii.settingsDir.temp", { + invokers: { + getBaseSettingsDir: "gpii.settingsDir.baseDir.temp" + } +}); + +fluid.defaults("gpii.settingsDir.windows", { + invokers: { + getBaseSettingsDir: "gpii.settingsDir.baseDir.windows" + } +}); + +fluid.defaults("gpii.settingsDir.standard", { + invokers: { + getBaseSettingsDir: "gpii.settingsDir.baseDir.standard" + } +}); + +gpii.settingsDir.createGpii = function (that) { + fluid.log(fluid.logLevel.WARN, "Creating GPII settings directory in ", that.gpiiSettingsDir); + try { // See code skeleton in http://stackoverflow.com/questions/13696148/node-js-create-folder-or-use-existing + fs.mkdirSync(that.gpiiSettingsDir); + } catch (e) { + if (e.code !== "EEXIST") { + fluid.fail("Unable to create GPII settings directory, code is " + e.code + ": exception ", e); + } + } +}; + +gpii.settingsDir.gpii = function (getBaseSettingsDir) { + return getBaseSettingsDir() + "/gpii"; +}; + +fluid.registerNamespace("gpii.settingsDir.baseDir"); + +gpii.settingsDir.baseDir.windows = function () { + return process.env.APPDATA; +}; + +gpii.settingsDir.baseDir.standard = function () { + return os.homedir(); +}; + +gpii.settingsDir.baseDir.temp = function () { + return os.tmpdir(); +}; diff --git a/gpii/node_modules/journal/test/html/JournalIdParserTests.html b/gpii/node_modules/journal/test/html/JournalIdParserTests.html new file mode 100644 index 000000000..7bf73647c --- /dev/null +++ b/gpii/node_modules/journal/test/html/JournalIdParserTests.html @@ -0,0 +1,34 @@ + + + + + GPII Journal ID Parser Tests + + + + + + + + + + + + + + + + + + + + + + +

GPII Journal ID Parser Tests

+

+
+

+
    + + diff --git a/gpii/node_modules/journal/test/js/JournalIdParserTests.js b/gpii/node_modules/journal/test/js/JournalIdParserTests.js new file mode 100644 index 000000000..51e8de3a6 --- /dev/null +++ b/gpii/node_modules/journal/test/js/JournalIdParserTests.js @@ -0,0 +1,155 @@ +/** + * GPII Journal ID Parser Tests + * + * Copyright 2016 Raising the Floor - International + * + * Licensed under the New BSD license. You may not use this file except in + * compliance with this License. + * + * You may obtain a copy of the License at + * https://github.com/GPII/universal/blob/master/LICENSE.txt + */ + +/* global jqUnit */ + +"use strict"; + +var fluid = fluid || require("infusion"); + +(function () { + + var gpii = fluid.registerNamespace("gpii"); + + fluid.registerNamespace("gpii.tests.journal.idParser"); + + gpii.tests.journal.idParser.fixtures = [{ + toParse: "HEAD", + expected: { + type: "HEAD", + depth: 0 + } + }, { + toParse: "HEAD~", + expected: { + type: "HEAD", + depth: 1 + } + }, { + toParse: "HEAD~1", + expected: { + type: "HEAD", + depth: 1 + } + }, { + toParse: "HEAD~2", + expected: { + type: "HEAD", + depth: 2 + } + }, { + toParse: "HEAD~x", + expected: gpii.journal.idFormatError + }, { + toParse: "Unfortunate", + expected: gpii.journal.idFormatError + }, { + toParse: "<2000-01-01", + expected: { + type: "DATE", + before: 1, + date: 946684800000 + } + }, { + toParse: ">1997-07-16T12:00:00.155Z", + expected: { + type: "DATE", + before: -1, + date: 869054400155 + } + }, { + toParse: ">1997-07-16X12:00:00.155Z", + expected: gpii.journal.idFormatError + } + ]; + + jqUnit.test("Parsing journal ID specifications", function () { + fluid.each(gpii.tests.journal.idParser.fixtures, function (fixture, index) { + var parsed = gpii.journal.parseId(fixture.toParse); + jqUnit.assertDeepEq("Expected result for fixture " + index + ": " + fixture.toParse, fixture.expected, parsed); + }); + }); + + gpii.tests.journal.idParser.journalFiles = [{ + id: 1, + date: "2016-07-21T11:10:42.584Z" + }, { + id: 2, + date: "2016-07-20T20:58:52.654Z" + }, { + id: 3, + date: "2016-07-20T20:58:38.942Z" + }, { + id: 4, + date: "2016-07-20T20:57:01.402Z" + }, { + id: 5, + date: "2016-07-20T20:56:41.981Z" + }]; + + gpii.tests.journal.idParser.fetchFixtures = [{ + journalId: "HEAD", + expected: 1 + }, { + journalId: "HEAD~", + expected: 2 + }, { + journalId: "HEAD~5", + expected: undefined + }, { + journalId: ">2016-07-20T20:58:52.000Z", + expected: 2 + }, { + journalId: "<2016-07-20T20:58:52.000Z", + expected: 3 + }, { + journalId: ">2000-01-01T00:00:00.000Z", + expected: 5 + }, { + journalId: "<2000-01-01T00:00:00.000Z", + expected: undefined + }, { + journalId: ">2020-01-01T00:00:00.000Z", + expected: undefined + }, { + journalId: "<2020-01-01T00:00:00.000Z", + expected: 1 + }, { + journalId: ">2016-07-21T11:10:42.584Z", + expected: 1 + }, { + journalId: "<2016-07-21T11:10:42.584Z", + expected: 1 + }]; + + jqUnit.test("Journal fetch test", function () { + var journalFiles = fluid.transform(gpii.tests.journal.idParser.journalFiles, function (journalFile) { + return { + id: journalFile.id, + createTime: Date.parse(journalFile.date) + }; + }); + fluid.each(gpii.tests.journal.idParser.fetchFixtures, function (fixture, index) { + var parsed = gpii.journal.parseId(fixture.journalId); + var fetched = gpii.journal.fetchJournal(journalFiles, parsed); + var fetchedId = fluid.get(fetched, "id"); + jqUnit.assertEquals("Expected result for fixture " + index + ": " + fixture.journalId, fixture.expected, fetchedId); + }); + }); + + jqUnit.test("Datestamp to filename formatter", function () { + var date = new Date("2016-07-20T20:58:52.000Z").getTime(); + var sanitized = gpii.journal.formatTimestamp(date); + jqUnit.assertEquals("Sanitized datestamp to remove colon characters", "2016-07-20T205852.000Z", sanitized); + }); + +})(); diff --git a/gpii/node_modules/lifecycleActions/package.json b/gpii/node_modules/lifecycleActions/package.json index d45b2d90c..89e273f5f 100644 --- a/gpii/node_modules/lifecycleActions/package.json +++ b/gpii/node_modules/lifecycleActions/package.json @@ -1,19 +1,13 @@ { - "name": "lifecycleActions", - "description": "Located on user device. Common (portable) definitions for lifecycle actions.", - "version": "0.1", - "author": "GPII", - "bugs": "http://issues.gpii.net/browse/GPII", - "homepage": "http://gpii.net/", - "dependencies": {}, - "licenses": [ - { - "type": "BSD-3-Clause", - "url": "http://www.opensource.org/licenses/BSD-3-Clause" - } - ], - "keywords": ["gpii", "accessibility", "settings", "fluid", "IoC", "Inversion of Control", "configuration", "evented"], - "repository": "git://github.com/GPII/universal.git", - "main": "./index.js", - "engines": { "node" : ">=0.8" } + "name": "lifecycleActions", + "description": "Located on user device. Common (portable) definitions for lifecycle actions", + "version": "0.3.0", + "author": "GPII", + "bugs": "http://issues.gpii.net/browse/GPII", + "homepage": "http://gpii.net/", + "dependencies": {}, + "license" : "BSD-3-Clause", + "repository": "git://github.com/GPII/universal.git", + "main": "./index.js", + "engines": { "node" : ">=4.2.1" } } \ No newline at end of file diff --git a/gpii/node_modules/lifecycleManager/index.js b/gpii/node_modules/lifecycleManager/index.js index 7f99c1194..5697f1b2e 100644 --- a/gpii/node_modules/lifecycleManager/index.js +++ b/gpii/node_modules/lifecycleManager/index.js @@ -4,4 +4,7 @@ var fluid = require("infusion"); fluid.module.register("lifecycleManager", __dirname, require); +require("./src/DynamicComponentIndexer.js"); +require("./src/Resolvers.js"); +require("./src/LifecycleManagerSession.js"); require("./src/LifecycleManager.js"); diff --git a/gpii/node_modules/lifecycleManager/package.json b/gpii/node_modules/lifecycleManager/package.json index acc012356..9dcda9a81 100644 --- a/gpii/node_modules/lifecycleManager/package.json +++ b/gpii/node_modules/lifecycleManager/package.json @@ -1,19 +1,13 @@ { - "name": "lifecycleManager", - "description": "Located on user device. Invoked by the FlowManager to manage the lifecycle and configuration of a particular solution.", - "version": "0.1", - "author": "GPII", - "bugs": "http://issues.gpii.net/browse/GPII", - "homepage": "http://gpii.net/", - "dependencies": {}, - "licenses": [ - { - "type": "BSD-3-Clause", - "url": "http://www.opensource.org/licenses/BSD-3-Clause" - } - ], - "keywords": ["gpii", "accessibility", "settings", "fluid", "IoC", "Inversion of Control", "configuration", "evented"], - "repository": "git://github.com/GPII/universal.git", - "main": "./index.js", - "engines": { "node" : ">=0.8" } + "name": "lifecycleManager", + "description": "Located on user device. Invoked by the FlowManager to manage the lifecycle and configuration of a particular solution.", + "version": "0.3.0", + "author": "GPII", + "bugs": "http://issues.gpii.net/browse/GPII", + "homepage": "http://gpii.net/", + "dependencies": {}, + "license" : "BSD-3-Clause", + "repository": "git://github.com/GPII/universal.git", + "main": "./index.js", + "engines": { "node" : ">=4.2.1" } } \ No newline at end of file diff --git a/gpii/node_modules/lifecycleManager/src/DynamicComponentIndexer.js b/gpii/node_modules/lifecycleManager/src/DynamicComponentIndexer.js new file mode 100644 index 000000000..79cecc718 --- /dev/null +++ b/gpii/node_modules/lifecycleManager/src/DynamicComponentIndexer.js @@ -0,0 +1,59 @@ +/*! + * Dynamic Component Indexer + * + * Copyright 2016 Raising the Floor - International + * + * Licensed under the New BSD license. You may not use this file except in + * compliance with this License. + * + * 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"); +var gpii = fluid.registerNamespace("gpii"); + +(function () { + + /** Maintains an index on a parent component of a collection of dynamic components which maps + * the value held at some specified path on the dynamic component onto the component's member name + * A piece of "proto-framework" which is a framework candidate. + */ + + fluid.defaults("gpii.indexedDynamicComponent", { + gradeNames: "fluid.component", + components: { + // This reference needs to be overridden by the concrete grade user + // TODO: Enhance "notImplemented" scheme to support custom messages when user has not overridden material + // which requires to be overridden + dynamicIndexTarget: "{fluid.notImplemented}.mustBeOverridden" + }, + // The path of the collection/member at which the index is to be held + dynamicIndexTargetPath: "{fluid.notImplemented}.mustBeOverridden", + // The path in this component at which the key is to be found + dynamicIndexKeyPath: "{fluid.notImplemented}.mustBeOverridden", + listeners: { + "onCreate.indexedDynamicComponent": "gpii.indexedDynamicComponent.onCreate", + "onDestroy.indexedDynamicComponent": "gpii.indexedDynamicComponent.onDestroy" + } + }); + + gpii.indexedDynamicComponent.onCreate = function (that) { + var key = fluid.getForComponent(that, that.options.dynamicIndexKeyPath); + var ourPath = fluid.pathForComponent(that); + var memberName = ourPath[ourPath.length - 1]; + var index = fluid.get(that.dynamicIndexTarget, that.options.dynamicIndexTargetPath); + index[key] = memberName; + }; + + gpii.indexedDynamicComponent.onDestroy = function (that) { + var key = fluid.getForComponent(that, that.options.dynamicIndexKeyPath); + var index = fluid.get(that.dynamicIndexTarget, that.options.dynamicIndexTargetPath); + if (index) { // workaround for FLUID-5930 + delete index[key]; + } + }; + +})(); diff --git a/gpii/node_modules/lifecycleManager/src/LifecycleManager.js b/gpii/node_modules/lifecycleManager/src/LifecycleManager.js index 7b72d653d..d2832cdd2 100644 --- a/gpii/node_modules/lifecycleManager/src/LifecycleManager.js +++ b/gpii/node_modules/lifecycleManager/src/LifecycleManager.js @@ -22,7 +22,7 @@ var gpii = fluid.registerNamespace("gpii"); (function () { fluid.defaults("gpii.lifecycleManager", { - gradeNames: ["fluid.component"], + gradeNames: ["fluid.modelComponent"], retryOptions: { // Options governing how often to recheck whether settings are set (and in future, to check for running processes) rewriteEvery: 3, // Every 3rd attempt, rewrite the required settings again numRetries: 12, // Make 12 attempts over a period of 12 seconds to discover whether settings are set @@ -36,156 +36,114 @@ var gpii = fluid.registerNamespace("gpii"); type: "gpii.lifecycleManager.nameResolver" } }, + dynamicComponents: { + sessions: { + type: "{arguments}.0", // a session grade derived from gpii.lifecycleManager.session + options: { + userToken: "{arguments}.1" + }, + createOnEvent: "onSessionStart" + } + }, members: { - activeSessions: {} + sessionIndex: {} // map of userToken to session component member name, managed by gpii.lifecycleManager.sessionIndexer + }, + events: { + onSessionStart: null, // fired with [gradeName, userToken] + onSessionSnapshotUpdate: null, // fired with [{lifecycleManager}, {session}, originalSettings] + onSessionStop: null // fired with [{lifecycleManager}, {session} + }, + listeners: { + "onSessionSnapshotUpdate.log": "gpii.lifecycleManager.logSnapshotUpdate" }, invokers: { getActiveSessionTokens: { funcName: "gpii.lifecycleManager.getActiveSessionTokens", - args: "{that}.activeSessions" + args: "{that}" }, + /** Accepts an array of user tokens, of which all but the first will currently be ignored. Returns the session + * component corresponding to that user, if they have an active session. A typical usage pattern is to call "getActiveSessionTokens" + * and send its return to "getSession" + */ getSession: { funcName: "gpii.lifecycleManager.getSession", - args: ["{that}.activeSessions", "{arguments}.0"] // user token + args: ["{that}", "{arguments}.0"] // user token }, // TODO: refactor these three methods so that they return promises // TODO: really do it! stop: { funcName: "gpii.lifecycleManager.stop", - args: ["{that}", "{request}", "{arguments}.0", "{arguments}.1"] + args: ["{that}", "{kettle.request}", "{arguments}.0", "{arguments}.1"] }, // options, callback start: { funcName: "gpii.lifecycleManager.start", - args: ["{that}", "{request}", "{arguments}.0", "{arguments}.1"] + args: ["{that}", "{kettle.request}", "{arguments}.0", "{arguments}.1"] // finalPayload, callback }, update: { funcName: "gpii.lifecycleManager.update", - args: ["{that}", "{request}", "{arguments}.0", "{arguments}.1"] + args: ["{that}", "{kettle.request}", "{arguments}.0", "{arguments}.1"] }, // finalPayload, callback applySolution: { funcName: "gpii.lifecycleManager.applySolution", args: ["{that}", "{arguments}.0", "{arguments}.1", "{arguments}.2", "{arguments}.3", "{arguments}.4"] - // solutionId, solutionRecord, sessionState, lifecycleBlocksKeys, saveSnapshot - }, - stopSolution: { - funcName: "gpii.lifecycleManager.stopSolution", - args: ["{that}", "{arguments}.0", "{arguments}.1", "{arguments}.2", "{arguments}.3"] - // solutionId, solutionRecord, sessionState, lifecycleBlockKeys + // solutionId, solutionRecord, session, lifecycleBlocksKeys, rootAction }, executeActions: { funcName: "gpii.lifecycleManager.executeActions", - args: ["{that}", "{arguments}.0", "{arguments}.1", "{arguments}.2", "{arguments}.3"] - // solutionId, settingsHandlers, actions, sessionState + args: ["{that}", "{arguments}.0", "{arguments}.1", "{arguments}.2", "{arguments}.3", "{arguments}.4"] + // solutionId, settingsHandlers, actions, session, rootAction }, - invokeSettingsHandlers: { - funcName: "gpii.lifecycleManager.invokeSettingsHandlers", + invokeSettingsHandler: { + funcName: "gpii.lifecycleManager.invokeSettingsHandler", args: ["{that}", "{arguments}.0", "{arguments}.1", "{arguments}.2"] // solutionId, settingsHandlers, isMultiSH - } - } - }); - - // A standard interception point so that the process of resolving names onto - // settings handlers and actions can be mocked for integration tests - fluid.defaults("gpii.lifecycleManager.nameResolver", { - gradeNames: ["fluid.component"], - invokers: { - resolveName: { - funcName: "fluid.identity" - } - } - }); - - fluid.defaults("gpii.lifecycleManager.variableResolver", { - gradeNames: ["fluid.component"], - components: { - resolverConfig: { - type: "gpii.lifecycleManager.standardResolverConfig" - } - }, - members: { - resolvers: { - expander: { - func: "gpii.lifecycleManager.variableResolver.computeResolvers", - args: "{that}.resolverConfig.options.resolvers" - } }, - fetcher: { - expander: { - func: "gpii.resolversToFetcher", - args: "{that}.resolvers" - } - } - }, - invokers: { - resolve: { - funcName: "gpii.lifecycleManager.variableResolver.resolve", - args: ["{arguments}.0", "{that}.fetcher", "{arguments}.1"] + restoreSnapshot: { + funcName: "gpii.lifecycleManager.restoreSnapshot", + args: ["{that}", "{arguments}.0"] + // originalSettings } } }); - gpii.lifecycleManager.variableResolver.computeResolvers = function (resolvers) { - return fluid.transform(resolvers, fluid.getGlobalValue); + gpii.lifecycleManager.logSnapshotUpdate = function (lifecycleManager, session, originalSettings) { + fluid.log("Settings for session " + session.id + " created at " + session.createTime + " updated to ", originalSettings); }; - gpii.lifecycleManager.variableResolver.resolve = function (material, fetcher, extraFetcher) { - return fluid.expand(material, { - bareContextRefs: false, - // TODO: FLUID-4932 - the framework currently has no wildcard support in mergePolicy. - mergePolicy: { - 0: { - capabilitiesTransformations: { - "*": { - noexpand: true - } - } - } - }, - fetcher: gpii.combineFetchers(fetcher, extraFetcher) - }); - }; - - gpii.resolversToFetcher = function (resolvers) { - return function (parsed) { - var resolver = resolvers[parsed.context]; - return !resolver ? undefined : ( - typeof(resolver) === "function" ? - resolver(parsed.path) : fluid.get(resolver, parsed.path)); - }; - }; - - gpii.combineFetchers = function (main, fallback) { - return fallback ? function (parsed) { - var fetched = main(parsed); - return fetched === undefined ? fallback(parsed) : fetched; - } : main; + // Will return one of the user's token keys for an active session + // TODO: We need to implement logic to ensure at most one of these is set, or + // to manage logic for superposition of sessions if we permit several (see GPII-102) + gpii.lifecycleManager.getActiveSessionTokens = function (that) { + return fluid.keys(that.sessionIndex); }; - fluid.defaults("gpii.lifecycleManager.standardResolverConfig", { - gradeNames: "fluid.component", - resolvers: { - environment: "gpii.lifecycleManager.environmentResolver" + gpii.lifecycleManager.getSession = function (that, userTokens) { + userTokens = fluid.makeArray(userTokens); + if (userTokens.length === 0) { + fluid.fail("Attempt to get sessions without keys"); + } else { + var memberKey = that.sessionIndex[userTokens[0]]; + if (memberKey === undefined) { + return undefined; + } + return that[memberKey]; } - }); - - gpii.lifecycleManager.environmentResolver = function (name) { - return process.env[name]; }; - // Transforms the handlerSpec (handler part of the transformer's response payload) to a form - // accepted by a settingsHandler - we use a 1-element array holding the payload for a single solution - // per handler - // @param isMultiSH {boolean} is a flag denoting whether it's a multi-settings handlers solution - // If it is, a supported settings block is required for each entry + /** Transforms the handlerSpec (handler part of the transformer's response payload) to a form + * accepted by a settingsHandler - we use a 1-element array holding the payload for a single solution + * per handler + * @param isMultiSH {boolean} is a flag denoting whether it's a multi-settingshandler solution + * If it is, a supported settings block is required for each entry + */ gpii.lifecycleManager.specToSettingsHandler = function (solutionId, handlerSpec, isMultiSH) { var returnObj = {}, settings = {}; if (handlerSpec.supportedSettings === undefined) { if (isMultiSH) { - fluid.fail("Solution " + solutionId + " has multiple settingshandler but is missing " + + fluid.fail("Solution " + solutionId + " has multiple settingshandlers but is missing " + "the 'supportedSettings' directive. Will not set the settings for this settingshandler"); } else { // if supportedSettings directive is not present, pass all settings: @@ -209,22 +167,27 @@ var gpii = fluid.registerNamespace("gpii"); return returnObj; // NB array removed here }; - // Transform the response from the handler SET to a format that we can pass to handler SET on restore - "oldValue" becomes plain value + // Transform the response from the handler SET to a format that we can persist in models before passing to handler SET on restore + // - "oldValue" becomes {type: "ADD", value: } + // - `undefined` value becomes {type: "DELETE"} gpii.lifecycleManager.responseToSnapshot = function (solutionId, handlerResponse) { var unValued = gpii.settingsHandlers.setResponseToSnapshot(handlerResponse); + var armoured = gpii.settingsHandlers.settingsPayloadToChanges(unValued); // Note - we deal in these 1-element arrays just for simplicity in the LifecycleManager. A more efficient // implementation might send settings for multiple solutions to the same settingsHandler in a single request. // Note that in the session's snapshots, this level of array containment has been removed. - return fluid.get(unValued, [solutionId, 0]); + return fluid.get(armoured, [solutionId, 0]); }; - //@param handlerSpec {Object} A single settings handler specification - //@param isMultiSH {boolean} if present and true, the solution has multiple settingshandlers - // Payload example: - // http://wiki.gpii.net/index.php/Settings_Handler_Payload_Examples - // Transformer output: - // http://wiki.gpii.net/index.php/Transformer_Payload_Examples - gpii.lifecycleManager.invokeSettingsHandlers = function (that, solutionId, handlerSpec, isMultiSH) { + /** + * @param handlerSpec {Object} A single settings handler specification + * @param isMultiSH {Boolean} [optional] if present and true, the solution has multiple settingshandlers + * Payload example: + * http://wiki.gpii.net/index.php/Settings_Handler_Payload_Examples + * Transformer output: + * http://wiki.gpii.net/index.php/Transformer_Payload_Examples + */ + gpii.lifecycleManager.invokeSettingsHandler = function (that, solutionId, handlerSpec, isMultiSH) { // first prepare the payload for the settingsHandler in question - a more efficient // implementation might bulk together payloads destined for the same handler var settingsHandlerPayload = gpii.lifecycleManager.specToSettingsHandler(solutionId, handlerSpec, isMultiSH); @@ -254,48 +217,113 @@ var gpii = fluid.registerNamespace("gpii"); return fluid.invokeGradedFunction(resolvedName, action); }; + /** Compensate for the effect of simpleminded merging when applied to a snapshot where an "DELETE" is merged on top of an "ADD" + */ + gpii.lifecycleManager.cleanDeletes = function (value) { + if (value.type === "DELETE") { + delete value.value; + } + return value; + }; + + /** Applies snapshotted settings from a single settingsHandler block attached to a single solution into the "originalSettings" + * model snapshot area in the LifecycleManager's session. Tightly bound to executeSettingsAction, executes one-to-one with it with + * almost identical argument list. + */ + + gpii.lifecycleManager.recordSnapshotInSession = function (that, snapshot, solutionId, solutionRecord, session, settingsHandlerBlockName, rootAction) { + var toSnapshot = fluid.copy(solutionRecord); + toSnapshot.settingsHandlers = {}; + toSnapshot.settingsHandlers[settingsHandlerBlockName] = snapshot; + if (rootAction === "start") { + session.applier.change(["originalSettings", solutionId], toSnapshot); + } else if (rootAction === "update") { + // if we're doing an update, keep the settings that are already stored from the + // original snapshot, but augment it with any settings from the new snapshot + // that were not present in the original snapshot. + // This workflow is tested in LifecycleManagerTests.js "Updating with normal reference to settingsHandler block, and 'undefined' value stored in snapshot" + var mergedSettings = fluid.extend(true, {}, toSnapshot, session.model.originalSettings[solutionId]); + var cleanedSettings = gpii.lifecycleManager.transformSolutionSettings(mergedSettings, gpii.lifecycleManager.cleanDeletes); + session.applier.change(["originalSettings", solutionId], cleanedSettings); + } else if (rootAction === "restore" || rootAction === "stop") { + // no-op - during a restore action we don't attempt to create a further snapshot + } else { + fluid.fail("Unrecognised rootAction " + rootAction); + } + }; + /** * @param that {Object} The lifecycle manager component * @param solutionId {String} the ID of the solution for which to execute the settings * @param solutionRecord {Object} The solution registry entry for the solution - * @param sessionState {Object} The current sessionState. This function will attach the - * solution record to the 'appliedSolutions' of the sessionState object (if successful) - * @param settingsReturn {Object} this object will contain the snapshotted settings (ie. recorded before new - * settings are applied). It can contain already recorded settings, but *will also be modified* by this - * function. - * @param settingsHandlerBlcok {String} should be a reference to a settings block from the + * @param session {Component} The current session component. This function will attach the + * solution record to the 'appliedSolutions' of the session's model (if successful) + * @param settingsHandlerBlockName {String} should be a reference to a settings block from the * settingsHandlers section. - * @return {Function} a function, that once executed will set the settings returning a promise + * @param rootAction {String} The root action on the LifecycleManager which is being serviced: "start", "stop" or "update" + * @return {Function} a nullary function (a task), that once executed will set the settings returning a promise * that will be resolved once the settings are successfully set. */ - gpii.lifecycleManager.executeSettingsAction = function (that, solutionId, solutionRecord, sessionState, settingsReturn, settingsHandlerBlockName) { + gpii.lifecycleManager.executeSettingsAction = function (that, solutionId, solutionRecord, session, settingsHandlerBlockName, rootAction) { var isMultiSH = Object.keys(solutionRecord.settingsHandlers).length > 1; var settingsHandlerBlock = solutionRecord.settingsHandlers[settingsHandlerBlockName]; if (settingsHandlerBlock === undefined) { - fluid.fail("Reference to non-existing settingshandler block named " + settingsHandlerBlockName + + fluid.fail("Reference to non-existing settingsHandler block named " + settingsHandlerBlockName + " in solution " + solutionId); } return function () { - var expanded = sessionState.localResolver(settingsHandlerBlock); - var settingsPromise = that.invokeSettingsHandlers(solutionId, expanded, isMultiSH); + var expanded = session.localResolver(settingsHandlerBlock); + var settingsPromise = that.invokeSettingsHandler(solutionId, expanded, isMultiSH); settingsPromise.then(function (snapshot) { - if (!sessionState.appliedSolutions) { - sessionState.appliedSolutions = {}; - } - sessionState.appliedSolutions[solutionId] = solutionRecord; - settingsReturn[settingsHandlerBlockName] = snapshot; + session.applier.change(["appliedSolutions", solutionId], solutionRecord); + gpii.lifecycleManager.recordSnapshotInSession(that, snapshot, solutionId, solutionRecord, session, settingsHandlerBlockName, rootAction); }); return settingsPromise; }; }; - // Called for each solution during both the "stop" and "start" phases - it is agnostic as to which phase it is. Actions to be performed - // are held in array "actions" and the settingsHandlers block from "solutions" (either Transformer output, or snapshot output from "start" - // phase) encodes the settings to be set - // Returns the results from the settings action present in the list, and builds up action returns in session state "actionResults" field - // (so that these may be referenced from context expressions in further actions). - gpii.lifecycleManager.executeActions = function (that, solutionId, solutionRecord, sessionState, actionBlock) { - var settingsReturn = {}; + /** For the complete entry for a single solution, transform each settingsHandler block by a supplied function - traditionally + * either gpii.settingsHandlers.changesToSettings or gpii.settingsHandlers.settingsToChanges . + * This is traditionally called during the "stop" action to unarmour all the settingsHandler blocks by converting from changes back to settings. In a + * future version of the SettingsHandler API, this will not be necessary. + * This is called during the "stop" action to convert the snapshotted "originalSettings" model material back to material suitable for being sent + * to executeSettingsAction, as well as at the corresponding point during the "journal restore" operation + * @param solutionSettings {Object} A settings block for a single solution, holding a member named `settingsHandlers` + * @param transformer {Function} A function which will transform one settingsHandlers block in the supplied `solutionSettings` + */ + gpii.lifecycleManager.transformSolutionSettings = function (solutionSettings, transformer) { + var togo = fluid.copy(solutionSettings); // safe since armoured + togo.settingsHandlers = fluid.transform(solutionSettings.settingsHandlers, function (handlerBlock) { + return gpii.settingsHandlers.transformOneSolutionSettings(handlerBlock, transformer); + }); + return togo; + }; + + /** As for gpii.lifecycleManager.transformSolutionSettings, only transforms the complete collection of stored solutions + * (e.g. a value like session.model.originalSettings) + */ + gpii.lifecycleManager.transformAllSolutionSettings = function (allSettings, transformer) { + return fluid.transform(allSettings, function (solutionSettings) { + return gpii.lifecycleManager.transformSolutionSettings(solutionSettings, transformer); + }); + }; + + /** Called for each solution during "start", "stop" and "update" phases + * Actions to be performed + * are held in array "actions" and the settingsHandlers block from "solutions" (either Transformer output, or snapshot output from "start" + * phase) encodes the settings to be set + * Returns the results from the settings action present in the list, and builds up action returns in session state "actionResults" field + * (so that these may be referenced from context expressions in further actions). + * @param that {Component:LifecycleManager} The LifecycleManager instance of type `gpii.lifecycleManager` + * @param solutionId {String} The id of the solution for which actions are to be performed + * @param solutionRecord {Object} Either the solution's registry entry (on start) or the fabricated "restore" entry (on stop) + * @param session {Component:LifecycleManagerSession} The LifecycleManager Session of type `gpii.lifecycleManager.session` + * @param actionBlock {String} The key for the particular block in the solutions registry entry which is being acted on - this may + * differ from `rootAction` because, e.g. an "update" action may be serviced by stopping and starting the solution, etc. + * @param rootAction {String} The root action on the LifecycleManager which is being serviced: "start", "stop" or "update" + */ + + gpii.lifecycleManager.executeActions = function (that, solutionId, solutionRecord, session, actionBlock, rootAction) { var steps = solutionRecord[actionBlock]; if (steps === undefined) { fluid.log("No " + actionBlock + " actions defined for solution " + solutionId); @@ -307,27 +335,29 @@ var gpii = fluid.registerNamespace("gpii"); // the settings handler block) if (action.indexOf("settings.") === 0) { var settingsHandlerBlockName = action.substring("settings.".length); - return gpii.lifecycleManager.executeSettingsAction(that, solutionId, solutionRecord, sessionState, settingsReturn, settingsHandlerBlockName); + return gpii.lifecycleManager.executeSettingsAction(that, solutionId, solutionRecord, session, settingsHandlerBlockName, rootAction); } else if (actionBlock === "update") { // Keywords: "start", "stop", "configure" are allowed here as well, and // and will result in evaluating the respective block - // TODO (GPII-1230) Fix this up so we dont always run the full start and stops (including) + // TODO (GPII-1230) Fix this up so we don't always run the full start and stops (including) // system restoration, etc. if (action === "start" || action === "configure") { - return that.executeActions(solutionId, solutionRecord, sessionState, action); + return that.executeActions(solutionId, solutionRecord, session, action, rootAction); } else if (action === "stop") { - return that.executeActions(solutionId, sessionState.originalSettings[solutionId], sessionState, "stop"); + // This branch is used when an "update" action requires a stop and start of the solution + var unarmoured = gpii.lifecycleManager.transformSolutionSettings(session.model.originalSettings[solutionId], gpii.settingsHandlers.changesToSettings); + return that.executeActions(solutionId, unarmoured, session, "stop", rootAction); } else { fluid.fail("Unrecognised string action in LifecycleManager: " + action + " inside 'update' section for solution " + solutionId); } } - } else { // TODO should be removed when GPII-1235 has been solved + } else { // TODO should be removed when GPII-1235 has been solved (lifecycle actions are settings handlers) return function () { - var expanded = sessionState.localResolver(action); + var expanded = session.localResolver(action); var result = gpii.lifecycleManager.invokeAction(expanded, that.nameResolver); if (action.name) { - sessionState.actionResults[action.name] = result; + session.applier.change(["actionResults", action.name], result); } }; } @@ -335,98 +365,69 @@ var gpii = fluid.registerNamespace("gpii"); var resolved = fluid.promise.sequence(sequence); var togo = fluid.promise(); resolved.then(function () { - togo.resolve(settingsReturn); + togo.resolve(); }, togo.reject); return togo; }; - /** Invoked on both "start" and "update" phases - in addition to forwarding to gpii.lifecycleManager.executeActions, + /** Invoked on "start", "update" and "stop" phases - in addition to forwarding to gpii.lifecycleManager.executeActions, * it is responsible for saving the settings that are being set (when the fullSnapshot is true) and storing - * the list of applied solutions to the sessionState + * the list of applied solutions to the session state * * @param solutionId {String} the ID of the solution * @param solutionRecord {Object} a solution record with settings that are to be applied to the system - * @param sessionState {Object} the object holding the state of the system - * @param lifecycleBlockKeys {Array} Array of ordered strings denoting which lifecycle blocks to run (usually "configure", "start" and/or "update") - * @param fullSnapshot {boolean} indicates whether a full snapshot of the original settings should be taken - * before the new settings are written. If false, only changed settings that are not part of - * an already existing snapshot will be saved + * @param session {Object} the object holding the state of the system. This is updated in place by the settings application process, if `rootAction` is start. + * @param lifecycleBlockKeys {Array} Array of ordered strings denoting which lifecycle blocks to run (supported values here are "configure", "start" and/or "update") + * @param rootAction {String} Either "start", "update" or "stop" depending on the lifecycleManager phase which is executing * @return {Promise} The same promise yielded by executeActions - the stateful construction of the session state is tacked onto this promise * as a side-effect */ - gpii.lifecycleManager.applySolution = function (that, solutionId, solutionRecord, sessionState, lifecycleBlockKeys, fullSnapshot) { - var promises = []; - fluid.each(lifecycleBlockKeys, function (key) { - promises.push(that.executeActions(solutionId, solutionRecord, sessionState, key)); - }); - var promiseSequence = fluid.promise.sequence(promises); - // snapshots is an array of snapshotted settingshandler results (one for each lifecycle action block) - promiseSequence.then(function (snapshots) { - var toSnapshot = fluid.copy(solutionRecord); - toSnapshot.settingsHandlers = {}; - fluid.each(snapshots, function (snapshot) { - if (snapshot !== undefined) { // can be removed once everything is settingsHandlers (GPII-1235) - fluid.each(snapshot, function (handlerBlock, blockKey) { - toSnapshot.settingsHandlers[blockKey] = handlerBlock; - }); - } - }); - if (fullSnapshot) { - sessionState.originalSettings[solutionId] = toSnapshot; - } else { - // if we're doing an update, keep the settings that are already stored from the - // original snapshot, but augment it with any settings from the new snapshot - // that were not present in the original snapshot. - // First merge the basic structures: - var tmpOrigSettings = fluid.extend(true, {}, toSnapshot, sessionState.originalSettings[solutionId]); - - // ensure that our augmented snapshot has any 'undefined' settings value copied over as well - gpii.settingsHandlers.copySettings(tmpOrigSettings.settingsHandlers, toSnapshot.settingsHandlers); - - // now recopy the original settings snapshot on top of the stored settings, but this time - // make sure that the settings from the original snapshot with a stored value of 'undefined' are copied over - // properly - if (sessionState.originalSettings[solutionId]) { - gpii.settingsHandlers.copySettings(tmpOrigSettings.settingsHandlers, sessionState.originalSettings[solutionId].settingsHandlers); - } - sessionState.originalSettings[solutionId] = tmpOrigSettings; + gpii.lifecycleManager.applySolution = function (that, solutionId, solutionRecord, session, lifecycleBlockKeys, rootAction) { + var promises = fluid.transform(lifecycleBlockKeys, function (key) { + // Courtesy to allow GPII-580 journalling tests to be expressed in-process - better expressed with FLUID-5790 cancellable promises + if (!fluid.isDestroyed(that)) { + return that.executeActions(solutionId, solutionRecord, session, key, rootAction); } }); - return promiseSequence; + return fluid.promise.sequence(promises); }; - /** - * Invoked on "stop" phase. Will run the relevant entries via the executeActions function. - * The expected outcome is that solution should be restored to its original state - * - * @param solutionId {String} the ID of the solution - * @param solutionRecord {Object} consists of the original settings of the given solution - * @param sessionState {Object} the object holding the state of the system - * @param lifecycleBlockKeys {Array} Array of ordered strings denoting which lifecycle blocks to - run (usually "restore" and/or "stop" - * @return {Promise} A promise (sequence of promises) as returned by executeActions - */ - gpii.lifecycleManager.stopSolution = function (that, solutionId, solutionRecord, sessionState, lifecycleBlockKeys) { - var promises = []; - fluid.each(lifecycleBlockKeys, function (key) { - promises.push(that.executeActions(solutionId, solutionRecord, sessionState, key)); + /** Common utility used by gpii.lifecycleManager.stop and gpii.lifecycleManager.restoreSnapshot + * @param session {gpii.lifecycleManager.session} which must contain + * * A `originalSettings` snapshot in its model + * * A `localResolver` member for expanding material + * @param rootAction {String} Must be either "stop" or "restore" + * @return {Promise} A promise for the action of restoring the system + */ + gpii.lifecycleManager.restoreSystem = function (that, session, rootAction) { + var promises = fluid.transform(session.model.originalSettings, function (changesSolutionRecord, solutionId) { + var solutionRecord = gpii.lifecycleManager.transformSolutionSettings(changesSolutionRecord, gpii.settingsHandlers.changesToSettings); + return that.applySolution(solutionId, solutionRecord, session, [ "stop", "restore" ], rootAction); }); - return fluid.promise.sequence(promises); - }; - // Will return one of the users token keys for an active session - // TODO: We need to implement logic to ensure at most one of these is set, or - // to manage logic for superposition of sessions if we permit several (see GPII-102) - gpii.lifecycleManager.getActiveSessionTokens = function (activeSessions) { - return fluid.keys(activeSessions); + // TODO: In theory we could stop all solutions in parallel + var sequence = fluid.promise.sequence(fluid.values(promises)); + return sequence; }; - gpii.lifecycleManager.getSession = function (activeSessions, userTokens) { - if (userTokens.length === 0) { - fluid.fail("Attempt to get sessions without keys"); - } else { - return activeSessions[userTokens[0]]; - } + /** Restore a snapshot of settings, perhaps captured in the journal. This constructs a "fake" session using the special user token "restore" + * @param originalSettings {Object} The system snapshot to be restored + * @return A promise for the action of restoring the system + */ + gpii.lifecycleManager.restoreSnapshot = function (that, originalSettings) { + // TODO: document/ensure that this token, as well as the special "reset", is reserved + that.events.onSessionStart.fire("gpii.lifecycleManager.restoreSession", "restore"); + var session = that[that.sessionIndex.restore]; + session.applier.change("originalSettings", originalSettings); + var restorePromise = gpii.lifecycleManager.restoreSystem(that, session, "restore"); + restorePromise.then(session.destroy, session.destroy); + + return fluid.promise.map(restorePromise, function () { + return { // TODO: The standard response yield is unhelpful, consisting of the returns of any actions in "stop" + message: "The system's settings were restored from a snapshot", + payload: originalSettings + }; + }); }; /** @@ -437,20 +438,16 @@ var gpii = fluid.registerNamespace("gpii"); */ gpii.lifecycleManager.stop = function (that, request, options, callback) { var userToken = options.userToken; - var sessionState = that.activeSessions[userToken]; - if (!sessionState) { + var session = that.getSession([userToken]); + if (!session) { callback(false); return; } + var restorePromise = gpii.lifecycleManager.restoreSystem(that, session, "stop"); - var promises = fluid.transform(sessionState.originalSettings, function (solutionRecord, solutionId) { - return that.stopSolution(solutionId, solutionRecord, sessionState, [ "stop", "restore" ]); - }); - - // TODO: In theory we could stop all solutions in parallel - var sequence = fluid.promise.sequence(fluid.values(promises)); - sequence.then(function () { - delete that.activeSessions[userToken]; + restorePromise.then(function () { + that.events.onSessionStop.fire(that, session); + session.destroy(); callback(true); }, function (error) { request.events.onError.fire(error); @@ -463,19 +460,20 @@ var gpii = fluid.registerNamespace("gpii"); gpii.lifecycleManager.update = function (that, request, finalPayload, callback) { var userToken = finalPayload.userToken, lifecycleInstructions = finalPayload.lifecycleInstructions; - var sessionState = that.activeSessions[userToken]; - if (!sessionState) { - fluid.fail("User with token ", userToken, " has no active session"); + var session = that.getSession([userToken]); + if (!session) { + fluid.fail("User with token " + userToken + " has no active session"); } + var appliedSolutions = session.model.appliedSolutions; var promises = []; fluid.each(lifecycleInstructions, function (solution, solutionId) { var sol = fluid.copy(solution); - if (sessionState.appliedSolutions && sessionState.appliedSolutions[solutionId]) { + if (appliedSolutions[solutionId]) { // merge already applied settings with the updates - sol = fluid.extend(true, {}, sessionState.appliedSolutions[solutionId], sol); + sol = fluid.extend(true, {}, appliedSolutions[solutionId], sol); } - promises.push(that.applySolution(solutionId, sol, sessionState, [ "update" ], false)); + promises.push(that.applySolution(solutionId, sol, session, [ "update" ], "update")); }); var sequence = fluid.promise.sequence(promises); sequence.then(function () { @@ -489,47 +487,39 @@ var gpii = fluid.registerNamespace("gpii"); gpii.lifecycleManager.start = function (that, request, finalPayload, callback) { var userToken = finalPayload.userToken, lifecycleInstructions = finalPayload.lifecycleInstructions; - if (that.activeSessions[userToken]) { + if (that.sessionIndex[userToken]) { // TODO: develop async architecture to prevent rat's nest of callbacks + // TODO: It seems this could never have been tested with solutions with an asynchronous stop action (which we have none currently) that.stop({userToken: userToken}, fluid.identity); } + that.events.onSessionStart.fire("gpii.lifecycleManager.userSession", userToken); + var session = that[that.sessionIndex[userToken]]; // TODO: Make global map of all users of session state // activeConfiguration: Assigned in contextManager.evaluateMatch, consumed in UserUpdate // userToken, solutionsRegistryEntries: Assigned in initialPayload, consumed in UserUpdate - var sessionState = fluid.extend(true, { - actionResults: {} - }, fluid.filterKeys(finalPayload, ["userToken", "activeContextName", "activeConfiguration", "solutionsRegistryEntries", "matchMakerOutput"])); - - // let the user's token as well as any named action results accumulated - // to date be resolvable for any future action - sessionState.localFetcher = gpii.combineFetchers( - gpii.resolversToFetcher({userToken: userToken}), - gpii.resolversToFetcher(sessionState.actionResults)); - - sessionState.localResolver = function (material) { - return that.variableResolver.resolve(material, sessionState.localFetcher); - }; + var filteredPayload = fluid.filterKeys(finalPayload, ["userToken", "activeContextName", "activeConfiguration", "solutionsRegistryEntries", "matchMakerOutput"]); + session.applier.change("", filteredPayload); // TODO: One day after the applier is refactored this will explicitly need to be a "MERGE" // This "start" action will result in the original settings of the system (i.e. those that - // were on the system before the user logged in) being stored inside sessionState.originalSettings. + // were on the system before the user logged in) being stored inside session.model.originalSettings. // When "stop" is called this payload will be used to restore the settings back to their // original state. - sessionState.originalSettings = {}; var tasks = []; fluid.each(lifecycleInstructions, function (solution, solutionId) { tasks.push(function () { - // build structure for returned values (for later reset) - return that.applySolution(solutionId, solution, sessionState, - (solution.active ? [ "configure", "start" ] : [ "configure" ]), true); + if (!fluid.isDestroyed(that)) { // See above comment for GPII-580 + // build structure for returned values (for later reset) + return that.applySolution(solutionId, solution, session, + (solution.active ? [ "configure", "start" ] : [ "configure" ]), "start"); + } }); }); - that.activeSessions[userToken] = sessionState; - // Note that these promises are only sequenced for their side-effects (the ones on sessionState within applySolution and on the system at large + // Note that these promises are only sequenced for their side-effects (the ones on session state within applySolution and on the system at large // via the settings handlers) var sequence = fluid.promise.sequence(tasks); sequence.then(function () { callback(true); - }, function (error) { + }, function (error) { // TODO: Unclear that this branch has tests request.events.onError.fire(error); }); }; diff --git a/gpii/node_modules/lifecycleManager/src/LifecycleManagerSession.js b/gpii/node_modules/lifecycleManager/src/LifecycleManagerSession.js new file mode 100644 index 000000000..84d546029 --- /dev/null +++ b/gpii/node_modules/lifecycleManager/src/LifecycleManagerSession.js @@ -0,0 +1,103 @@ +/*! + * Lifecycle Manager Session + * + * Copyright 2016 Raising the Floor - International + * + * Licensed under the New BSD license. You may not use this file except in + * compliance with this License. + * + * 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"); +var gpii = fluid.registerNamespace("gpii"); + +(function () { + + /** Mediate between the dynamic collection of session components and the lifecycleManager by maintaining an index + * named "sessionIndex" mapping userTokens onto session member names + */ + + fluid.defaults("gpii.lifecycleManager.sessionIndexer", { + gradeNames: "gpii.indexedDynamicComponent", + components: { + dynamicIndexTarget: "{gpii.lifecycleManager}" + }, + dynamicIndexTargetPath: "sessionIndex", + dynamicIndexKeyPath: "options.userToken" + }); + + /** Mediate between updates to the session component's original snapshot and the external `onSessionSnapshotUpdate` fired by the + * LifecycleManager (principally to the journaller) + */ + fluid.defaults("gpii.lifecycleManager.sessionSnapshotUpdater", { + gradeNames: "fluid.component", + modelListeners: { + "originalSettings": { + excludeSource: "init", + func: "{gpii.lifecycleManager}.events.onSessionSnapshotUpdate.fire", + args: ["{gpii.lifecycleManager}", "{that}", "{change}.value"] + } + } + }); + + /** One dynamic component of this type is constructed for each active (local) session on the LifecycleManager. + * In practice we only support one of these at a time - in future we may support sessions bound to multiple users + * simultaneously or multiple simultaneous sessions. + */ + + fluid.defaults("gpii.lifecycleManager.session", { + gradeNames: ["fluid.modelComponent", "gpii.lifecycleManager.sessionIndexer"], + model: { + actionResults: {}, + originalSettings: {}, + appliedSolutions: {} + }, + members: { + createTime: "@expand:Date.now()", + // TODO: Refactor this into some more satisfactory future pattern - some kind of lightweight version of "transforming promise chain" + localFetcher: "@expand:gpii.lifecycleManager.computeLocalFetcher({that}, {that}.actionResultsFetcher)" + }, + invokers: { + actionResultsFetcher: "gpii.lifecycleManager.actionResultsFetcher({that}, {arguments}.0)", + localResolver: "gpii.lifecycleManager.localResolver({gpii.lifecycleManager}, {that}, {arguments}.0)" + } + }); + + /** A standard session corresponding to a standard user logon + */ + fluid.defaults("gpii.lifecycleManager.userSession", { + gradeNames: ["gpii.lifecycleManager.session", "gpii.lifecycleManager.sessionSnapshotUpdater"] + }); + + /** A special session type corresponding to a "restore" action by the journaller + */ + fluid.defaults("gpii.lifecycleManager.restoreSession", { + gradeNames: "gpii.lifecycleManager.session" + }); + + gpii.lifecycleManager.actionResultsFetcher = function (session, parsed) { + var result = session.model.actionResults[parsed.context]; + return fluid.get(result, parsed.path); + }; + + gpii.lifecycleManager.localResolver = function (lifecycleManager, session, material) { + return lifecycleManager.variableResolver.resolve(material, session.localFetcher); + }; + + gpii.lifecycleManager.computeLocalFetcher = function (session, actionResultsFetcher) { + // let the user's token as well as any named action results accumulated + // to date be resolvable for any future action + // TODO: This is an opportunistic hack based on the existing 2-arg gpii.combineFetchers. Needs to be + // generalised + return gpii.combineFetchers( + gpii.resolversToFetcher({userToken: session.options.userToken}), + actionResultsFetcher); + }; + + + +})(); diff --git a/gpii/node_modules/lifecycleManager/src/Resolvers.js b/gpii/node_modules/lifecycleManager/src/Resolvers.js new file mode 100644 index 000000000..f2316185f --- /dev/null +++ b/gpii/node_modules/lifecycleManager/src/Resolvers.js @@ -0,0 +1,140 @@ +/*! + * Lifecycle Manager Resolvers + * + * Copyright 2012 Antranig Basman + * + * 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"); +var gpii = fluid.registerNamespace("gpii"); + +(function () { + // A standard interception point so that the process of resolving names onto + // settings handlers and actions can be mocked for integration tests + + fluid.defaults("gpii.lifecycleManager.nameResolver", { + gradeNames: ["fluid.component"], + invokers: { + resolveName: { + funcName: "fluid.identity" + } + } + }); + + /** The central machinery in the Lifecycle Manager which manages the process + * of resolving contextual expressions such as ${{environment}.WINDIR} onto + * strings by interpolation. + */ + + fluid.defaults("gpii.lifecycleManager.variableResolver", { + gradeNames: ["fluid.component"], + components: { + resolverConfig: { + type: "gpii.lifecycleManager.standardResolverConfig" + } + }, + members: { + resolvers: { + expander: { + func: "gpii.lifecycleManager.variableResolver.computeResolvers", + args: "{that}.resolverConfig.options.resolvers" + } + }, + fetcher: { + expander: { + func: "gpii.resolversToFetcher", + args: "{that}.resolvers" + } + } + }, + invokers: { + /** Resolves interpolated variables within some options material (argument 0). + * This is resolved first against the `builtin fetcher` which is computed by pooling + * environmental fetchers from the platform-specific repositories, together with + * a fetcher resolving onto variables in the user's current session (argument 1) + */ + resolve: { + funcName: "gpii.lifecycleManager.variableResolver.resolve", + args: ["{arguments}.0", "{that}.fetcher", "{arguments}.1"] + } + } + }); + + gpii.lifecycleManager.variableResolver.computeResolvers = function (resolvers) { + return fluid.transform(resolvers, fluid.getGlobalValue); + }; + + gpii.lifecycleManager.variableResolver.resolve = function (material, fetcher, extraFetcher) { + // TODO: This relies on a horribly undocumented and unstable Infusion API + return fluid.expand(material, { + bareContextRefs: false, + // TODO: FLUID-4932 - the framework currently has no wildcard support in mergePolicy. + mergePolicy: { + 0: { + capabilitiesTransformations: { + "*": { + noexpand: true + } + } + } + }, + fetcher: gpii.combineFetchers(fetcher, extraFetcher) + }); + }; + + /** Converts a free hash of "resolvers" (keyed by resolver name) into a + * "fetcher" suitable for being operated by Infusion's expansion machinery resolving + * expressions such as ${{environment}.WINDIR} when they are seen within strings for interpolation. + */ + + gpii.resolversToFetcher = function (resolvers) { + return function (parsed) { + var resolver = resolvers[parsed.context]; + return !resolver ? undefined : ( + typeof(resolver) === "function" ? + resolver(parsed.path) : fluid.get(resolver, parsed.path)); + }; + }; + + /** A hacked and unsatisfactory method to compose two fetchers (the second of which is optional) + * into a single one + */ + gpii.combineFetchers = function (main, fallback) { + return fallback ? function (parsed) { + var fetched = main(parsed); + return fetched === undefined ? fallback(parsed) : fetched; + } : main; + }; + + /** Central standard repository for "resolvers" mapping some environmental material onto + * resolvable expressions. This is targetted, e.g., from the windows repository by a + * grade linkage registering the Windows Registry resolver. + * It contains an option named `resolvers` which maps resolver names (which will act as context names, + * in expressions such as ${{environment}.WINDIR}) onto global names, which are resolvable via + * `fluid.getGlobalValue` to unary functions resolving the path expressions - these unary + * functions mapping Strings to Strings are named (variable) `resolvers`. + */ + + fluid.defaults("gpii.lifecycleManager.standardResolverConfig", { + gradeNames: "fluid.component", + resolvers: { + environment: "gpii.lifecycleManager.environmentResolver" + } + }); + + gpii.lifecycleManager.environmentResolver = function (name) { + return process.env[name]; + }; + +})(); diff --git a/gpii/node_modules/lifecycleManager/test/html/DynamicComponentIndexerTest.html b/gpii/node_modules/lifecycleManager/test/html/DynamicComponentIndexerTest.html new file mode 100644 index 000000000..30399ae9f --- /dev/null +++ b/gpii/node_modules/lifecycleManager/test/html/DynamicComponentIndexerTest.html @@ -0,0 +1,35 @@ + + + + + GPII Dynamic Component Indexer Tests + + + + + + + + + + + + + + + + + + + + + + +

    GPII Dynamic Component Indexer Tests

    +

    +
    +

    +
      + + + diff --git a/gpii/node_modules/lifecycleManager/test/html/LifecycleManagerTest.html b/gpii/node_modules/lifecycleManager/test/html/LifecycleManagerTest.html index 29c337daf..29af8a940 100644 --- a/gpii/node_modules/lifecycleManager/test/html/LifecycleManagerTest.html +++ b/gpii/node_modules/lifecycleManager/test/html/LifecycleManagerTest.html @@ -12,10 +12,10 @@ + - @@ -23,6 +23,9 @@ + + + diff --git a/gpii/node_modules/lifecycleManager/test/js/DynamicComponentIndexerTests.js b/gpii/node_modules/lifecycleManager/test/js/DynamicComponentIndexerTests.js new file mode 100644 index 000000000..08e384e12 --- /dev/null +++ b/gpii/node_modules/lifecycleManager/test/js/DynamicComponentIndexerTests.js @@ -0,0 +1,73 @@ +/*! +GPII Dynamic Component Indexer Tests + +Copyright 2012 OCAD University +Copyright 2012 Raising The Floor - International + +Licensed under the New BSD license. You may not use this file except in +compliance with this License. + +You may obtain a copy of the License at +https://github.com/GPII/universal/blob/master/LICENSE.txt +*/ + +/* eslint-env browser */ +/* eslint strict: ["error", "function"] */ + +/* global jqUnit, fluid */ + +(function () { + "use strict"; + + var gpii = fluid.registerNamespace("gpii"); + + fluid.defaults("gpii.tests.sessionIndexer", { + gradeNames: "gpii.indexedDynamicComponent", + components: { + dynamicIndexTarget: "{gpii.tests.dynamicIndexerHolder}" + }, + dynamicIndexTargetPath: "sessionIndex", + dynamicIndexKeyPath: "options.userId" + }); + + fluid.defaults("gpii.tests.dynamicIndexerHolder", { + gradeNames: "fluid.component", + members: { + sessionIndex: {} + }, + dynamicComponents: { + session: { + type: "gpii.tests.sessionIndexer", + options: { + userId: "{arguments}.0" + }, + createOnEvent: "onSessionStart" + } + }, + events: { + onSessionStart: null + } + }); + + gpii.tests.sessionIndexer.assertIndexed = function (that, keys) { + var sessionIndex = that.sessionIndex; + jqUnit.assertDeepEq("Exactly those expected components indexed", keys, fluid.keys(sessionIndex)); + fluid.each(keys, function (key) { + jqUnit.assertEquals("Dynamic component with key " + key + " indexed", key, that[sessionIndex[key]].options.userId); + }); + }; + + jqUnit.test("Dynamic Component Indexer", function () { + var holder = gpii.tests.dynamicIndexerHolder(); + gpii.tests.sessionIndexer.assertIndexed(holder, []); + holder.events.onSessionStart.fire("user1"); + gpii.tests.sessionIndexer.assertIndexed(holder, ["user1"]); + holder.events.onSessionStart.fire("user2"); + gpii.tests.sessionIndexer.assertIndexed(holder, ["user1", "user2"]); + holder[holder.sessionIndex.user1].destroy(); + gpii.tests.sessionIndexer.assertIndexed(holder, ["user2"]); + holder[holder.sessionIndex.user2].destroy(); + gpii.tests.sessionIndexer.assertIndexed(holder, []); + }); + +})(); diff --git a/gpii/node_modules/lifecycleManager/test/js/LifecycleManagerTests.js b/gpii/node_modules/lifecycleManager/test/js/LifecycleManagerTests.js index 9430608a8..406df736d 100644 --- a/gpii/node_modules/lifecycleManager/test/js/LifecycleManagerTests.js +++ b/gpii/node_modules/lifecycleManager/test/js/LifecycleManagerTests.js @@ -21,6 +21,7 @@ https://github.com/GPII/universal/blob/master/LICENSE.txt (function () { "use strict"; + fluid.setLogging(true); var gpii = fluid.registerNamespace("gpii"); @@ -193,7 +194,7 @@ https://github.com/GPII/universal/blob/master/LICENSE.txt }, response: [{ "settings": { - "weirdSetting": undefined, + "weirdSetting": undefined }, "options": {} }] @@ -383,7 +384,7 @@ https://github.com/GPII/universal/blob/master/LICENSE.txt }] }; - gpii.tests.lifecycleManager.updateTestDefs = [{ + gpii.tests.lifecycleManager.updateTestDefs = fluid.freezeRecursive([{ name: "Updating with the same prefs and values as already applied", activeSessions: gpii.tests.lifecycleManager.sampleActiveSession, startPayload: gpii.tests.lifecycleManager.startPayload, @@ -513,6 +514,7 @@ https://github.com/GPII/universal/blob/master/LICENSE.txt gpii.tests.lifecycleManager.noUpdateLifecycle, { "update": [ "settings.myconf" ] })) }, { + // TODO: What is the difference between this test and the next one? Also, eliminate the massive duplication name: "Updating with normal reference to settingsHandler block, and 'undefined' value stored in snapshot", activeSessions: gpii.tests.lifecycleManager.sampleActiveSession, startPayload: fluid.extend(true, {}, gpii.tests.lifecycleManager.userOptions, { @@ -553,7 +555,7 @@ https://github.com/GPII/universal/blob/master/LICENSE.txt "options": {}, "settings": { "cross-hairs-clip": false, - "undefSetting": undefined + "undefSetting": gpii.undefinedEncoding }, "type": "gpii.tests.lifecycleManager.mockSettingsHandler" } @@ -634,7 +636,7 @@ https://github.com/GPII/universal/blob/master/LICENSE.txt ] } } - }]; + }]); gpii.tests.lifecycleManager.buildUpdateTests = function () { fluid.each(gpii.tests.lifecycleManager.updateTestDefs, function (test) { @@ -655,12 +657,14 @@ https://github.com/GPII/universal/blob/master/LICENSE.txt gpii.tests.lifecycleManager.assertExpectedSettingsHandler(" - on start", expectedFirstAppliedSettings); lifecycleManager.update(updatePayload, function () { - var appliedSolutions = lifecycleManager.activeSessions[test.startPayload.userToken].appliedSolutions; - var originalSettings = lifecycleManager.activeSessions[test.startPayload.userToken].originalSettings; - jqUnit.assertDeepEq("Checking appliedSolutions after Update", test.expectedAppliedSolutions, appliedSolutions); - jqUnit.assertDeepEq("Checking originalSettings after Update", test.expectedOriginalSettings, originalSettings); + var session = lifecycleManager.getSession(test.startPayload.userToken); + var appliedSolutions = session.model.appliedSolutions; + var originalSettings = session.model.originalSettings; + jqUnit.assertDeepEq("Checking appliedSolutions after update", test.expectedAppliedSolutions, appliedSolutions); + var expected = gpii.lifecycleManager.transformAllSolutionSettings(test.expectedOriginalSettings, gpii.settingsHandlers.settingsToChanges); + jqUnit.assertDeepEq("Checking originalSettings after update", expected, originalSettings); lifecycleManager.stop(gpii.tests.lifecycleManager.userOptions, function () { - jqUnit.assertUndefined("no active sessions running after stop", lifecycleManager.activeSessions[test.startPayload.userToken]); + jqUnit.assertDeepEq("Session not running after stop", lifecycleManager.getSession(test.startPayload.userToken)); jqUnit.start(); }); }); @@ -669,6 +673,14 @@ https://github.com/GPII/universal/blob/master/LICENSE.txt }); }; + gpii.tests.lifecycleManager.settingsToChanges = function (expected) { + return gpii.settingsHandlers.transformOneSolutionSettings(expected, gpii.settingsHandlers.settingsToChanges); + }; + + gpii.tests.lifecycleManager.assertNoActiveSessions = function (lifecycleManager, message) { + jqUnit.assertDeepEq(message + "- session state should be empty", [], lifecycleManager.getActiveSessionTokens()); + }; + gpii.tests.lifecycleManager.runTests = function () { jqUnit.module("Lifecycle Manager", function () { gpii.tests.staticRepository = {}; @@ -707,16 +719,18 @@ https://github.com/GPII/universal/blob/master/LICENSE.txt jqUnit.test("gpii.lifecycleManager.responseToSnapshot()", function () { jqUnit.expect(1); var response = gpii.lifecycleManager.responseToSnapshot("org.gnome.desktop.a11y.magnifier", gpii.tests.lifecycleManager.responseToSnapshotRequest); - jqUnit.assertDeepEq("responseToSnapshot returning the correct payload", gpii.tests.lifecycleManager.responseToSnapshotExpectedResponse, response); + var expected = gpii.tests.lifecycleManager.settingsToChanges(gpii.tests.lifecycleManager.responseToSnapshotExpectedResponse); + jqUnit.assertDeepEq("responseToSnapshot returning the correct payload", expected, response); }); - jqUnit.asyncTest("gpii.lifecycleManager.invokeSettingsHandlers()", function () { + jqUnit.asyncTest("gpii.lifecycleManager.invokeSettingsHandler()", function () { jqUnit.expect(2); var lifecycleManager = gpii.lifecycleManager(); gpii.tests.lifecycleManager.initBackingMock(); - var snapshotPromise = lifecycleManager.invokeSettingsHandlers("org.gnome.desktop.a11y.magnifier", gpii.tests.lifecycleManager.invokeSettingsHandlersRequest); + var snapshotPromise = lifecycleManager.invokeSettingsHandler("org.gnome.desktop.a11y.magnifier", gpii.tests.lifecycleManager.invokeSettingsHandlersRequest); + var expected = gpii.tests.lifecycleManager.settingsToChanges(gpii.tests.lifecycleManager.invokeSettingsHandlersExpectedSnapshot); snapshotPromise.then(function (snapshot) { - jqUnit.assertDeepEq("invokeSettingsHandlers produced snapshot", gpii.tests.lifecycleManager.invokeSettingsHandlersExpectedSnapshot, snapshot); + jqUnit.assertDeepEq("invokeSettingsHandlers produced snapshot", expected, snapshot); gpii.tests.lifecycleManager.assertExpectedSettingsHandler(" - standalone", gpii.tests.lifecycleManager.settingsHandlerExpectedInputNewSettings); jqUnit.start(); }); @@ -738,7 +752,7 @@ https://github.com/GPII/universal/blob/master/LICENSE.txt gpii.tests.lifecycleManager.assertExpectedSettingsHandler(" - on stop", gpii.tests.lifecycleManager.settingsHandlerExpectedInputRestoreSettings); jqUnit.assertEquals("Expected pid has been sent to kill handler", 8839, gpii.tests.lifecycleManager.staticRepository.killHandler.pid); - jqUnit.assertDeepEq("stop: Stop message to lifecycle manager, empty session state", {}, lifecycleManager.activeSessions); + gpii.tests.lifecycleManager.assertNoActiveSessions(lifecycleManager, "stop: Stop message to lifecycle manager"); jqUnit.start(); }); }); @@ -801,7 +815,7 @@ https://github.com/GPII/universal/blob/master/LICENSE.txt jqUnit.assertEquals("Expected pid has been sent to kill handler", 8839, gpii.tests.lifecycleManager.staticRepository.killHandler.pid); lifecycleManager.stop(gpii.tests.lifecycleManager.userOptions, function () { - jqUnit.assertUndefined("no active sessions running after stop", lifecycleManager.activeSessions[startPayload.userToken]); + gpii.tests.lifecycleManager.assertNoActiveSessions(lifecycleManager, "no active sessions running after stop"); jqUnit.start(); }); }); @@ -835,7 +849,7 @@ https://github.com/GPII/universal/blob/master/LICENSE.txt gpii.tests.lifecycleManager.assertExpectedExec(); lifecycleManager.stop(gpii.tests.lifecycleManager.userOptions, function () { - jqUnit.assertUndefined("no active sessions running after stop", lifecycleManager.activeSessions[startPayload.userToken]); + gpii.tests.lifecycleManager.assertNoActiveSessions(lifecycleManager, "no active sessions running after stop"); jqUnit.start(); }); }); diff --git a/gpii/node_modules/matchMakerFramework/package.json b/gpii/node_modules/matchMakerFramework/package.json index fb666fc23..49747067d 100644 --- a/gpii/node_modules/matchMakerFramework/package.json +++ b/gpii/node_modules/matchMakerFramework/package.json @@ -1,19 +1,13 @@ { - "name": "matchMakerFramework", - "description": "Match Maker framework doing pre- and post processing and calling the appropriate MM.", - "version": "0.1", - "author": "GPII", - "bugs": "http://issues.gpii.net/browse/GPII", - "homepage": "http://gpii.net/", - "dependencies": {}, - "licenses": [ - { - "type": "BSD-3-Clause", - "url": "http://www.opensource.org/licenses/BSD-3-Clause" - } - ], - "keywords": ["infusion", "framework", "application", "fluid", "IoC", "Inversion of Control", "MVC", "evented"], - "repository": "git://github.com/GPII/universal.git", - "main": "./index.js", - "engines": { "node" : ">=0.8" } + "name": "matchMakerFramework", + "description": "Match Maker framework doing pre- and post processing and calling the appropriate MM.", + "version": "0.3.0", + "author": "GPII", + "bugs": "http://issues.gpii.net/browse/GPII", + "homepage": "http://gpii.net/", + "dependencies": {}, + "license" : "BSD-3-Clause", + "repository": "git://github.com/GPII/universal.git", + "main": "./index.js", + "engines": { "node" : ">=4.2.1" } } \ No newline at end of file diff --git a/gpii/node_modules/ontologyHandler/package.json b/gpii/node_modules/ontologyHandler/package.json index fdbfd6b50..05c7cdb03 100644 --- a/gpii/node_modules/ontologyHandler/package.json +++ b/gpii/node_modules/ontologyHandler/package.json @@ -1,19 +1,13 @@ { - "name": "ontologyHandler", - "description": "Ontology server provides an API for accessing ontology related information.", - "version": "0.1", - "author": "GPII", - "bugs": "http://issues.gpii.net/browse/GPII", - "homepage": "http://gpii.net/", - "dependencies": {}, - "licenses": [ - { - "type": "BSD-3-Clause", - "url": "http://www.opensource.org/licenses/BSD-3-Clause" - } - ], - "keywords": ["infusion", "framework", "application", "fluid", "IoC", "Inversion of Control", "MVC", "evented"], - "repository": "git://github.com/GPII/universal.git", - "main": "./index.js", - "engines": { "node" : ">=0.8" } + "name": "ontologyHandler", + "description": "A component for transforming preferences between ontologies", + "version": "0.3.0", + "author": "GPII", + "bugs": "http://issues.gpii.net/browse/GPII", + "homepage": "http://gpii.net/", + "dependencies": {}, + "license" : "BSD-3-Clause", + "repository": "git://github.com/GPII/universal.git", + "main": "./index.js", + "engines": { "node" : ">=4.2.1" } } \ No newline at end of file diff --git a/gpii/node_modules/preferencesServer/package.json b/gpii/node_modules/preferencesServer/package.json index a766f2d21..5423f57fd 100644 --- a/gpii/node_modules/preferencesServer/package.json +++ b/gpii/node_modules/preferencesServer/package.json @@ -1,19 +1,13 @@ { - "name": "preferencesServer", - "description": "Preferences server provides an API for accessing user preferences and settings.", - "version": "0.1", - "author": "GPII", - "bugs": "http://issues.gpii.net/browse/GPII", - "homepage": "http://gpii.net/", - "dependencies": {}, - "licenses": [ - { - "type": "BSD-3-Clause", - "url": "http://www.opensource.org/licenses/BSD-3-Clause" - } - ], - "keywords": ["infusion", "framework", "application", "fluid", "IoC", "Inversion of Control", "MVC", "evented"], - "repository": "git://github.com/GPII/universal.git", - "main": "./index.js", - "engines": { "node" : ">=0.8" } + "name": "preferencesServer", + "description": "Provides an API for accessing user preferences and settings", + "version": "0.3.0", + "author": "GPII", + "bugs": "http://issues.gpii.net/browse/GPII", + "homepage": "http://gpii.net/", + "dependencies": {}, + "license" : "BSD-3-Clause", + "repository": "git://github.com/GPII/universal.git", + "main": "./index.js", + "engines": { "node" : ">=4.2.1" } } \ No newline at end of file diff --git a/gpii/node_modules/rawPreferencesServer/package.json b/gpii/node_modules/rawPreferencesServer/package.json index abb901bdd..6f6b32df4 100644 --- a/gpii/node_modules/rawPreferencesServer/package.json +++ b/gpii/node_modules/rawPreferencesServer/package.json @@ -1,19 +1,13 @@ { - "name": "rawPreferencesServer", - "description": "The Raw Preferences server provides an API for accessing the raw user preferences and settings.", - "version": "0.1", - "author": "GPII", - "bugs": "http://issues.fluidproject.org/browse/FLUID", - "homepage": "http://gpii.net/", - "dependencies": {}, - "licenses": [ - { - "type": "BSD-3-Clause", - "url": "http://www.opensource.org/licenses/BSD-3-Clause" - } - ], - "keywords": ["infusion", "framework", "application", "fluid", "IoC", "Inversion of Control", "MVC", "evented"], - "repository": "git://github.com/GPII/universal.git", - "main": "./index.js", - "engines": { "node" : ">=0.8" } + "name": "rawPreferencesServer", + "description": "The Raw Preferences server provides an API for accessing the raw user preferences and settings.", + "version": "0.3.0", + "author": "GPII", + "bugs": "http://issues.fluidproject.org/browse/FLUID", + "homepage": "http://gpii.net/", + "dependencies": {}, + "license" : "BSD-3-Clause", + "repository": "git://github.com/GPII/universal.git", + "main": "./index.js", + "engines": { "node" : ">=4.2.1" } } \ No newline at end of file diff --git a/gpii/node_modules/settingsHandlers/package.json b/gpii/node_modules/settingsHandlers/package.json index a0d49cebb..e46a4c25f 100644 --- a/gpii/node_modules/settingsHandlers/package.json +++ b/gpii/node_modules/settingsHandlers/package.json @@ -1,19 +1,14 @@ { - "name": "settingsHandlers", - "description": "Cross Platform settingsHandlers to use in the GPII personalization framework. SettingsHandlers are responsible for writing the settings of an application - where/how to write them depends on the settingsHandler in question (it could be XML, JSON, etc).", - "version": "0.1", - "author": "GPII", - "bugs": "http://issues.gpii.net/browse/GPII", - "homepage": "http://gpii.net/", - "dependencies": {}, - "licenses": [ - { - "type": "BSD-3-Clause", - "url": "http://www.opensource.org/licenses/BSD-3-Clause" - } - ], - "keywords": ["gpii", "accessibility", "settings", "fluid", "IoC", "Inversion of Control", "configuration", "evented"], - "repository": "git://github.com/GPII/universal.git", - "main": "./index.js", - "engines": { "node" : ">=0.8" } + "name": "settingsHandlers", + "description": "Cross Platform settingsHandlers to use in the GPII personalization framework. SettingsHandlers are responsible for writing the settings of an application - where/how to write them depends on the settingsHandler in question (it could be XML, JSON, etc).", + "version": "0.3.0", + "author": "GPII", + "bugs": "http://issues.gpii.net/browse/GPII", + "homepage": "http://gpii.net/", + "dependencies": {}, + "license" : "BSD-3-Clause", + "keywords": ["gpii", "accessibility", "settings", "fluid", "IoC", "Inversion of Control", "configuration", "evented"], + "repository": "git://github.com/GPII/universal.git", + "main": "./index.js", + "engines": { "node" : ">=4.2.1" } } diff --git a/gpii/node_modules/settingsHandlers/src/settingsHandlerUtilities.js b/gpii/node_modules/settingsHandlers/src/settingsHandlerUtilities.js index 9d065d959..0e8f307e4 100644 --- a/gpii/node_modules/settingsHandlers/src/settingsHandlerUtilities.js +++ b/gpii/node_modules/settingsHandlers/src/settingsHandlerUtilities.js @@ -55,43 +55,65 @@ gpii.toPromise = function (value) { fluid.registerNamespace("gpii.settingsHandlers"); -/* A general utility function which helps in transforming settings handler payloads - these algorithms generally +// TODO: These utilities all clearly form part of some wider idiom of "payload handling" without some underpinnings +// for which they are rather hard to follow. For example, they could benefit from some form of "JSON type system" +// (whether provided via JSON schema or otherwise) in order to provide landmarks within the payloads, as well +// as actually validating arguments to these functions and payloads in general. +// cf. some vaguely related art in F# "Type Providers": http://fsharp.github.io/FSharp.Data/library/JsonProvider.html + +/** A general utility function which helps in transforming settings handler payloads - these algorithms generally * must iterate over the two levels of containment (per-solution, per-settings) and then apply some transform * to the nested values - * @param payload {Object} A settings handler payload (either GET/SET response or return) - * @param handler {Function} A function whose signature is (entry, [path], solution) where - - * - entry is the nested payload (i.e. the level containing "settings"/"options" + * @param payload {Object} A settings handler payload (either GET/SET response or return). The top-level keys will be solution ids + * @param handler {Function} A function whose signature is (oneSetting, [path], oneSolution) where - + * - oneSetting is the nested payload (i.e. the level containing "settings"/"options") * - path is an array holding the path to this payload i.e. [solutionId, index] - * - solution is the payload at the outer level of containment - i.e. payload[solutionId] + * - oneSolution is the payload at the outer level of containment - i.e. payload[solutionId] * @return The transformed payload-structured value */ gpii.settingsHandlers.transformPayload = function (payload, handler) { return fluid.transform(payload, function (oneSolution, solutionPath) { - return fluid.transform(oneSolution, function (element, elementPath) { - var path = [solutionPath, elementPath]; - return handler(element, path, oneSolution); + return fluid.transform(oneSolution, function (oneSetting, settingPath) { + var path = [solutionPath, settingPath]; + return handler(oneSetting, path, oneSolution); }); }); }; +/** Transform the settings for one solution's worth of settings by a supplied transform. + * @param oneSolution {Object} One solution's worth of settings, with a member named `settings` containing a free hash of the settings indexed by settingsHandlerBlock name + * @param handler {Function} A transformer whose signature is the same as the 2nd argument to gpii.settingsHandlers.transformPayload + * @param path {Array of String} (optional) An array of path segments holding the path of oneSolution within an overall payload - this will be used to form the 2nd argument of `handler` +*/ + +gpii.settingsHandlers.transformOneSolutionSettings = function (oneSolution, handler, path) { + path = path || []; + var togo = fluid.censorKeys(oneSolution, "settings"); + togo.settings = fluid.transform(oneSolution.settings, function (oneSetting, settingKey) { + var innerPath = path.concat(["settings", settingKey]); + return handler(oneSetting, innerPath, oneSolution); + }); + return togo; +}; + /** As for gpii.settingsHandlers.transformPayload, only the transform runs one level deeper and iterates over any * "settings" block held within the payload. */ gpii.settingsHandlers.transformPayloadSettings = function (payload, handler) { - return gpii.settingsHandlers.transformPayload(payload, function (element, path, oneSolution) { - var settings = {}; // note that we don't use fluid.transform here to avoid destroying undefined values - fluid.each(element.settings, function (oneSetting, settingKey) { - var innerPath = path.concat(["settings", settingKey]); - settings[settingKey] = handler(oneSetting, innerPath, element, oneSolution); - }); - var togo = {settings: settings}; - return togo; + return gpii.settingsHandlers.transformPayload(payload, function (oneSolution, path) { + return gpii.settingsHandlers.transformOneSolutionSettings(oneSolution, handler, path); }); }; +/** Extract just the settings (eliminating "options") from the nested settingsHandler blocks for a full payload (top-level keys are solution ids) + * - this is used from gpii.settingsHandlers.comparePayloads and gpii.test.checkConfiguration + */ + gpii.settingsHandlers.extractSettingsBlocks = function (payload) { - return gpii.settingsHandlers.transformPayloadSettings(payload, fluid.identity); + return gpii.settingsHandlers.transformPayload(payload, function (oneSolution) { + return fluid.filterKeys(oneSolution, "settings"); + }); }; // A utility which will accept a payload and convert all leaf values to numbers which may validly be. This @@ -143,6 +165,49 @@ gpii.settingsHandlers.invokeSettingsHandler = function (handler, payload) { return togo; }; +/** Utilities for converting to and from concrete settings to ChangeApplier requests + * to enact those settings. On the "get" or "set" settingsHandler returns, we receive + * concrete settings, which need to be modelised so that they can safely be stored and persisted. + * In future, the "set" action will accept these requests as they are, but for now we need to + * convert them back using "changesToSettings" to adapt to the historical API. + */ + +// Firstly two utilities which transform an individual settings value from one format to the other + +gpii.settingsHandlers.settingsToChanges = function (element) { + return element === undefined ? { + type: "DELETE" + } : { + type: "ADD", + value: element + }; +}; + +gpii.settingsHandlers.changesToSettings = function (element) { + return element.type === "DELETE" ? undefined : element.value; +}; + +// Secondly two utilities which do the same for an entire solution's worth of payloads as used in the LifecycleManager + +gpii.settingsHandlers.settingsPayloadToChanges = function (response) { + return gpii.settingsHandlers.transformPayloadSettings(response, gpii.settingsHandlers.settingsToChanges); +}; + +gpii.settingsHandlers.changesPayloadToSettings = function (response) { + return gpii.settingsHandlers.transformPayloadSettings(response, gpii.settingsHandlers.changesToSettings); +}; + + +/** Invoked by, e.g., the LifecycleManager to convert the SET response from a settingsHandler + * to a form where it can be immediately relayed back to another SET handler to + * restore the original settings values + */ + +gpii.settingsHandlers.setResponseToSnapshot = function (response) { + return gpii.settingsHandlers.transformPayloadSettings(response, function (element) { + return element.oldValue; + }); +}; /************************************************************************ @@ -162,12 +227,6 @@ gpii.settingsHandlers.comparePayloads = function (payload1, payload2) { return equal; }; -gpii.settingsHandlers.setResponseToSnapshot = function (response) { - return gpii.settingsHandlers.transformPayloadSettings(response, function (element) { - return element.oldValue; - }); -}; - gpii.settingsHandlers.checkRereadSettings = function (that) { var writeAttempt = that.retryOptions.rewriteEvery && (that.retries % that.retryOptions.rewriteEvery === 0); if (writeAttempt) { @@ -186,7 +245,10 @@ gpii.settingsHandlers.checkRereadSettings = function (that) { if (that.retries === that.retryOptions.numRetries) { fluid.log(fluid.logLevel.WARN, "Maximum retry count exceeded in settings handler " + that.resolvedName + " at retry " + that.retries + ": rejecting settings action"); - that.togo.reject("Failure to read settings for handler " + that.resolvedName + " as written after " + that.retries + " retries"); + that.togo.reject({ + isError: true, + message: "Failure to read settings for handler " + that.resolvedName + " as written after " + that.retries + " retries" + }); } else { fluid.log(fluid.logLevel.WARN, "Settings have not settled to required values - retrying read at attempt " + that.retries + " of " + that.retryOptions.numRetries + " in " + that.retryOptions.retryInterval + "ms"); @@ -217,7 +279,7 @@ gpii.settingsHandlers.checkRereadSettings = function (that) { * that retrying is enabled, a rejection occurs if the GET payloads never match after the end of the nominated retry period */ gpii.settingsHandlers.dispatchSettingsHandler = function (resolvedName, payload, retryOptions) { - // TODO "gpii.lifecycleManager.specToSettingsHandler" is the one responsible for this akward + // TODO "gpii.lifecycleManager.specToSettingsHandler" is the one responsible for this awkward // layout of the settings handler payload - all of this infrastructure will have to be updated // and cleaned up at some point var solutionIds = fluid.keys(payload); @@ -308,6 +370,7 @@ gpii.settingsHandlers.setSettings = function (solutionEntry, currentSettings) { }; }); if (currentSettings !== holder.model) { + // TODO: Re-understand again exactly why this is, and eliminate it fluid.clear(currentSettings); // We have a silly model based on object reference identity fluid.extend(currentSettings, holder.model); } @@ -395,9 +458,9 @@ gpii.settingsHandlers.makeFileSet = function (parser) { }; /** - * Given two settingshandler blocks, copy the settings (even those with a value of undefined) from + * Given two settingsHandler blocks, copy the settings (even those with a value of undefined) from * the source to the target - overwriting the target settings. Note that the keys/values immediately - * within the source settingshandler block (ie. the named settinghandler sub blocks) must be present + * within the source settingshandler block (i.e. the named settinghandler sub blocks) must be present * in the target block structure * Also note that the target object *will be modified* */ diff --git a/gpii/node_modules/settingsHandlers/test/settingsHandlerUtilitiesTests.js b/gpii/node_modules/settingsHandlers/test/settingsHandlerUtilitiesTests.js index 5c671e2a9..e71c13d43 100644 --- a/gpii/node_modules/settingsHandlers/test/settingsHandlerUtilitiesTests.js +++ b/gpii/node_modules/settingsHandlers/test/settingsHandlerUtilitiesTests.js @@ -35,10 +35,10 @@ gpii.tests.settingsHandlers.readFile = function () { jqUnit.assertDeepEq("Expecting to have read the file properly", { Hello: "World" }, JSON.parse(result)); // check reading non-existing file - var result = gpii.settingsHandlers.readFile({ + var result2 = gpii.settingsHandlers.readFile({ filename: __dirname + "/data/bogus.json" }); - jqUnit.assertEquals("Expecting undefined on reading non-existing file", undefined, result); + jqUnit.assertEquals("Expecting undefined on reading non-existing file", undefined, result2); }; gpii.tests.settingsHandlers.writeFile = function () { diff --git a/gpii/node_modules/testing/package.json b/gpii/node_modules/testing/package.json index 332f2cec9..561d032fc 100644 --- a/gpii/node_modules/testing/package.json +++ b/gpii/node_modules/testing/package.json @@ -1,19 +1,13 @@ { - "name": "gpii-testing", - "description": "This module contains common definitions which assist in writing acceptance and integration tests for the GPII framework", - "version": "0.1", - "author": "GPII", - "bugs": "http://issues.gpii.net/browse/GPII", - "homepage": "http://gpii.net/", - "dependencies": {}, - "licenses": [ - { - "type": "BSD-3-Clause", - "url": "http://www.opensource.org/licenses/BSD-3-Clause" - } - ], - "keywords": ["gpii", "accessibility", "settings", "fluid", "IoC", "Inversion of Control", "configuration", "evented"], - "repository": "git://github.com/GPII/universal.git", - "main": "./index.js", - "engines": { "node" : ">=0.8" } + "name": "gpii-testing", + "description": "This module contains common definitions which assist in writing acceptance and integration tests for the GPII framework", + "version": "0.3.0", + "author": "GPII", + "bugs": "http://issues.gpii.net/browse/GPII", + "homepage": "http://gpii.net/", + "dependencies": {}, + "license" : "BSD-3-Clause", + "repository": "git://github.com/GPII/universal.git", + "main": "./index.js", + "engines": { "node" : ">=4.2.1" } } diff --git a/gpii/node_modules/testing/src/Integration.js b/gpii/node_modules/testing/src/Integration.js index 447398f3c..19011c326 100644 --- a/gpii/node_modules/testing/src/Integration.js +++ b/gpii/node_modules/testing/src/Integration.js @@ -41,6 +41,7 @@ gpii.test.integration.exec.exec = function (that, processSpec /*, expected */) { fluid.defaults("gpii.test.integration.testCaseHolder", { gradeNames: ["gpii.test.common.testCaseHolder"], + // TODO: Namespace these distributions so they can be overridden distributeOptions: [{ record: { funcName: null, @@ -252,7 +253,7 @@ gpii.test.integration.mockSettingsHandler.setImpl = function (that, payload) { var applySettings = function () { gpii.test.integration.mockSettingsHandler.applySettings(store, payload.settings); }; - if (that.options.delaying) { + if (that.options.delaying && !payload.options.mockSync) { // behave, for example, like the real SPI settings handler - claim to apply settings immediately but don't actually do it till later gpii.invokeLater(applySettings, 5000); } else { @@ -301,20 +302,24 @@ fluid.defaults("gpii.test.integration.mockSettingsHandlerRegistry", { } }, listeners: { - onCreate: "gpii.test.integration.mockSettingsHandlerRegistry.populate" + "onCreate.populate": "gpii.test.integration.mockSettingsHandlerRegistry.populate" } }); gpii.test.integration.mockSettingsHandlers = {}; +gpii.test.integration.mockSettingsHandlerRegistry.populateOne = function (that, mock, key) { + that.settingsHandlers[key] = mock; + // Mount the mock handler at a global path + var setPath = that.options.rootPath + "." + key + ".set"; + fluid.setGlobalValue(setPath, mock.set); + fluid.setGlobalValue(that.options.rootPath + "." + key + ".get", mock.get); +}; + gpii.test.integration.mockSettingsHandlerRegistry.populate = function (that) { fluid.each(that.options.settingsHandlers, function (options, key) { var mock = gpii.test.integration.mockSettingsHandler(options); - that.settingsHandlers[key] = mock; - // Mount the mock handler at a global path - var setPath = that.options.rootPath + "." + key + ".set"; - fluid.setGlobalValue(setPath, mock.set); - fluid.setGlobalValue(that.options.rootPath + "." + key + ".get", mock.get); + gpii.test.integration.mockSettingsHandlerRegistry.populateOne(that, mock, key); }); }; diff --git a/gpii/node_modules/testing/src/Testing.js b/gpii/node_modules/testing/src/Testing.js index a129c0910..3fdff025a 100644 --- a/gpii/node_modules/testing/src/Testing.js +++ b/gpii/node_modules/testing/src/Testing.js @@ -19,7 +19,7 @@ https://github.com/GPII/universal/blob/master/LICENSE.txt var fluid = fluid || require("infusion"), jqUnit = jqUnit || fluid.require("node-jqunit", require, "jqUnit"), - path = typeof(require) === "undefined" ? null : require("path"), // courtesy to allow use in file-based tests + path = typeof(require) === "undefined" ? null : require("path"), // courtesy to allow use in browser-based tests kettle = fluid.registerNamespace("kettle"), gpii = fluid.registerNamespace("gpii"); @@ -45,7 +45,7 @@ gpii.test.settleStructure = function (structure) { } var settleRec = { // This flag is an awkward consequence of our choice to allow synchronous promise resolution - inSync: true, // Are we still in sychronous scanning - necessary to avoid double resolution on hitting unresolved === 0 on initial scan + 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(), @@ -95,20 +95,72 @@ gpii.test.settleStructurePush = function (settleRec, holder, promise, key) { // END ALGORITHM gpii.test.settleStructure -gpii.test.getSettings = function (payload, nameResolver) { +// 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 settingsHandlers {Object} A map of settings handler names to `settingsHandler` blocks as seen in the `settingsHandlers` + * option of a `gpii.test.common.testCaseHolder` + * @param nameResolver {NameResolver|Null} 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 method {String} Either `"get"` or `"set"` depending on whether the settingsHandler `get` or `set` action is to be invoked + */ +gpii.test.operateSettings = function (settingsHandlers, nameResolver, method) { var ret = {}; - fluid.each(payload, function (handlerBlock, handlerID) { + fluid.each(settingsHandlers, function (handlerBlock, handlerID) { var resolvedName = nameResolver ? nameResolver.resolveName(handlerID, "settingsHandler") : handlerID; - var response = fluid.invokeGlobalFunction(resolvedName + ".get", [handlerBlock]); + var response = fluid.invokeGlobalFunction(resolvedName + "." + method, [handlerBlock]); ret[handlerID] = response; }); var togo = gpii.test.settleStructure(ret); togo.then(null, function (err) { - fluid.fail("Error when getting settings ", err); + fluid.fail("Error when operating settings handler: ", err); }); return togo; }; +/** Snapshot the state of all settingsHandlers by stashing them in a member named `orig` on the supplied settingsStore + * @param settingsHandlers {Object} A map of settings handler names to `settingsHandler` blocks as seen in the `settingsHandlers` + * option of a `gpii.test.common.testCaseHolder` + * @param settingStore {Object} The `settingsStore` member of a `gpii.test.common.testCaseHolder`. This will have a snapshot of + * the state of the supplied settingsHandlers stored in a member named `orig` + * @param nameResolver {NameResolver|Null} A `nameResolver` mapping global names onto alternatives for use in a testing context + * @param onComplete {Function} 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); @@ -157,9 +209,37 @@ gpii.test.common.receiveVariableResolver = function (testCaseHolder, variableRes testCaseHolder.variableResolver = variableResolver; }; +/** The base testCaseHolder for the straightforward family of login/logout system test cases - for example + * the very simple DevelopmentTests.js, integration tests and acceptance tests. These all operate the + * stereotypical workflow of snapshotting the initial settings handler state, logging in a user, + * checking that expected settings are set, logging out the user, and then verifying that the initial + * state is restored. + * + * This takes its place in the standard IoC Testing Framework environment structure rooted at "kettle.test.serverEnvironment" + * defined in kettle/lib/test/KettleTestUtils.js as the member named `test`. This structure is constructed via + * kettle.test.bootstrapServer which is kicked off from the standard launcher gpii.test.runTests + */ + +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/%userToken/login", + termMap: { + userToken: "{testCaseHolder}.options.userToken" + } +}); + +fluid.defaults("gpii.test.common.logoutRequestComponent", { + gradeNames: "kettle.test.request.http", + path: "/user/%userToken/logout", + termMap: { + userToken: "{testCaseHolder}.options.userToken" + } +}); + fluid.defaults("gpii.test.common.testCaseHolder", { gradeNames: ["kettle.test.testCaseHolder"], members: { + // The "orig" member of this area will be written by gpii.test.snapshotSettings, and read back by gpii.test.checkRestoredConfiguration settingsStore: {} }, mergePolicy: { @@ -189,27 +269,34 @@ fluid.defaults("gpii.test.common.testCaseHolder", { // ginger resolution in the framework too much. // In fact, a proper solution would allow us to directly write: // variableResolver: "{that lifecycleManager variableResolver}" without the listener hack above - this requires FLUID-5556 - logoutRequest: { - type: "kettle.test.request.http", - options: { - path: "/user/%userToken/logout", - termMap: { - userToken: "{tests}.options.userToken" - } - } - }, loginRequest: { - type: "kettle.test.request.http", - options: { - path: "/user/%userToken/login", - termMap: { - userToken: "{tests}.options.userToken" - } - } + type: "gpii.test.common.loginRequestComponent" + }, + logoutRequest: { + type: "gpii.test.common.logoutRequestComponent" } } }); +/** A mixin grade 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.receiveLifecycleManager", + args: ["{testCaseHolder}", "{arguments}.0"] + }, + target: "{that lifecycleManager}.options.listeners.onCreate" + } +}); + +gpii.test.common.receiveLifecycleManager = function (testCaseHolder, lifecycleManager) { + fluid.globalInstantiator.recordKnownComponent(testCaseHolder, lifecycleManager, "lifecycleManager", false); +}; + + // Will return the part of a test sequence that tests for the process state based on the // 'expectedKey' parameter gpii.test.createProcessChecks = function (processList, expectedKey) { @@ -230,8 +317,13 @@ gpii.test.createProcessChecks = function (processList, expectedKey) { return sequence; }; -// Expand the test fixture using the variableResolver fished out of the real implementation, and place it at -// this public member for the assertions to use +/** Expand material in a `gpii.test.common.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 testCaseHolder {gpii.test.common.testCaseHolder} a testCaseHolder with some expandable material in its options + * @param members {String|Array of String} one or more paths in the options to be expanded + * @return *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) { @@ -296,6 +388,10 @@ gpii.test.push = function (array, elements) { }; +/** Build a test fixture for integration/acceptance tests operating the stereotypical workflow - + * snapshot, login, expectConfigured, logout and expectRestored + */ + gpii.test.buildSingleTestFixture = function (testDef, rootGrades) { var processes = testDef.processes || []; testDef.gradeNames = fluid.makeArray(testDef.gradeNames).concat(fluid.makeArray(rootGrades)); @@ -319,8 +415,28 @@ gpii.test.buildSingleTestFixture = function (testDef, rootGrades) { return testDef; }; +/** Build a fixture set suitable for sending to kettle.test.bootstrapServer that includes a non-flat + * array of sequences within the member `sequenceSegments` as well as a base record to be merged into each testDef + * @param fixtures {Array} An array of proto-testDefs, each members `name` and `expect` with the same semantic + * as a standard testDefa, and a member `sequenceSegments` whose elements may themselves be arrays of sequences + * @param baseTestDef {TestDef} A base test def record, holding members `configName`, `configPath` and other + * members appropriate for this options structure + */ +// TODO: Review that we shouldn't use gradeNames and unify with buildSingleTestFixture's "rootGrades" pattern +gpii.test.buildSegmentedFixtures = function (fixtures, baseTestDef) { + return fluid.transform(fixtures, function (fixture) { + var overlay = { + name: fixture.name, + expect: fixture.expect, + sequence: fluid.flatten(fixture.sequenceSegments) + }; + return fluid.extend(true, {}, baseTestDef, overlay); + }); +}; + // Convert a record as returned by a "portable test" into a full testDefs structure as accepted by gpii.test.buildTests +// TODO better docs and explanation gpii.test.recordToTestDefs = function (record) { var testDefs = fluid.copy(fluid.getGlobalValue(record.testDefs)); fluid.each(testDefs, function (testDef) { @@ -338,7 +454,7 @@ gpii.test.runTests = function (record, rootGrades) { return kettle.test.bootstrapServer(testDefs2); }; -// Run from the base of every platform-specific test fixture file as its per-file bootstrap. +// Run from the base of every platform-specific acceptance/integration test fixture file as its per-file bootstrap. // The long argument list is required in order to detect whether the file is being run as a top-level // node executable - if it is, we default to running it as an integration test - otherwise, we just // return its record. diff --git a/gpii/node_modules/transformer/package.json b/gpii/node_modules/transformer/package.json index 44d29c676..cb87cd324 100644 --- a/gpii/node_modules/transformer/package.json +++ b/gpii/node_modules/transformer/package.json @@ -1,19 +1,13 @@ { - "name": "transformer", - "description": "The Transformer module in the GPII personalization framework that is responsible for transforming generic user preferences into application-specific settings.", - "version": "0.1", - "author": "GPII", - "bugs": "http://issues.gpii.net/browse/GPII", - "homepage": "http://gpii.net/", - "dependencies": {}, - "licenses": [ - { - "type": "BSD-3-Clause", - "url": "http://www.opensource.org/licenses/BSD-3-Clause" - } - ], - "keywords": ["gpii", "accessibility", "settings", "fluid", "IoC", "Inversion of Control", "configuration", "evented"], - "repository": "git://github.com/GPII/universal.git", - "main": "./index.js", - "engines": { "node" : ">=0.8" } + "name": "transformer", + "description": "The Transformer module in the GPII personalization framework that is responsible for transforming generic user preferences into application-specific settings.", + "version": "0.3.0", + "author": "GPII", + "bugs": "http://issues.gpii.net/browse/GPII", + "homepage": "http://gpii.net/", + "dependencies": {}, + "license" : "BSD-3-Clause", + "repository": "git://github.com/GPII/universal.git", + "main": "./index.js", + "engines": { "node" : ">=4.2.1" } } \ No newline at end of file diff --git a/index.js b/index.js index 1e3229c5a..7e37e498d 100644 --- a/index.js +++ b/index.js @@ -36,8 +36,14 @@ require("./gpii/node_modules/matchMakerFramework"); require("./gpii/node_modules/flatMatchMaker"); require("./gpii/node_modules/canopyMatchMaker"); require("./gpii/node_modules/contextManager"); +require("./gpii/node_modules/journal"); gpii.loadTestingSupport = function () { + fluid.contextAware.makeChecks({ + "gpii.contexts.test": { + value: true + } + }); require("./gpii/node_modules/testing"); }; diff --git a/package.json b/package.json index a4c735ef1..f5e1a4dbf 100644 --- a/package.json +++ b/package.json @@ -9,23 +9,25 @@ "infusion": "2.0.0-dev.20160902T155354Z.b963420", "node-jqunit": "1.1.4", "ini": "git://github.com/GPII/ini.git#be8a04aa22f5ad9321ebcbba7740314a53bc8ab2", - "node-uuid": "1.4.0", + "node-uuid": "1.4.7", "semver": "1.1.4", "xml-mapping": "1.2.0", - "kettle": "1.0.0", + "kettle": "1.1.0", "express": "4.13.3", "body-parser": "1.13.3", "connect-ensure-login": "0.1.1", "express-handlebars": "3.0.0", - "express-session": "1.11.3", + "express-session": "1.14.0", "oauth2orize": "1.0.1", "passport": "0.2.1", "passport-local": "1.0.0", - "passport-oauth2-client-password": "0.1.2" + "passport-oauth2-client-password": "0.1.2", + "glob": "7.0.5", + "write-file-atomic": "1.1.4" }, "devDependencies": { "grunt": "1.0.1", - "fluid-grunt-eslint": "18.1.1", + "fluid-grunt-eslint": "18.1.2", "grunt-jsonlint": "1.0.4", "grunt-shell": "1.3.0" }, diff --git a/testData/deviceReporter/acceptanceTests/win7_builtIn.json b/testData/deviceReporter/acceptanceTests/win7_builtIn.json index 3aa438820..313c3860c 100644 --- a/testData/deviceReporter/acceptanceTests/win7_builtIn.json +++ b/testData/deviceReporter/acceptanceTests/win7_builtIn.json @@ -17,5 +17,18 @@ { "id": "com.microsoft.windows.cursors" + }, + + { + "id": "com.microsoft.windows.stickyKeys" + }, + + { + "id": "com.microsoft.windows.filterKeys" + }, + + { + "id": "com.microsoft.windows.mouseKeys" } + ] diff --git a/testData/deviceReporter/installedSolutions.json b/testData/deviceReporter/installedSolutions.json index efd67070b..7995469e8 100644 --- a/testData/deviceReporter/installedSolutions.json +++ b/testData/deviceReporter/installedSolutions.json @@ -43,6 +43,18 @@ "id": "com.microsoft.windows.highContrast" }, + { + "id": "com.microsoft.windows.stickyKeys" + }, + + { + "id": "com.microsoft.windows.filterKeys" + }, + + { + "id": "com.microsoft.windows.mouseKeys" + }, + { "id": "com.microsoft.windows.mouseTrailing" }, diff --git a/testData/preferences/acceptanceTests/README.txt b/testData/preferences/acceptanceTests/README.txt index e639e27ca..3a42764ac 100644 --- a/testData/preferences/acceptanceTests/README.txt +++ b/testData/preferences/acceptanceTests/README.txt @@ -2,4 +2,7 @@ This folder contains the NP sets that are used for acceptance testing. Each NP set is depended on by an (or several) acceptance test, so they should not be deleted or modified, unless it is ensured that this will not affect the acceptance test(s) in question. -Note that the acceptance tests are located not only in universal, but also in the Linux, Windows and Android repositories. +The acceptance test definitions are located in the %universal/test/platform subdirectories. When run from universal, they +function as integration tests (with mocks for settings handlers and other environmental effects), but when run from the +platform-specific repositories (Linux, Windows and Android) they function as acceptance tests, having real effects on the +surrounding OS. diff --git a/testData/preferences/acceptanceTests/os_common.json b/testData/preferences/acceptanceTests/os_common.json index 42577ee06..f2051074c 100644 --- a/testData/preferences/acceptanceTests/os_common.json +++ b/testData/preferences/acceptanceTests/os_common.json @@ -11,7 +11,13 @@ "http://registry.gpii.net/common/cursorSize": 0.9, "http://registry.gpii.net/common/fontSize": 9, "http://registry.gpii.net/common/mouseTrailing": 10, - "http://registry.gpii.net/common/highContrastEnabled": true + "http://registry.gpii.net/common/highContrastEnabled": true, + "http://registry.gpii.net/common/mouseEmulationEnabled": true, + "http://registry.gpii.net/common/cursorAcceleration": 1, + "http://registry.gpii.net/common/cursorSpeed": 1, + "http://registry.gpii.net/common/stickyKeys": true, + "http://registry.gpii.net/common/debounceEnable": true, + "http://registry.gpii.net/common/debounceInterval": 1 } } } diff --git a/testData/preferences/acceptanceTests/os_win7.json b/testData/preferences/acceptanceTests/os_win7.json index 95931ea4a..44b5863a8 100644 --- a/testData/preferences/acceptanceTests/os_win7.json +++ b/testData/preferences/acceptanceTests/os_win7.json @@ -13,6 +13,36 @@ "value": 10 } }, + "http://registry.gpii.net/applications/com.microsoft.windows.mouseKeys": { + "MouseKeysOn": { + "path": "pvParam.dwFlags.MKF_MOUSEKEYSON", + "value": true + }, + "MaxSpeed": { + "path": "pvParam.iMaxSpeed", + "value": 100 + }, + "Acceleration": { + "path": "pvParam.iTimeToMaxSpeed", + "value": 1000 + } + }, + "http://registry.gpii.net/applications/com.microsoft.windows.stickyKeys": { + "StickyKeysOn": { + "path": "pvParam.dwFlags.SKF_STICKYKEYSON", + "value": true + } + }, + "http://registry.gpii.net/applications/com.microsoft.windows.filterKeys": { + "FilterKeysEnable": { + "path": "pvParam.dwFlags.FKF_FILTERKEYSON", + "value": true + }, + "BounceKeysInterval": { + "path": "pvParam.iBounceMSec", + "value": 1000 + } + }, "http://registry.gpii.net/applications/com.microsoft.windows.highContrast": { "HighContrastOn": { "path": "pvParam.dwFlags.HCF_HIGHCONTRASTON", diff --git a/testData/solutions/win32.json b/testData/solutions/win32.json index de03ac36b..20f07cd3a 100644 --- a/testData/solutions/win32.json +++ b/testData/solutions/win32.json @@ -764,6 +764,285 @@ ] }, + "com.microsoft.windows.stickyKeys": { + "name": "Windows StickyKeys", + "contexts": { + "OS": [ + { + "id": "win32", + "version": ">=5.0" + } + ] + }, + "settingsHandlers": { + "configure": { + "type": "gpii.windows.spiSettingsHandler", + "options": { + "getAction": "SPI_GETSTICKYKEYS", + "setAction": "SPI_SETSTICKYKEYS", + "uiParam": "struct_size", + "pvParam": { + "type": "struct", + "name": "STICKYKEYS" + }, + "verifySettings": true + }, + "capabilities": [ + "applications.com\\.microsoft\\.windows\\.stickyKeys\\.id" + ], + "capabilitiesTransformations": { + "StickyKeysOn": { + "transform": { + "type": "fluid.transforms.value", + "inputPath": "http://registry\\.gpii\\.net/common/stickyKeys", + "outputPath": "value" + }, + "path": { + "transform": { + "type": "fluid.transforms.literalValue", + "input": "pvParam.dwFlags.SKF_STICKYKEYSON" + } + } + } + } + } + }, + "configure": [ + "settings.configure" + ], + "restore": [ + "settings.configure" + ], + "isInstalled": [ + { + "type": "gpii.deviceReporter.alwaysInstalled" + } + ] + }, + + "com.microsoft.windows.filterKeys": { + "name": "Windows FilterKeys", + "contexts": { + "OS": [ + { + "id": "win32", + "version": ">=5.0" + } + ] + }, + "settingsHandlers": { + "configure": { + "type": "gpii.windows.spiSettingsHandler", + "options": { + "getAction": "SPI_GETFILTERKEYS", + "setAction": "SPI_SETFILTERKEYS", + "uiParam": "struct_size", + "pvParam": { + "type": "struct", + "name": "FILTERKEYS" + }, + "verifySettings": false + }, + "capabilities": [ + "applications.com\\.microsoft\\.windows\\.filterKeys\\.id" + ], + "capabilitiesTransformations": { + "FilterKeysEnable": { + "transform": { + "type": "fluid.transforms.binaryOp", + "leftPath": "http://registry\\.gpii\\.net/common/debounceEnable", + "left": false, + "rightPath": "http://registry\\.gpii\\.net/common/slowKeysEnable", + "right": false, + "operator": "||", + "outputPath": "value" + }, + "path": { + "transform": { + "type": "fluid.transforms.literalValue", + "input": "pvParam.dwFlags.FKF_FILTERKEYSON" + } + } + }, + "SlowKeysInterval": { + "transform": { + "type": "fluid.transforms.condition", + "conditionPath": "http://registry\\.gpii\\.net/common/slowKeysEnable", + "true": { + "transform": { + "type": "fluid.transforms.linearScale", + "inputPath": "http://registry\\.gpii\\.net/common/slowKeysInterval", + "factor": 1000 + } + }, + "false": 0, + "outputPath": "value" + }, + "path": { + "transform": { + "type": "fluid.transforms.literalValue", + "input": "pvParam.iWaitMSec" + } + } + }, + "BounceKeysInterval": { + "transform": { + "type": "fluid.transforms.condition", + "condition": { + "transform": { + "type": "fluid.transforms.binaryOp", + "leftPath": "http://registry\\.gpii\\.net/common/slowKeysEnable", + "left": false, + "right": false, + "operator": "===" + } + }, + "true": { + "transform": { + "type": "fluid.transforms.condition", + "conditionPath": "http://registry\\.gpii\\.net/common/debounceEnable", + "true": { + "transform": { + "type": "fluid.transforms.linearScale", + "inputPath": "http://registry\\.gpii\\.net/common/debounceInterval", + "factor": 1000 + } + }, + "false": 0, + "outputPath": "value" + } + }, + "false": 0, + "outputPath": "value" + }, + "path": { + "transform": { + "type": "fluid.transforms.literalValue", + "input": "pvParam.iBounceMSec" + } + } + } + } + } + }, + "configure": [ + "settings.configure" + ], + "restore": [ + "settings.configure" + ], + "isInstalled": [ + { + "type": "gpii.deviceReporter.alwaysInstalled" + } + ] + }, + + "com.microsoft.windows.mouseKeys": { + "name": "Windows MouseKeys", + "contexts": { + "OS": [ + { + "id": "win32", + "version": ">=5.0" + } + ] + }, + "settingsHandlers": { + "configure": { + "type": "gpii.windows.spiSettingsHandler", + "options": { + "getAction": "SPI_GETMOUSEKEYS", + "setAction": "SPI_SETMOUSEKEYS", + "uiParam": "struct_size", + "pvParam": { + "type": "struct", + "name": "MOUSEKEYS" + }, + "verifySettings": true + }, + "capabilities": [ + "applications.com\\.microsoft\\.windows\\.mouseKeys\\.id" + ], + "capabilitiesTransformations": { + "MouseKeysOn": { + "transform": { + "type": "fluid.transforms.value", + "inputPath": "http://registry\\.gpii\\.net/common/mouseEmulationEnabled", + "outputPath": "value" + }, + "path": { + "transform": { + "type": "fluid.transforms.literalValue", + "input": "pvParam.dwFlags.MKF_MOUSEKEYSON" + } + } + }, + "MaxSpeed": { + "transform": { + "type": "fluid.transforms.round", + "input": { + "transform": { + "type": "fluid.transforms.linearScale", + "inputPath": "http://registry\\.gpii\\.net/common/cursorSpeed", + "factor": 350, + "offset": 10 + } + }, + "outputPath": "value" + }, + "path": { + "transform": { + "type": "fluid.transforms.literalValue", + "input": "pvParam.iMaxSpeed" + } + } + }, + "Acceleration": { + "transform": { + "type": "fluid.transforms.binaryOp", + "left": { + "transform": { + "type": "fluid.transforms.linearScale", + "inputPath": "http://registry\\.gpii\\.net/common/initDelay", + "factor": 1000, + "outputPath": "value" + } + }, + "right": { + "transform": { + "type": "fluid.transforms.linearScale", + "inputPath": "http://registry\\.gpii\\.net/common/cursorAcceleration", + "factor": 1000, + "offset": 1000, + "outputPath": "value" + } + }, + "operator": "+" + }, + "path": { + "transform": { + "type": "fluid.transforms.literalValue", + "input": "pvParam.iTimeToMaxSpeed" + } + } + } + } + } + }, + "configure": [ + "settings.configure" + ], + "restore": [ + "settings.configure" + ], + "isInstalled": [ + { + "type": "gpii.deviceReporter.alwaysInstalled" + } + ] + }, + "com.microsoft.windows.mouseTrailing": { "name": "Windows Mouse Trailing", "contexts": { diff --git a/tests/ContextIntegrationTests.js b/tests/ContextIntegrationTests.js index 923696a64..bafdbab73 100644 --- a/tests/ContextIntegrationTests.js +++ b/tests/ContextIntegrationTests.js @@ -34,8 +34,6 @@ fluid.defaults("gpii.tests.contextIntegration.testCaseHolder.linux", { ] }); -// TODO: The potential for this type name to conflict with an member name is unfortunate! -// Should always pick an member name if it matches first fluid.defaults("gpii.tests.contextIntegration.environmentChangedRequestType", { gradeNames: "kettle.test.request.http", path: "/environmentChanged", @@ -45,6 +43,7 @@ fluid.defaults("gpii.tests.contextIntegration.environmentChangedRequestType", { // NOTE: This inherits from from "gpii.test.common.testCaseHolder" via "gpii.test.integration.testCaseHolder.linux" // from which it gets loginRequest, logoutRequest and other standard events fluid.defaults("gpii.tests.contextIntegration.testCaseHolder", { + gradeNames: "gpii.test.common.lifecycleManagerReceiver", events: { refreshEnvironmentChangedRequest: null }, @@ -56,23 +55,13 @@ fluid.defaults("gpii.tests.contextIntegration.testCaseHolder", { environmentChangedRequest2: { type: "gpii.tests.contextIntegration.environmentChangedRequestType" } - }, - distributeOptions: { - record: { - funcName: "gpii.tests.contextIntegration.receiveLifecycleManager", - args: ["{testCaseHolder}", "{arguments}.0"] - }, - target: "{that lifecycleManager}.options.listeners.onCreate" } }); -gpii.tests.contextIntegration.receiveLifecycleManager = function (testCaseHolder, flowManager) { - testCaseHolder.flowManager = flowManager; -}; gpii.tests.contextIntegration.checkCurrentContext = function (lifecycleManager, token, expected) { - jqUnit.assertEquals("Checking that the activeContextName matches: ", expected, - lifecycleManager.activeSessions[token].activeContextName); + var session = lifecycleManager.getSession(token); + jqUnit.assertEquals("Checking that the activeContextName matches: ", expected, session.model.activeContextName); }; @@ -159,7 +148,7 @@ gpii.tests.contextIntegration.data = { } } }, - "onlyBright": { // if user logs in when brightnes is active from the beginning - only expect mag + "onlyBright": { // if user logs in when brightness is active from the beginning - only expect mag "settingsHandlers": { "gpii.gsettings": { "data": [{ @@ -399,16 +388,5 @@ gpii.tests.contextIntegration.baseTestDef = { contexts: gpii.tests.contextIntegration.data.contexts }; -gpii.tests.contextIntegration.buildTestFixtures = function (fixtures) { - return fluid.transform(fixtures, function (fixture) { - var overlay = { - name: fixture.name, - expect: fixture.expect, - sequence: fluid.flatten(fixture.sequenceSegments) - }; - return fluid.extend(true, {}, gpii.tests.contextIntegration.baseTestDef, overlay); - }); -}; - -kettle.test.bootstrapServer(gpii.tests.contextIntegration.buildTestFixtures( - gpii.tests.contextIntegration.fixtures)); +kettle.test.bootstrapServer(gpii.test.buildSegmentedFixtures( + gpii.tests.contextIntegration.fixtures, gpii.tests.contextIntegration.baseTestDef)); diff --git a/tests/JournalIntegrationTests.js b/tests/JournalIntegrationTests.js new file mode 100644 index 000000000..f0e8940e7 --- /dev/null +++ b/tests/JournalIntegrationTests.js @@ -0,0 +1,424 @@ +/** +GPII Context Integration Tests + +Copyright 2016 Raising the Floor - International + +Licensed under the New BSD license. You may not use this file except in +compliance with this License. + +You may obtain a copy of the License at +https://github.com/GPII/universal/blob/master/LICENSE.txt +*/ + + +"use strict"; + +var fluid = require("infusion"), + kettle = fluid.registerNamespace("kettle"), + jqUnit = fluid.registerNamespace("jqUnit"), + gpii = fluid.registerNamespace("gpii"); + +require("../index.js"); + +gpii.loadTestingSupport(); +fluid.setLogging(true); + +fluid.registerNamespace("gpii.tests.journal"); + +gpii.tests.journal.testSpec = fluid.require("%universal/tests/platform/windows/windows-builtIn-testSpec.js"); + +// The os_win7 entry forms the spine of our test. This user has 4 application-specific preferences encoded +// for Windows built-in a11y features - mouse trailing (SPI), high contrast (SPI), large cursors (registry), magnifier (registry) +// our test strategy involves overriding the mock implementation for the large cursors solutions registry entry to instead invoke an +// exploding settings handler +gpii.tests.journal.testDef = gpii.tests.windows.builtIn[0]; + +gpii.tests.journal.initialSettings = { + "gpii.windows.spiSettingsHandler": { + "some.app.id": [{ + "settings": { + "MouseTrails": { + "value": 20 + } + }, + "options": { // We use a lower-quality utility than gpii.settingsHandlers.invokeRetryingHandler in our test case setup + "mockSync": true, + "getAction": "SPI_GETMOUSETRAILS", + "setAction": "SPI_SETMOUSETRAILS" + } + }] + }, + "gpii.windows.registrySettingsHandler": { + "some.app.id": [{ // magnifier stuff + "settings": { + "Invert": 1, + "Magnification": 200, + "MagnificationMode": 4, + "FollowFocus": 0, + "FollowCaret": 1, + "FollowMouse": 1 + }, + "options": { + "mockSync": true, + "hKey": "HKEY_CURRENT_USER", + "path": "Software\\Microsoft\\ScreenMagnifier", + "dataTypes": { + "Magnification": "REG_DWORD", + "Invert": "REG_DWORD", + "FollowFocus": "REG_DWORD", + "FollowCaret": "REG_DWORD", + "FollowMouse": "REG_DWORD", + "MagnificationMode": "REG_DWORD" + } + } + } + ] + } +}; + +fluid.defaults("gpii.tests.journal.solutionsRegistryAdvisor", { + gradeNames: "fluid.component", + distributeOptions: { + "tests.journal.solutionsRegistry": { + record: { + namespace: "journalFilter", + priority: "after:encoding", + funcName: "gpii.tests.journal.solutionsRegistryFilter" + }, + target: "{testCaseHolder flowManager solutionsRegistryDataSource}.options.listeners.onRead" + } + } +}); + +fluid.defaults("gpii.tests.journal.listJournalsRequestComponent", { + gradeNames: "kettle.test.request.http", + path: "/journal/journals.html" +}); + +// Derive from the standard integration testCaseHolder in testing/src/Integration.js to define new requests +// and overrides of the solutions registry and mock settings handlers +fluid.defaults("gpii.tests.journal.testCaseHolder", { + gradeNames: "gpii.tests.journal.baseTestCaseHolder", + events: { + FLUID5931wait: null, + onInitialSettingsComplete: null + }, + components: { + solutionsRegistryAdvisor: { + type: "gpii.tests.journal.solutionsRegistryAdvisor" + }, + // Override this component from gpii.test.integration.testCaseHolder to include the exploding handler + mockSettingsHandlers: { + type: "gpii.tests.integration.mockSettingsHandlerRegistry.journal" + } + } +}); + +// A base holder which doesn't harbour the exploding settings handler +fluid.defaults("gpii.tests.journal.baseTestCaseHolder", { + gradeNames: "gpii.test.integration.testCaseHolder.windows", + distributeOptions: { + "tests.journal.fastSPI": { + record: { + "gpii.windows.spiSettingsHandler": { + delaying: false // defeat this annoying built-in mock behaviour so we can run these tests faster + } + }, + target: "{that mockSettingsHandlers}.options.settingsHandlers" + } + }, + components: { + restoreJournalRequest: { + type: "kettle.test.request.http", + options: { + path: "/journal/restore/%journalId" + } + }, + listJournalsRequest: { + type: "gpii.tests.journal.listJournalsRequestComponent" + }, + loginRequest2: { + type: "gpii.test.common.loginRequestComponent" + }, + logoutRequest2: { + type: "gpii.test.common.logoutRequestComponent" + }, + listJournalsRequest2: { + type: "gpii.tests.journal.listJournalsRequestComponent" + } + } +}); + +gpii.tests.journal.solutionsRegistryOverlay = { + "com.microsoft.windows.cursors": { + settingsHandlers: { + explode: { + type: "gpii.tests.journal.explodingSettingsHandler", + supportedSettings: { + cursorSize: {} + } + }, + configure: { + supportedSettings: { + cursorSize: {} + } + } + }, + configure: ["settings.configure", "settings.explode"] + } +}; + +gpii.tests.journal.solutionsRegistryFilter = function (payload, options) { + if (options.directModel.os === "win32") { // TODO: When GPII-1809 is merged in with whatever strategy it uses, this will need to be improved + return fluid.extend(true, {}, payload, gpii.tests.journal.solutionsRegistryOverlay); + } else { + return payload; + } +}; + +fluid.defaults("gpii.tests.integration.mockSettingsHandlerRegistry.journal", { + gradeNames: "gpii.test.integration.mockSettingsHandlerRegistry.windows", + components: { + explodingSettingsHandler: { + type: "gpii.tests.journal.explodingSettingsHandler" + } + }, + listeners: { + "onCreate.populateExploding": "gpii.tests.journal.populateExplodingSettingsHandler" + } +}); + +// A settings handler with its "set" action overridden to have a globally destructive side-effect - +// the entire Kettle server hosting the FlowManager, etc. will be destroyed +fluid.defaults("gpii.tests.journal.explodingSettingsHandler", { + gradeNames: "gpii.test.integration.mockSettingsHandler", + invokers: { + set: { + funcName: "gpii.tests.journal.settingsHandlerExplode", + args: ["{kettle.test.configuration}", "{that}", "{arguments}.0"] + } + } +}); + +// Reach upwards into the global configuration's server and destroy it +gpii.tests.journal.settingsHandlerExplode = function (configuration, that, payload) { + fluid.log("EXPLODING SETTINGS HANDLER EXECUTING with id " + that.id + " and payload ", payload); + // null out our payload, compensate for GPII-1223/GPII-1891 + payload["com.microsoft.windows.cursors"][0].settings = {}; + // Beat jqUnit's failure handler to ignore the various errors falling out from this process + // kettle.test.pushInstrumentedErrors(function () { + // fluid.log("Received expected failure from destroying active server: ", arguments); + //}); + fluid.invokeLater(function () { // invoke later so that we do not race with construction - TODO: framework bug + configuration.server.destroy(); + fluid.log("Server destroyed"); + }); + // TODO: failure of Ungarism here - invokers need to be eliminated from the framework + return gpii.settingsHandlers.invokeSettingsHandler(that.setImpl, payload); +}; + +gpii.tests.journal.populateExplodingSettingsHandler = function (that) { + gpii.test.integration.mockSettingsHandlerRegistry.populateOne(that, that.explodingSettingsHandler, "gpii.tests.journal.explodingSettingsHandler"); +}; + +gpii.tests.journal.logDestroyed = function (loginRequest, testCaseHolder) { + fluid.log("Received expected server destroyed event"); + // Remove KettleTestUtils.http native handler to this which will trigger a test failure + loginRequest.nativeRequest.removeAllListeners("error"); + loginRequest.nativeRequest.on("error", function (err) { + fluid.log("Received expected hangup error from login request", err); + }); + jqUnit.assert("Reached checkpoint for destruction of server"); + var mocks = fluid.queryIoCSelector(fluid.rootComponent, "gpii.test.integration.mockSettingsHandler", true); + fluid.each(mocks, function (mock) { + mock.settingsStore = {}; + }); + fluid.log("CLEARED " + mocks.length + " mock settingsHandlers"); + // Arbitrarily wait as a result of FLUID5931 bug preventing immediate reconstruction of the server + fluid.invokeLater(testCaseHolder.events.FLUID5931wait.fire); +}; + +gpii.tests.journal.stashJournalId = function (component) { + component.stashedStartTime = Date.now(); + component.stashedJournalId = ">" + new Date(component.stashedStartTime).toISOString(); +}; + +gpii.tests.journal.checkJournalsList = function (markup, component, expectCrashed) { + console.log("Got markup of " + markup); + var match = /a href=".*\/%3E(.*)"/.exec(markup); + console.log("Got " + match.length + " matches"); + var firstDate = decodeURIComponent(match[1]); + var firstTime = Date.parse(firstDate); + fluid.log("Parsed link date " + firstDate + " to time " + firstTime); + jqUnit.assertTrue("Received correct journal time in journal list markup", firstTime > component.stashedStartTime && firstTime < (component.stashedStartTime + 2000)); + // See: http://stackoverflow.com/questions/1979884/how-to-use-javascript-regex-over-multiple-lines + var snapshots = markup.match(/

      ([\s\S]*?)<\/p>/g); + fluid.log("Acquired " + snapshots.length + " snapshots"); + var isCrashed = snapshots[0].indexOf("crashed session") !== -1; + jqUnit.assertEquals("First snapshot has expected crashed status", expectCrashed, isCrashed); +}; + +// Stashes the special members used by gpii.test.checkConfiguration into the testCaseHolder +gpii.tests.journal.stashInitial = function (settingsHandlersPayload, settingsStore, testCaseHolder) { + // Like the effect of gpii.test.snapshotSettings + settingsStore.orig = fluid.transform(settingsHandlersPayload, gpii.settingsHandlers.extractSettingsBlocks); + // Like the effect of gpii.test.expandSettings with 2 blocks missing + var settingsHandlers = fluid.copy(testCaseHolder.options.settingsHandlers); + // We eliminate the last blocks since our initial settings state does not include them, and the blocks + // with values all `undefined` will confuse jqUnit.assertDeepEq in gpii.test.checkConfiguration + settingsHandlers["gpii.windows.spiSettingsHandler"]["some.app.id"].length = 1; + settingsHandlers["gpii.windows.registrySettingsHandler"]["some.app.id"].length = 1; + testCaseHolder.settingsHandlers = settingsHandlers; +}; + +gpii.tests.journal.normalLoginFixtures = [ + { func: "gpii.tests.journal.stashJournalId", + args: "{testCaseHolder}" + }, { + func: "{loginRequest2}.send" + }, { + event: "{loginRequest2}.events.onComplete", + listener: "gpii.test.loginRequestListen" + }, { // TODO: As well as FLUID-5903, implement "parameterised grades" of some kind + func: "{logoutRequest2}.send" + }, { + event: "{logoutRequest2}.events.onComplete", + listener: "gpii.test.logoutRequestListen" + }, { + func: "{listJournalsRequest2}.send" + }, { + event: "{listJournalsRequest2}.events.onComplete", + listener: "gpii.tests.journal.checkJournalsList", + args: ["{arguments}.0", "{testCaseHolder}", false] + } +]; + +gpii.tests.journal.fixtures = [ + { + name: "Journal state and restoration", + expect: 10, + sequenceSegments: [ + { func: "gpii.tests.journal.stashJournalId", + args: "{testCaseHolder}" + }, { + func: "gpii.test.setSettings", + args: [gpii.tests.journal.initialSettings, "{nameResolver}", "{testCaseHolder}.events.onInitialSettingsComplete.fire"] + }, { + event: "{tests}.events.onInitialSettingsComplete", + listener: "fluid.identity" + }, { // Destroy this advisor after first successful startup so that when we recreate the FlowManager, the exploding listener is gone + func: "{testCaseHolder}.solutionsRegistryAdvisor.destroy" + }, + // The following three steps intentionally commented out - they are here to exhibit what *SHOULD* be equivalent to the call to gpii.tests.journal.stashInitial, + // only we prefer to make a more reliable test by verifying that the settings really are identical to the initialSettings in our own records + /* { + func: "gpii.test.expandSettings", + args: [ "{testCaseHolder}", "settingsHandlers" ] + }, { + func: "gpii.test.snapshotSettings", + args: ["{testCaseHolder}.contexts.gpii-default.settingsHandlers", "{tests}.settingsStore", "{nameResolver}", "{testCaseHolder}.events.onSnapshotComplete.fire"] + }, { + event: "{testCaseHolder}.events.onSnapshotComplete", + listener: "fluid.identity" + }, */ { + func: "gpii.tests.journal.stashInitial", + args: [gpii.tests.journal.initialSettings, "{testCaseHolder}.settingsStore", "{testCaseHolder}"] + }, { + func: "{loginRequest}.send" + }, { // As a result of the exploding settings handler, the attempt to login will destroy the server + event: "{configuration}.server.events.afterDestroy", + listener: "gpii.tests.journal.logDestroyed", + args: ["{loginRequest}", "{testCaseHolder}"] + }, { + event: "{testCaseHolder}.events.FLUID5931wait", + listener: "fluid.identity" + }, + kettle.test.startServerSequence, + { + func: "{listJournalsRequest}.send" + }, { + event: "{listJournalsRequest}.events.onComplete", + listener: "gpii.tests.journal.checkJournalsList", + args: ["{arguments}.0", "{testCaseHolder}", true] + }, { + func: "{restoreJournalRequest}.send", + args: [null, { + termMap: { + journalId: "{testCaseHolder}.stashedJournalId" + } + }] + }, { + event: "{restoreJournalRequest}.events.onComplete", + listener: "gpii.test.assertJSONResponseSubset", + args: { + message: "Successful response from journal restoration", + string: "{arguments}.0", + request: "{restoreJournalRequest}", + expected: { + message: "The system's settings were restored from a snapshot" + } + } + }, gpii.test.checkSequence, + // Now verify that we can log on normally after a restore, and generate a non-crashed session after logging off + gpii.tests.journal.normalLoginFixtures + ] + } +]; + +gpii.tests.journal.badJournalFixtures = [ + { + name: "Restoration of bad journal file doesn't jam system", + expect: 7, + sequenceSegments: [{ + funcName: "kettle.test.pushInstrumentedErrors", + args: "kettle.requestUncaughtExceptionHandler" + }, { + func: "{restoreJournalRequest}.send", + args: [null, { + termMap: { + journalId: "Invalid journal ID" + } + }] + }, { + event: "{restoreJournalRequest}.events.onComplete", + listener: "kettle.test.assertErrorResponse", + args: { + message: "Received 500 error when asking for bad journal file", + errorTexts: "The only supported formats for journalId are", + statusCode: 500, + string: "{arguments}.0", + request: "{restoreJournalRequest}" + } + }, { + func: "kettle.test.popInstrumentedErrors" + }, + gpii.tests.journal.normalLoginFixtures + ] + } +]; + +gpii.tests.journal.baseTestDefBase = fluid.freezeRecursive({ + userToken: gpii.tests.journal.testDef.userToken, + settingsHandlers: gpii.tests.journal.testDef.settingsHandlers, + config: { + configName: gpii.tests.journal.testSpec.configName, + configPath: gpii.tests.journal.testSpec.configPath + } +}); + +gpii.tests.journal.baseTestDef = fluid.extend({ + name: "Journal Restoration Tests", + gradeNames: "gpii.tests.journal.testCaseHolder" +}, gpii.tests.journal.baseTestDefBase); + +gpii.tests.journal.badJournalBaseTestDef = fluid.extend({ + name: "Bad Journal Restoration Tests", + gradeNames: "gpii.tests.journal.baseTestCaseHolder" +}, gpii.tests.journal.baseTestDefBase); + + +kettle.test.bootstrapServer(gpii.test.buildSegmentedFixtures( + gpii.tests.journal.fixtures, gpii.tests.journal.baseTestDef)); + +kettle.test.bootstrapServer(gpii.test.buildSegmentedFixtures( + gpii.tests.journal.badJournalFixtures, gpii.tests.journal.badJournalBaseTestDef)); diff --git a/tests/MultiSettingsHandlerTests.js b/tests/MultiSettingsHandlerTests.js index eee9dc28b..7474ce007 100644 --- a/tests/MultiSettingsHandlerTests.js +++ b/tests/MultiSettingsHandlerTests.js @@ -43,7 +43,7 @@ gpii.tests.multiSHSupport.testDefs = [ }, "options": { "filename": "/tmp/fakemag1.settings.json" - }, + } }, { // high contrast settings "settings": { "crazyColor": true diff --git a/tests/all-tests.js b/tests/all-tests.js index 884c4c487..20b84a3ec 100644 --- a/tests/all-tests.js +++ b/tests/all-tests.js @@ -44,6 +44,7 @@ var testIncludes = [ "./MultiSettingsHandlerTests.js", "./IntegrationTests.js", "./ContextIntegrationTests.js", + "./JournalIntegrationTests.js", "./DeviceReporterErrorTests.js", "./PreferencesServerErrorTests.js", "./UntrustedBrowserChannelTests.js", diff --git a/tests/platform/windows/windows-builtIn-testSpec.js b/tests/platform/windows/windows-builtIn-testSpec.js index b4c6a6668..a94761df5 100644 --- a/tests/platform/windows/windows-builtIn-testSpec.js +++ b/tests/platform/windows/windows-builtIn-testSpec.js @@ -47,6 +47,66 @@ gpii.tests.windows.builtIn = [ "type": "BOOL" } } + }, { + "settings": { + "MouseKeysOn": { + "path": "pvParam.dwFlags.MKF_MOUSEKEYSON", + "value": true + }, + "MaxSpeed": { + "path": "pvParam.iMaxSpeed", + "value": 100 + }, + "Acceleration": { + "path": "pvParam.iTimeToMaxSpeed", + "value": 1000 + } + }, + "options": { + "getAction": "SPI_GETMOUSEKEYS", + "setAction": "SPI_SETMOUSEKEYS", + "uiParam": "struct_size", + "pvParam": { + "type": "struct", + "name": "MOUSEKEYS" + } + } + }, { + "settings": { + "StickyKeysOn": { + "path": "pvParam.dwFlags.SKF_STICKYKEYSON", + "value": true + } + }, + "options": { + "getAction": "SPI_GETSTICKYKEYS", + "setAction": "SPI_SETSTICKYKEYS", + "uiParam": "struct_size", + "pvParam": { + "type": "struct", + "name": "STICKYKEYS" + } + } + }, { + "settings": { + "FilterKeysEnable": { + "path": "pvParam.dwFlags.FKF_FILTERKEYSON", + "value": true + }, + "BounceKeysInterval": { + "path": "pvParam.iBounceMSec", + "value": 1000 + } + }, + "options": { + "getAction": "SPI_GETFILTERKEYS", + "setAction": "SPI_SETFILTERKEYS", + "uiParam": "struct_size", + "pvParam": { + "type": "struct", + "name": "FILTERKEYS" + } + } }, { // high contrast settings "settings": { "HighContrastOn": { @@ -160,6 +220,58 @@ gpii.tests.windows.builtIn = [ "type": "BOOL" } } + }, { + "settings": { + "MouseKeysOn": { + "path": "pvParam.dwFlags.MKF_MOUSEKEYSON", + "value": true + } + }, + "options": { + "getAction": "SPI_GETMOUSEKEYS", + "setAction": "SPI_SETMOUSEKEYS", + "uiParam": "struct_size", + "pvParam": { + "type": "struct", + "name": "MOUSEKEYS" + } + } + }, { + "settings": { + "StickyKeysOn": { + "path": "pvParam.dwFlags.SKF_STICKYKEYSON", + "value": true + } + }, + "options": { + "getAction": "SPI_GETSTICKYKEYS", + "setAction": "SPI_SETSTICKYKEYS", + "uiParam": "struct_size", + "pvParam": { + "type": "struct", + "name": "STICKYKEYS" + } + } + }, { + "settings": { + "FilterKeysEnable": { + "path": "pvParam.dwFlags.FKF_FILTERKEYSON", + "value": true + }, + "BounceKeysInterval": { + "path": "pvParam.iBounceMSec", + "value": 1000 + } + }, + "options": { + "getAction": "SPI_GETFILTERKEYS", + "setAction": "SPI_SETFILTERKEYS", + "uiParam": "struct_size", + "pvParam": { + "type": "struct", + "name": "FILTERKEYS" + } + } }, { // high contrast settings "settings": { "HighContrastOn": { diff --git a/tests/web/html/all-tests.html b/tests/web/html/all-tests.html index 670a9ef42..033827505 100644 --- a/tests/web/html/all-tests.html +++ b/tests/web/html/all-tests.html @@ -14,7 +14,9 @@ "../../../gpii/node_modules/canopyMatchMaker/test/web/html/CanopyMatchMakerUtilitiesTests.html", "../../../gpii/node_modules/matchMakerFramework/test/html/MatchMakerUtilitiesTest.html", "../../../gpii/node_modules/transformer/test/html/TransformerTests.html", + "../../../gpii/node_modules/journal/test/html/JournalIdParserTests.html", "../../../gpii/node_modules/lifecycleManager/test/html/LifecycleManagerTest.html", + "../../../gpii/node_modules/lifecycleManager/test/html/DynamicComponentIndexerTest.html", "../../../gpii/node_modules/ontologyHandler/test/html/OntologyHandlerUtilitiesTest.html", "../../../gpii/node_modules/settingsHandlers/test/web/html/SettingsHandlerUtilitiesTest.html", "../../../gpii/node_modules/contextManager/test/html/ContextManagerUtilitiesTests.html", From 7c7c6be29b265b65cbd1d579f463a826b9bfc038 Mon Sep 17 00:00:00 2001 From: Joseph Scheuhammer Date: Tue, 1 Nov 2016 13:18:29 -0400 Subject: [PATCH 7/8] GPII-1839: Improved test of handling writing non-existent file. Added some directory structure to better reflect the situation when a settings file does not exist because the solution is not installed. --- .../settingsHandlers/test/settingsHandlerUtilitiesTests.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/gpii/node_modules/settingsHandlers/test/settingsHandlerUtilitiesTests.js b/gpii/node_modules/settingsHandlers/test/settingsHandlerUtilitiesTests.js index e71c13d43..7c4d13957 100644 --- a/gpii/node_modules/settingsHandlers/test/settingsHandlerUtilitiesTests.js +++ b/gpii/node_modules/settingsHandlers/test/settingsHandlerUtilitiesTests.js @@ -58,10 +58,10 @@ gpii.tests.settingsHandlers.writeFile = function () { // Check writing non-existing file gpii.settingsHandlers.writeFile(dataOut, { - filename: __dirname + "/data/bogus.json" + filename: __dirname + "/data/bogus/bogus.ini" }); dataIn = gpii.settingsHandlers.readFile({ - filename: __dirname + "/data/bogus.json" + filename: __dirname + "/data/bogus/bogus.ini" }); jqUnit.assertEquals("Writing to non-existing file", undefined, dataIn); }; From 0f326985a9af1242cbd212dec387279670d99f67 Mon Sep 17 00:00:00 2001 From: Joseph Scheuhammer Date: Wed, 9 Nov 2016 09:32:52 -0500 Subject: [PATCH 8/8] GPII-1839: Consolidated basic configuration files. - replaced base gpii.tests.acceptance.localInstall.config.json|txt with gpii.tests.acceptance.localInstall.dynamicDeviceReporter.config.json|txt - updated dependant configuration files to reference the new dynamicDeviceReporter base configuration file. - updated all the associated ".txt" documentation files. --- .../gpii.tests.acceptance.localInstall.config.json | 13 ------------- ...e.localInstall.dynamicDeviceReporter.config.txt} | 9 +++++---- ...pii.tests.acceptance.android.builtIn.config.json | 2 +- ...gpii.tests.acceptance.android.builtIn.config.txt | 9 +++------ ...ii.tests.acceptance.android.talkback.config.json | 2 +- ...pii.tests.acceptance.android.talkback.config.txt | 9 +++------ .../gpii.tests.acceptance.linux.builtIn.config.json | 2 +- .../gpii.tests.acceptance.linux.builtIn.config.txt | 9 +++------ .../gpii.tests.acceptance.linux.chrome.config.json | 2 +- .../gpii.tests.acceptance.linux.chrome.config.txt | 10 ++++------ .../gpii.tests.acceptance.linux.orca.config.json | 2 +- .../gpii.tests.acceptance.linux.orca.config.txt | 12 +++++------- ...pii.tests.acceptance.windows.builtIn.config.json | 2 +- ...gpii.tests.acceptance.windows.builtIn.config.txt | 10 ++++++---- ...gpii.tests.acceptance.windows.chrome.config.json | 2 +- .../gpii.tests.acceptance.windows.chrome.config.txt | 11 +++++------ .../gpii.tests.acceptance.windows.jaws.config.json | 2 +- .../gpii.tests.acceptance.windows.jaws.config.txt | 6 ++++++ ...gpii.tests.acceptance.windows.maavis.config.json | 2 +- .../gpii.tests.acceptance.windows.maavis.config.txt | 6 ++++++ .../gpii.tests.acceptance.windows.nvda.config.json | 2 +- .../gpii.tests.acceptance.windows.nvda.config.txt | 6 ++---- ...i.tests.acceptance.windows.readWrite.config.json | 2 +- ...ii.tests.acceptance.windows.readWrite.config.txt | 3 +-- 24 files changed, 60 insertions(+), 75 deletions(-) delete mode 100644 tests/configs/gpii.tests.acceptance.localInstall.config.json rename tests/configs/{gpii.tests.acceptance.localInstall.config.txt => gpii.tests.acceptance.localInstall.dynamicDeviceReporter.config.txt} (50%) create mode 100644 tests/platform/windows/configs/gpii.tests.acceptance.windows.jaws.config.txt create mode 100644 tests/platform/windows/configs/gpii.tests.acceptance.windows.maavis.config.txt diff --git a/tests/configs/gpii.tests.acceptance.localInstall.config.json b/tests/configs/gpii.tests.acceptance.localInstall.config.json deleted file mode 100644 index 9d0d4dce9..000000000 --- a/tests/configs/gpii.tests.acceptance.localInstall.config.json +++ /dev/null @@ -1,13 +0,0 @@ -{ - "type": "gpii.tests.acceptance.localInstall.config", - "options": { - "distributeOptions": { - "acceptance.rawPreferencesDataSource": { - "record": "%universal/testData/preferences/acceptanceTests/%userToken.json", - "target": "{that rawPreferencesServer rawPreferencesDataSource}.options.path", - "priority": "after:development.rawPreferencesDataSource" - } - } - }, - "mergeConfigs": "%universal/gpii/configs/gpii.config.all.development.dr.production.json" -} diff --git a/tests/configs/gpii.tests.acceptance.localInstall.config.txt b/tests/configs/gpii.tests.acceptance.localInstall.dynamicDeviceReporter.config.txt similarity index 50% rename from tests/configs/gpii.tests.acceptance.localInstall.config.txt rename to tests/configs/gpii.tests.acceptance.localInstall.dynamicDeviceReporter.config.txt index 43de6b612..bcfaeef4c 100644 --- a/tests/configs/gpii.tests.acceptance.localInstall.config.txt +++ b/tests/configs/gpii.tests.acceptance.localInstall.dynamicDeviceReporter.config.txt @@ -1,9 +1,10 @@ This configuration file is used for acceptance testing. -It uses the basic (default) development setup of the system, but replaces the -folder with NP sets used to be (universal)/testData/preferences/acceptanceTests -as opposed to the normal preferences folder. -Besides that, everything will be based on the normal development mode setup. +It uses the basic (default) development setup of the system, with a couple of +modifications. It replaces the folder with NP sets in (universal)/testData/preferences/acceptanceTests +as opposed to the normal preferences folder. It also uses the dynamic device +reporter (the device reporter in production mode). Otherwise, everything else +is based on the normal development mode setup. The name "localInstall" refers to a setup where GPII is running as installed locally on the machine (as opposed to eg. running the system as a cloudBased flowmanager). diff --git a/tests/platform/android/configs/gpii.tests.acceptance.android.builtIn.config.json b/tests/platform/android/configs/gpii.tests.acceptance.android.builtIn.config.json index 331f771c7..d3669f183 100644 --- a/tests/platform/android/configs/gpii.tests.acceptance.android.builtIn.config.json +++ b/tests/platform/android/configs/gpii.tests.acceptance.android.builtIn.config.json @@ -9,5 +9,5 @@ } } }, - "mergeConfigs": "%universal/tests/configs/gpii.tests.acceptance.localInstall.config.json" + "mergeConfigs": "%universal/tests/configs/gpii.tests.acceptance.localInstall.dynamicDeviceReporter.config.json" } diff --git a/tests/platform/android/configs/gpii.tests.acceptance.android.builtIn.config.txt b/tests/platform/android/configs/gpii.tests.acceptance.android.builtIn.config.txt index 9b629914e..debaa2181 100644 --- a/tests/platform/android/configs/gpii.tests.acceptance.android.builtIn.config.txt +++ b/tests/platform/android/configs/gpii.tests.acceptance.android.builtIn.config.txt @@ -1,8 +1,5 @@ This config file is used for integration and acceptance tests on Android. -It includes to the basic localInstall setup for acceptance tests (modifying the -preferences folder used), which in turn includes the standard development -config file (running GPII locally, using development setup). - -This config sets the device reporter file to be 'android_builtIn.json', which will -report only the standard built in solutions as installed. +It makes use of dynamic device reporter: It uses the basic local install setup +in development mode, but with the device reporter in production mode, merging in +gpii's "all.development.dr.production" configuration. diff --git a/tests/platform/android/configs/gpii.tests.acceptance.android.talkback.config.json b/tests/platform/android/configs/gpii.tests.acceptance.android.talkback.config.json index 1471a5475..a578bdbbe 100644 --- a/tests/platform/android/configs/gpii.tests.acceptance.android.talkback.config.json +++ b/tests/platform/android/configs/gpii.tests.acceptance.android.talkback.config.json @@ -9,5 +9,5 @@ } } }, - "mergeConfigs": "%universal/tests/configs/gpii.tests.acceptance.localInstall.config.json" + "mergeConfigs": "%universal/tests/configs/gpii.tests.acceptance.localInstall.dynamicDeviceReporter.config.json" } diff --git a/tests/platform/android/configs/gpii.tests.acceptance.android.talkback.config.txt b/tests/platform/android/configs/gpii.tests.acceptance.android.talkback.config.txt index 640847d84..ecf7e67d0 100644 --- a/tests/platform/android/configs/gpii.tests.acceptance.android.talkback.config.txt +++ b/tests/platform/android/configs/gpii.tests.acceptance.android.talkback.config.txt @@ -1,8 +1,5 @@ This configuration file is used by the for acceptance test: android-talkback-testSpec.js. -It includes to the basic localInstall setup for acceptance tests (modifying the -preferences folder used), which in turn includes the standard development -config file (running GPII locally, using development setup). - -This config sets the device reporter file to be 'talkback.json', which will report -Talkback as being installed on the system. +It makes use of dynamic device reporter: It includes the basic local install +setup in development mode, but with the device reporter in production mode, +merging in gpii's "all.development.dr.production" configuration. diff --git a/tests/platform/linux/configs/gpii.tests.acceptance.linux.builtIn.config.json b/tests/platform/linux/configs/gpii.tests.acceptance.linux.builtIn.config.json index e7f91dca5..a5e52b6f2 100644 --- a/tests/platform/linux/configs/gpii.tests.acceptance.linux.builtIn.config.json +++ b/tests/platform/linux/configs/gpii.tests.acceptance.linux.builtIn.config.json @@ -9,5 +9,5 @@ } } }, - "mergeConfigs": "%universal/tests/configs/gpii.tests.acceptance.localInstall.config.json" + "mergeConfigs": "%universal/tests/configs/gpii.tests.acceptance.localInstall.dynamicDeviceReporter.config.json" } diff --git a/tests/platform/linux/configs/gpii.tests.acceptance.linux.builtIn.config.txt b/tests/platform/linux/configs/gpii.tests.acceptance.linux.builtIn.config.txt index 1880e0363..1e0c2b21d 100644 --- a/tests/platform/linux/configs/gpii.tests.acceptance.linux.builtIn.config.txt +++ b/tests/platform/linux/configs/gpii.tests.acceptance.linux.builtIn.config.txt @@ -1,8 +1,5 @@ This config file is used for integration and acceptance tests on Linux as well as the context integration tests -It includes to the basic localInstall setup for acceptance tests (modifying the -preferences folder used), which in turn includes the standard development -config file (running GPII locally, using development setup). - -This config sets the device reporter file to be 'linux_builtIn.json', which will -report only the standard built in solutions as installed. +It makes use of dynamic device reporter: It includes the basic local install +setup in development mode, but with the device reporter in production mode, +merging in gpii's "all.development.dr.production" configuration. diff --git a/tests/platform/linux/configs/gpii.tests.acceptance.linux.chrome.config.json b/tests/platform/linux/configs/gpii.tests.acceptance.linux.chrome.config.json index eb9e07bfe..dceee6c40 100644 --- a/tests/platform/linux/configs/gpii.tests.acceptance.linux.chrome.config.json +++ b/tests/platform/linux/configs/gpii.tests.acceptance.linux.chrome.config.json @@ -9,5 +9,5 @@ } } }, - "mergeConfigs": "%universal/tests/configs/gpii.tests.acceptance.localInstall.config.json" + "mergeConfigs": "%universal/tests/configs/gpii.tests.acceptance.localInstall.dynamicDeviceReporter.config.json" } diff --git a/tests/platform/linux/configs/gpii.tests.acceptance.linux.chrome.config.txt b/tests/platform/linux/configs/gpii.tests.acceptance.linux.chrome.config.txt index 5a536ac33..c8a385bc7 100644 --- a/tests/platform/linux/configs/gpii.tests.acceptance.linux.chrome.config.txt +++ b/tests/platform/linux/configs/gpii.tests.acceptance.linux.chrome.config.txt @@ -1,8 +1,6 @@ This configuration file is used for testing the chrome extension in Linux -It includes to the basic localInstall setup for acceptance tests (modifying the -preferences folder used), which in turn includes the standard development -config file (running GPII locally, using development setup). - -This config sets the device reporter file to be 'chrome.json', which will report -the Cloud4all Chrome extension for Google Chrome as being installed on the system. +It makes use of dynamic device reporter: It includes the basic local install +setup in development mode (modifying the preferences folder used), but with the +device reporter in production mode, merging in gpii's +"all.development.dr.production" configuration. diff --git a/tests/platform/linux/configs/gpii.tests.acceptance.linux.orca.config.json b/tests/platform/linux/configs/gpii.tests.acceptance.linux.orca.config.json index 6c6750324..72f5966d5 100644 --- a/tests/platform/linux/configs/gpii.tests.acceptance.linux.orca.config.json +++ b/tests/platform/linux/configs/gpii.tests.acceptance.linux.orca.config.json @@ -9,5 +9,5 @@ } } }, - "mergeConfigs": "%universal/tests/configs/gpii.tests.acceptance.localInstall.config.json" + "mergeConfigs": "%universal/tests/configs/gpii.tests.acceptance.localInstall.dynamicDeviceReporter.config.json" } diff --git a/tests/platform/linux/configs/gpii.tests.acceptance.linux.orca.config.txt b/tests/platform/linux/configs/gpii.tests.acceptance.linux.orca.config.txt index c8c82d5f0..1e0014f98 100644 --- a/tests/platform/linux/configs/gpii.tests.acceptance.linux.orca.config.txt +++ b/tests/platform/linux/configs/gpii.tests.acceptance.linux.orca.config.txt @@ -1,8 +1,6 @@ -This configuration file is used by the for acceptance test: AcceptanceTests_orca.js. +This configuration file is used by the acceptance test: AcceptanceTests_orca.js. -It includes to the basic localInstall setup for acceptance tests (modifying the -preferences folder used), which in turn includes the standard development -config file (running GPII locally, using development setup). - -This config sets the device reporter file to be 'orca.json', which will report -Orca as being installed on the system. +It makes use of dynamic device reporter: It includes the basic local install +setup in development mode (modifying the preferences folder used), but with the +device reporter in production mode, merging in gpii's +"all.development.dr.production" configuration. diff --git a/tests/platform/windows/configs/gpii.tests.acceptance.windows.builtIn.config.json b/tests/platform/windows/configs/gpii.tests.acceptance.windows.builtIn.config.json index 064856ecb..063273c9c 100644 --- a/tests/platform/windows/configs/gpii.tests.acceptance.windows.builtIn.config.json +++ b/tests/platform/windows/configs/gpii.tests.acceptance.windows.builtIn.config.json @@ -9,5 +9,5 @@ } } }, - "mergeConfigs": "%universal/tests/configs/gpii.tests.acceptance.localInstall.config.json" + "mergeConfigs": "%universal/tests/configs/gpii.tests.acceptance.localInstall.dynamicDeviceReporter.config.json" } diff --git a/tests/platform/windows/configs/gpii.tests.acceptance.windows.builtIn.config.txt b/tests/platform/windows/configs/gpii.tests.acceptance.windows.builtIn.config.txt index 91d923402..1a509d009 100644 --- a/tests/platform/windows/configs/gpii.tests.acceptance.windows.builtIn.config.txt +++ b/tests/platform/windows/configs/gpii.tests.acceptance.windows.builtIn.config.txt @@ -1,5 +1,7 @@ -This configuration file is used by the for acceptance test: AcceptanceTests_builtIn.js. +This configuration file is used by the acceptance test: AcceptanceTests_builtIn.js. -It includes to the basic localInstall setup for acceptance tests (modifying the preferences folder used), which in turn includes the standard development config file (running GPII locally, using development setup). - -This config sets the device reporter file to be 'win7_builtIn.json', which will report only the standard built in solutions as installed. \ No newline at end of file +It uses the basic (default) development setup of the system, with a couple of +modifications. It replaces the folder with NP sets in (universal)/testData/preferences/acceptanceTests +as opposed to the normal preferences folder. It also uses the dynamic device +reporter (the device reporter in production mode). Otherwise, everything else +is based on the normal development mode setup. diff --git a/tests/platform/windows/configs/gpii.tests.acceptance.windows.chrome.config.json b/tests/platform/windows/configs/gpii.tests.acceptance.windows.chrome.config.json index a901f51a9..3cccf7ace 100644 --- a/tests/platform/windows/configs/gpii.tests.acceptance.windows.chrome.config.json +++ b/tests/platform/windows/configs/gpii.tests.acceptance.windows.chrome.config.json @@ -9,5 +9,5 @@ } } }, - "mergeConfigs": "%universal/tests/configs/gpii.tests.acceptance.localInstall.config.json" + "mergeConfigs": "%universal/tests/configs/gpii.tests.acceptance.localInstall.dynamicDeviceReporter.config.json" } diff --git a/tests/platform/windows/configs/gpii.tests.acceptance.windows.chrome.config.txt b/tests/platform/windows/configs/gpii.tests.acceptance.windows.chrome.config.txt index 02a8388b4..8d8cbeb04 100644 --- a/tests/platform/windows/configs/gpii.tests.acceptance.windows.chrome.config.txt +++ b/tests/platform/windows/configs/gpii.tests.acceptance.windows.chrome.config.txt @@ -1,8 +1,7 @@ This configuration file is used for testing the chrome extension in Windows -It includes to the basic localInstall setup for acceptance tests (modifying the -preferences folder used), which in turn includes the standard development -config file (running GPII locally, using development setup). - -This config sets the device reporter file to be 'chrome.json', which will report -the Cloud4all Chrome extension for Google Chrome as being installed on the system. +It uses the basic (default) development setup of the system, with a couple of +modifications. It replaces the folder with NP sets in (universal)/testData/preferences/acceptanceTests +as opposed to the normal preferences folder. It also uses the dynamic device +reporter (the device reporter in production mode). Otherwise, everything else +is based on the normal development mode setup. diff --git a/tests/platform/windows/configs/gpii.tests.acceptance.windows.jaws.config.json b/tests/platform/windows/configs/gpii.tests.acceptance.windows.jaws.config.json index 668c38354..ecedcc8a3 100644 --- a/tests/platform/windows/configs/gpii.tests.acceptance.windows.jaws.config.json +++ b/tests/platform/windows/configs/gpii.tests.acceptance.windows.jaws.config.json @@ -9,5 +9,5 @@ } } }, - "mergeConfigs": "%universal/tests/configs/gpii.tests.acceptance.localInstall.config.json" + "mergeConfigs": "%universal/tests/configs/gpii.tests.acceptance.localInstall.dynamicDeviceReporter.config.json" } diff --git a/tests/platform/windows/configs/gpii.tests.acceptance.windows.jaws.config.txt b/tests/platform/windows/configs/gpii.tests.acceptance.windows.jaws.config.txt new file mode 100644 index 000000000..4fe817376 --- /dev/null +++ b/tests/platform/windows/configs/gpii.tests.acceptance.windows.jaws.config.txt @@ -0,0 +1,6 @@ +This configuration file is used by the acceptance test: windows-jaws-testSpec.js. + +It makes use of dynamic device reporter: It includes the basic local install +setup in development mode (modifying the preferences folder used), but with the +device reporter in production mode, merging in gpii's +"all.development.dr.production" configuration. diff --git a/tests/platform/windows/configs/gpii.tests.acceptance.windows.maavis.config.json b/tests/platform/windows/configs/gpii.tests.acceptance.windows.maavis.config.json index fa57ba860..c360cd265 100644 --- a/tests/platform/windows/configs/gpii.tests.acceptance.windows.maavis.config.json +++ b/tests/platform/windows/configs/gpii.tests.acceptance.windows.maavis.config.json @@ -9,5 +9,5 @@ } } }, - "mergeConfigs": "%universal/tests/configs/gpii.tests.acceptance.localInstall.config.json" + "mergeConfigs": "%universal/tests/configs/gpii.tests.acceptance.localInstall.dynamicDeviceReporter.config.json" } diff --git a/tests/platform/windows/configs/gpii.tests.acceptance.windows.maavis.config.txt b/tests/platform/windows/configs/gpii.tests.acceptance.windows.maavis.config.txt new file mode 100644 index 000000000..15c9e3e34 --- /dev/null +++ b/tests/platform/windows/configs/gpii.tests.acceptance.windows.maavis.config.txt @@ -0,0 +1,6 @@ +This configuration file is used by the acceptance test: windows-maavis-testSpec.js. + +It makes use of dynamic device reporter: It includes the basic local install +setup in development mode (modifying the preferences folder used), but with the +device reporter in production mode, merging in gpii's +"all.development.dr.production" configuration. diff --git a/tests/platform/windows/configs/gpii.tests.acceptance.windows.nvda.config.json b/tests/platform/windows/configs/gpii.tests.acceptance.windows.nvda.config.json index 66501749c..bbba68e0d 100644 --- a/tests/platform/windows/configs/gpii.tests.acceptance.windows.nvda.config.json +++ b/tests/platform/windows/configs/gpii.tests.acceptance.windows.nvda.config.json @@ -9,5 +9,5 @@ } } }, - "mergeConfigs": "%universal/tests/configs/gpii.tests.acceptance.localInstall.config.json" + "mergeConfigs": "%universal/tests/configs/gpii.tests.acceptance.localInstall.dynamicDeviceReporter.config.json" } diff --git a/tests/platform/windows/configs/gpii.tests.acceptance.windows.nvda.config.txt b/tests/platform/windows/configs/gpii.tests.acceptance.windows.nvda.config.txt index 6029932aa..62ed68580 100644 --- a/tests/platform/windows/configs/gpii.tests.acceptance.windows.nvda.config.txt +++ b/tests/platform/windows/configs/gpii.tests.acceptance.windows.nvda.config.txt @@ -1,5 +1,3 @@ -This configuration file is used by the for acceptance test: AcceptanceTests_nvda.js. +This configuration file is used by the acceptance test: AcceptanceTests_nvda.js. -It includes to the basic localInstall setup for acceptance tests (modifying the preferences folder used), which in turn includes the standard development config file (running GPII locally, using development setup). - -This config sets the device reporter file to be 'nvda.json', which will report nvda as being installed on the system. \ No newline at end of file +It makes use of dynamic device reporter: It includes the basic local install setup in development mode (modifying the preferences folder used), but with the device reporter in production mode, merging in gpii's "all.development.dr.production" configuration. diff --git a/tests/platform/windows/configs/gpii.tests.acceptance.windows.readWrite.config.json b/tests/platform/windows/configs/gpii.tests.acceptance.windows.readWrite.config.json index b8dd7c7ba..747fb9f96 100644 --- a/tests/platform/windows/configs/gpii.tests.acceptance.windows.readWrite.config.json +++ b/tests/platform/windows/configs/gpii.tests.acceptance.windows.readWrite.config.json @@ -9,5 +9,5 @@ } } }, - "mergeConfigs": "%universal/tests/configs/gpii.tests.acceptance.localInstall.config.json" + "mergeConfigs": "%universal/tests/configs/gpii.tests.acceptance.localInstall.dynamicDeviceReporter.config.json" } diff --git a/tests/platform/windows/configs/gpii.tests.acceptance.windows.readWrite.config.txt b/tests/platform/windows/configs/gpii.tests.acceptance.windows.readWrite.config.txt index 24f6bd073..cbf5a3c4c 100644 --- a/tests/platform/windows/configs/gpii.tests.acceptance.windows.readWrite.config.txt +++ b/tests/platform/windows/configs/gpii.tests.acceptance.windows.readWrite.config.txt @@ -1,5 +1,4 @@ This configuration file is used by the acceptance tests for Read&Write Gold -It includes the basic localInstall setup for acceptance tests (modifying the preferences folder used), which in turn includes the standard development config file (running GPII locally, using development setup). +It makes use of dynamic device reporter: It includes the basic local install setup in development mode (modifying the preferences folder used), but with the device reporter in production mode, merging in gpii's "all.development.dr.production" configuration. -This config sets the device reporter file to be 'readWrite.json', which will report Read&Write Gold as being installed on the system.