You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@ignite.apache.org by sb...@apache.org on 2016/09/27 15:26:53 UTC

[63/68] [abbrv] ignite git commit: Web console beta-4.

Web console beta-4.


Project: http://git-wip-us.apache.org/repos/asf/ignite/repo
Commit: http://git-wip-us.apache.org/repos/asf/ignite/commit/48d4a925
Tree: http://git-wip-us.apache.org/repos/asf/ignite/tree/48d4a925
Diff: http://git-wip-us.apache.org/repos/asf/ignite/diff/48d4a925

Branch: refs/heads/ignite-comm-opts2
Commit: 48d4a9252536dd82811a10327b2df6ddbd1ff13a
Parents: 41df266
Author: Alexey Kuznetsov <ak...@apache.org>
Authored: Tue Sep 27 17:03:36 2016 +0700
Committer: Alexey Kuznetsov <ak...@apache.org>
Committed: Tue Sep 27 17:03:36 2016 +0700

----------------------------------------------------------------------
 modules/web-console/DEVNOTES.txt                |  25 +--
 modules/web-console/backend/.babelrc            |   9 -
 modules/web-console/backend/.eslintrc           |   4 +-
 modules/web-console/backend/.gitignore          |   2 -
 modules/web-console/backend/app/agent.js        |  66 ++++++--
 modules/web-console/backend/app/app.js          |   2 +
 modules/web-console/backend/app/browser.js      |  41 ++++-
 modules/web-console/backend/app/index.js        | 116 -------------
 modules/web-console/backend/app/mongo.js        |   7 +-
 modules/web-console/backend/app/mongoose.js     |  29 ++++
 modules/web-console/backend/app/settings.js     |  10 +-
 .../backend/errors/AuthFailedException.js       |   2 +-
 .../backend/errors/DuplicateKeyException.js     |   2 +-
 .../backend/errors/IllegalAccessError.js        |   4 +-
 .../backend/errors/IllegalArgumentException.js  |   2 +-
 .../backend/errors/MissingResourceException.js  |   2 +-
 modules/web-console/backend/errors/index.js     |  14 +-
 modules/web-console/backend/index.js            |  89 +++++++++-
 modules/web-console/backend/injector.js         |   2 +-
 modules/web-console/backend/package.json        |  23 +--
 modules/web-console/backend/routes/demo.js      |   8 +-
 modules/web-console/backend/routes/public.js    |  69 ++------
 modules/web-console/backend/services/auth.js    |  79 ++++++++-
 modules/web-console/backend/services/domains.js |   2 +
 modules/web-console/backend/services/mails.js   |  16 +-
 .../web-console/backend/services/sessions.js    |   4 +-
 modules/web-console/backend/test/app/db.js      |  66 ++++++++
 .../web-console/backend/test/app/httpAgent.js   |  50 ++++++
 .../web-console/backend/test/app/mockgoose.js   |  30 ++++
 .../web-console/backend/test/data/accounts.json |   5 +-
 .../web-console/backend/test/data/caches.json   |  30 ++--
 .../web-console/backend/test/data/clusters.json |  10 +-
 .../web-console/backend/test/data/domains.json  |  10 ++
 .../web-console/backend/test/data/igfss.json    |   4 +-
 .../web-console/backend/test/data/spaces.json   |  14 ++
 modules/web-console/backend/test/index.js       |  35 ++++
 modules/web-console/backend/test/injector.js    |  43 +++--
 .../web-console/backend/test/routes/clusters.js |  83 ++++++++++
 .../web-console/backend/test/routes/public.js   |  68 ++++++++
 .../backend/test/unit/AuthService.test.js       | 107 ++++++++++++
 .../backend/test/unit/CacheService.test.js      |  20 +--
 .../backend/test/unit/ClusterService.test.js    |  19 ++-
 .../backend/test/unit/DomainService.test.js     |  19 ++-
 .../backend/test/unit/IgfsService.test.js       |  19 ++-
 modules/web-console/frontend/app/app.config.js  |   2 +-
 modules/web-console/frontend/app/app.js         |  27 +--
 .../frontend/app/data/jdbc-types.json           |  44 +++++
 .../frontend/app/data/sql-keywords.json         |  41 +++++
 .../frontend/app/decorator/tooltip.js           |  38 +++--
 .../directives/retain-selection.directive.js    |  67 ++++++++
 .../ui-ace-pom/ui-ace-pom.controller.js         |   4 +-
 .../frontend/app/helpers/jade/form.jade         |   1 +
 .../helpers/jade/form/form-field-datalist.jade  |   8 +-
 .../helpers/jade/form/form-field-dropdown.jade  |   8 +-
 .../app/helpers/jade/form/form-field-label.jade |   4 +-
 .../helpers/jade/form/form-field-number.jade    |   8 +-
 .../helpers/jade/form/form-field-password.jade  |  47 ++++++
 .../app/helpers/jade/form/form-field-text.jade  |  10 +-
 .../frontend/app/helpers/jade/mixins.jade       | 101 +++++-------
 .../modules/configuration/Version.service.js    |  25 +++
 .../configuration/configuration.module.js       |   2 +
 .../configuration/generator/Pom.service.js      |  30 ++--
 .../form/validator/java-identifier.directive.js |   4 +-
 .../form/validator/java-keywords.directive.js   |   6 +-
 .../validator/java-package-name.directive.js    |   4 +-
 .../java-package-specified.directive.js         |   7 +-
 .../modules/form/validator/uuid.directive.js    |  12 +-
 .../frontend/app/modules/sql/Notebook.data.js   |  16 +-
 .../states/configuration/caches/memory.jade     |   5 +-
 .../states/configuration/caches/query.jade      |  12 +-
 .../configuration/clusters/cache-key-cfg.jade   |   2 +-
 .../configuration/clusters/communication.jade   |   2 +-
 .../states/configuration/clusters/odbc.jade     |  47 ++++++
 .../states/configuration/domains/general.jade   |   4 +-
 .../states/configuration/domains/query.jade     |  16 +-
 .../configuration/summary/summary.controller.js |  24 ++-
 .../app/modules/version/Version.provider.js     |  32 ----
 .../app/services/ErrorPopover.service.js        |   3 +
 .../frontend/app/services/JavaTypes.service.js  | 163 +++++++++++--------
 .../app/services/LegacyUtils.service.js         |  44 -----
 .../frontend/app/services/SqlTypes.service.js   |  67 ++++++++
 .../frontend/controllers/admin-controller.js    |   7 +-
 .../frontend/controllers/domains-controller.js  |  86 +++++-----
 .../frontend/controllers/profile-controller.js  |   5 +-
 .../frontend/generator/generator-common.js      |  19 ++-
 .../frontend/generator/generator-java.js        |  89 +++++++++-
 .../frontend/generator/generator-properties.js  |   2 +-
 .../frontend/generator/generator-xml.js         |  17 +-
 .../frontend/public/stylesheets/form-field.scss | 108 ++++++++++++
 .../frontend/public/stylesheets/style.scss      |   1 +
 .../frontend/test/unit/JavaTypes.test.js        |  82 +++++++---
 .../frontend/test/unit/SqlTypes.test.js         |  68 ++++++++
 .../frontend/views/configuration/clusters.jade  |   1 +
 .../views/configuration/domains-import.jade     | 116 ++++---------
 94 files changed, 1988 insertions(+), 811 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/ignite/blob/48d4a925/modules/web-console/DEVNOTES.txt
----------------------------------------------------------------------
diff --git a/modules/web-console/DEVNOTES.txt b/modules/web-console/DEVNOTES.txt
index 27211aa..cf2b650 100644
--- a/modules/web-console/DEVNOTES.txt
+++ b/modules/web-console/DEVNOTES.txt
@@ -3,30 +3,21 @@ Ignite Web Console Instructions
 
 How to deploy locally:
 
-1. Install locally MongoDB (version >=3.x) follow instructions from site http://docs.mongodb.org/manual/installation.
-2. Install locally NodeJS (version >=4.x) using installer from site https://nodejs.org for your OS.
-3. Change directory '$IGNITE_HOME/modules/web-console/src/main/js'.
-4. Update npm to 3.x:
-  Linux:
-       npm install -g npm
-  Windows:
-       npm install -g npm-windows-upgrade
-       npm-windows-upgrade
-       See: https://github.com/felixrieseberg/npm-windows-upgrade
-  Check npm version: "npm --version", it should be 3.x.
-5. Change directory to '$IGNITE_HOME/modules/web-console/backend' and
+1. Install locally MongoDB (version >=3.2.x) follow instructions from site http://docs.mongodb.org/manual/installation.
+2. Install locally NodeJS (version >=6.5.x) using installer from site https://nodejs.org/en/download/current for your OS.
+3. Change directory to '$IGNITE_HOME/modules/web-console/backend' and
  run "npm install --no-optional" for download backend dependencies.
-6. Change directory to '$IGNITE_HOME/modules/web-console/frontend' and
+4. Change directory to '$IGNITE_HOME/modules/web-console/frontend' and
  run "npm install --no-optional" for download frontend dependencies.
-7. Build ignite-web-agent module follow instructions from 'modules/web-agent/README.txt'.
-8. Copy ignite-web-agent-<version>.zip from '$IGNITE_HOME/modules/web-console/web-agent/target'
+5. Build ignite-web-agent module follow instructions from 'modules/web-agent/README.txt'.
+6. Copy ignite-web-agent-<version>.zip from '$IGNITE_HOME/modules/web-console/web-agent/target'
  to '$IGNITE_HOME/modules/web-console/backend/agent_dists' folder.
 
-Steps 1 - 8 should be executed once.
+Steps 1 - 6 should be executed once.
 
 How to run console in development mode:
 
-1. Configure MongoDB to run as service or in terminal change dir to $MONGO_INSTALL_DIR/server/3.0/bin
+1. Configure MongoDB to run as service or in terminal change dir to $MONGO_INSTALL_DIR/server/3.2/bin
   and start MongoDB by executing "mongod".
 
 2. In new terminal change directory to '$IGNITE_HOME/modules/web-console/backend'.

http://git-wip-us.apache.org/repos/asf/ignite/blob/48d4a925/modules/web-console/backend/.babelrc
----------------------------------------------------------------------
diff --git a/modules/web-console/backend/.babelrc b/modules/web-console/backend/.babelrc
deleted file mode 100644
index 7eb36f4..0000000
--- a/modules/web-console/backend/.babelrc
+++ /dev/null
@@ -1,9 +0,0 @@
-{
-  "presets": ["es2015", "stage-1"],
-  "plugins": [[
-    "transform-builtin-extend", {
-      "globals": ["Error", "Array"],
-      "approximate": true
-    }
-  ]]
-}

http://git-wip-us.apache.org/repos/asf/ignite/blob/48d4a925/modules/web-console/backend/.eslintrc
----------------------------------------------------------------------
diff --git a/modules/web-console/backend/.eslintrc b/modules/web-console/backend/.eslintrc
index c0c772b..7eb04b7 100644
--- a/modules/web-console/backend/.eslintrc
+++ b/modules/web-console/backend/.eslintrc
@@ -1,5 +1,3 @@
-parser: "babel-eslint"
-
 env:
     es6: true
     node: true
@@ -124,7 +122,7 @@ rules:
     no-path-concat: 0
     no-plusplus: 0
     no-process-env: 0
-    no-process-exit: 1
+    no-process-exit: 0
     no-proto: 2
     no-redeclare: 2
     no-regex-spaces: 1

http://git-wip-us.apache.org/repos/asf/ignite/blob/48d4a925/modules/web-console/backend/.gitignore
----------------------------------------------------------------------
diff --git a/modules/web-console/backend/.gitignore b/modules/web-console/backend/.gitignore
index f95e2bf..1074aff 100644
--- a/modules/web-console/backend/.gitignore
+++ b/modules/web-console/backend/.gitignore
@@ -2,7 +2,5 @@
 *.log
 .npmrc
 node_modules
-serve/config/*.json
-serve/agent_dists/*.zip
 agent_dists/*.zip
 config/*.json

http://git-wip-us.apache.org/repos/asf/ignite/blob/48d4a925/modules/web-console/backend/app/agent.js
----------------------------------------------------------------------
diff --git a/modules/web-console/backend/app/agent.js b/modules/web-console/backend/app/agent.js
index a1858fd..aa06c84 100644
--- a/modules/web-console/backend/app/agent.js
+++ b/modules/web-console/backend/app/agent.js
@@ -24,13 +24,12 @@
  */
 module.exports = {
     implements: 'agent-manager',
-    inject: ['require(lodash)', 'require(ws)', 'require(fs)', 'require(path)', 'require(jszip)', 'require(socket.io)', 'settings', 'mongo']
+    inject: ['require(lodash)', 'require(fs)', 'require(path)', 'require(jszip)', 'require(socket.io)', 'settings', 'mongo']
 };
 
 /**
  * @param _
  * @param fs
- * @param ws
  * @param path
  * @param JSZip
  * @param socketio
@@ -38,7 +37,7 @@ module.exports = {
  * @param mongo
  * @returns {AgentManager}
  */
-module.exports.factory = function(_, ws, fs, path, JSZip, socketio, settings, mongo) {
+module.exports.factory = function(_, fs, path, JSZip, socketio, settings, mongo) {
     /**
      *
      */
@@ -487,6 +486,56 @@ module.exports.factory = function(_, ws, fs, path, JSZip, socketio, settings, mo
 
             return this.executeRest(cmd);
         }
+
+        /**
+         * Collect cache partitions.
+         * @param {Boolean} demo Is need run command on demo node.
+         * @param {Array.<String>} nids Cache node IDs.
+         * @param {String} cacheName Cache name.
+         * @returns {Promise}
+         */
+        partitions(demo, nids, cacheName) {
+            const cmd = new Command(demo, 'exe')
+                .addParam('name', 'org.apache.ignite.internal.visor.compute.VisorGatewayTask')
+                .addParam('p1', nids)
+                .addParam('p2', 'org.apache.ignite.internal.visor.cache.VisorCachePartitionsTask')
+                .addParam('p3', 'java.lang.String')
+                .addParam('p4', cacheName);
+
+            return this.executeRest(cmd);
+        }
+
+        /**
+         * Stops given node IDs.
+         * @param {Boolean} demo Is need run command on demo node.
+         * @param {Array.<String>} nids Nodes IDs.
+         * @returns {Promise}
+         */
+        stopNodes(demo, nids) {
+            const cmd = new Command(demo, 'exe')
+                .addParam('name', 'org.apache.ignite.internal.visor.compute.VisorGatewayTask')
+                .addParam('p1', nids)
+                .addParam('p2', 'org.apache.ignite.internal.visor.node.VisorNodeStopTask')
+                .addParam('p3', 'java.lang.Void');
+
+            return this.executeRest(cmd);
+        }
+
+        /**
+         * Restarts given node IDs.
+         * @param {Boolean} demo Is need run command on demo node.
+         * @param {Array.<String>} nids Nodes IDs.
+         * @returns {Promise}
+         */
+        restartNodes(demo, nids) {
+            const cmd = new Command(demo, 'exe')
+                .addParam('name', 'org.apache.ignite.internal.visor.compute.VisorGatewayTask')
+                .addParam('p1', nids)
+                .addParam('p2', 'org.apache.ignite.internal.visor.node.VisorNodeRestartTask')
+                .addParam('p3', 'java.lang.Void');
+
+            return this.executeRest(cmd);
+        }
     }
 
     /**
@@ -573,17 +622,6 @@ module.exports.factory = function(_, ws, fs, path, JSZip, socketio, settings, mo
                 });
         }
 
-        attachLegacy(server) {
-            const wsSrv = new ws.Server({server});
-
-            wsSrv.on('connection', (_wsClient) => {
-                _wsClient.send(JSON.stringify({
-                    method: 'authResult',
-                    args: ['You are using an older version of the agent. Please reload agent archive']
-                }));
-            });
-        }
-
         /**
          * @param {http.Server|https.Server} srv Server instance that we want to attach agent handler.
          */

http://git-wip-us.apache.org/repos/asf/ignite/blob/48d4a925/modules/web-console/backend/app/app.js
----------------------------------------------------------------------
diff --git a/modules/web-console/backend/app/app.js b/modules/web-console/backend/app/app.js
index 1bbfd2c..eb236e7 100644
--- a/modules/web-console/backend/app/app.js
+++ b/modules/web-console/backend/app/app.js
@@ -56,6 +56,8 @@ module.exports.factory = function(Express, configure, routes) {
             });
 
             srv.addListener('request', app);
+
+            return app;
         }
     };
 };

http://git-wip-us.apache.org/repos/asf/ignite/blob/48d4a925/modules/web-console/backend/app/browser.js
----------------------------------------------------------------------
diff --git a/modules/web-console/backend/app/browser.js b/modules/web-console/backend/app/browser.js
index 3256b6a..d3a673b 100644
--- a/modules/web-console/backend/app/browser.js
+++ b/modules/web-console/backend/app/browser.js
@@ -377,7 +377,7 @@ module.exports.factory = (_, socketio, agentMgr, configure) => {
                         .catch((err) => cb(_errorToJson(err)));
                 });
 
-                // GC node and return result to browser.
+                // Thread dump for node.
                 socket.on('node:thread:dump', (nid, cb) => {
                     agentMgr.findAgent(accountId())
                         .then((agent) => agent.threadDump(demo, nid))
@@ -390,6 +390,45 @@ module.exports.factory = (_, socketio, agentMgr, configure) => {
                         .catch((err) => cb(_errorToJson(err)));
                 });
 
+                // Collect cache partitions.
+                socket.on('node:cache:partitions', (nids, cacheName, cb) => {
+                    agentMgr.findAgent(accountId())
+                        .then((agent) => agent.partitions(demo, nids, cacheName))
+                        .then((data) => {
+                            if (data.finished)
+                                return cb(null, data.result);
+
+                            cb(_errorToJson(data.error));
+                        })
+                        .catch((err) => cb(_errorToJson(err)));
+                });
+
+                // Stops given node IDs
+                socket.on('node:stop', (nids, cb) => {
+                    agentMgr.findAgent(accountId())
+                        .then((agent) => agent.stopNodes(demo, nids))
+                        .then((data) => {
+                            if (data.finished)
+                                return cb(null, data.result);
+
+                            cb(_errorToJson(data.error));
+                        })
+                        .catch((err) => cb(_errorToJson(err)));
+                });
+
+                // Restarts given node IDs.
+                socket.on('node:restart', (nids, cb) => {
+                    agentMgr.findAgent(accountId())
+                        .then((agent) => agent.restartNodes(demo, nids))
+                        .then((data) => {
+                            if (data.finished)
+                                return cb(null, data.result);
+
+                            cb(_errorToJson(data.error));
+                        })
+                        .catch((err) => cb(_errorToJson(err)));
+                });
+
                 const count = agentMgr.addAgentListener(user._id, socket);
 
                 socket.emit('agent:count', {count});

http://git-wip-us.apache.org/repos/asf/ignite/blob/48d4a925/modules/web-console/backend/app/index.js
----------------------------------------------------------------------
diff --git a/modules/web-console/backend/app/index.js b/modules/web-console/backend/app/index.js
deleted file mode 100644
index 5796318..0000000
--- a/modules/web-console/backend/app/index.js
+++ /dev/null
@@ -1,116 +0,0 @@
-/*
- * Licensed to the Apache Software Foundation (ASF) under one or more
- * contributor license agreements.  See the NOTICE file distributed with
- * this work for additional information regarding copyright ownership.
- * The ASF licenses this file to You under the Apache License, Version 2.0
- * (the "License"); you may not use this file except in compliance with
- * the License.  You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-'use strict';
-
-import fs from 'fs';
-import path from 'path';
-import http from 'http';
-import https from 'https';
-
-const igniteModules = process.env.IGNITE_MODULES || './ignite_modules';
-
-let injector;
-
-try {
-    const igniteModulesInjector = path.resolve(path.join(igniteModules, 'backend', 'injector.js'));
-
-    fs.accessSync(igniteModulesInjector, fs.F_OK);
-
-    injector = require(igniteModulesInjector);
-} catch (ignore) {
-    injector = require(path.join(__dirname, '../injector'));
-}
-
-/**
- * Event listener for HTTP server "error" event.
- */
-const _onError = (port, error) => {
-    if (error.syscall !== 'listen')
-        throw error;
-
-    const bind = typeof port === 'string' ? 'Pipe ' + port : 'Port ' + port;
-
-    // Handle specific listen errors with friendly messages.
-    switch (error.code) {
-        case 'EACCES':
-            console.error(bind + ' requires elevated privileges');
-            process.exit(1);
-
-            break;
-        case 'EADDRINUSE':
-            console.error(bind + ' is already in use');
-            process.exit(1);
-
-            break;
-        default:
-            throw error;
-    }
-};
-
-/**
- * Event listener for HTTP server "listening" event.
- */
-const _onListening = (addr) => {
-    const bind = typeof addr === 'string' ? 'pipe ' + addr : 'port ' + addr.port;
-
-    console.log('Start listening on ' + bind);
-};
-
-Promise.all([injector('settings'), injector('app'), injector('agent-manager'), injector('browser-manager')])
-    .then(([settings, app, agentMgr, browserMgr]) => {
-        // Start rest server.
-        const server = settings.server.SSLOptions
-            ? https.createServer(settings.server.SSLOptions) : http.createServer();
-
-        server.listen(settings.server.port);
-        server.on('error', _onError.bind(null, settings.server.port));
-        server.on('listening', _onListening.bind(null, server.address()));
-
-        app.listen(server);
-        browserMgr.attach(server);
-
-        // Start legacy agent server for reject connection with message.
-        if (settings.agent.legacyPort) {
-            const agentLegacySrv = settings.agent.SSLOptions
-                ? https.createServer(settings.agent.SSLOptions) : http.createServer();
-
-            agentLegacySrv.listen(settings.agent.legacyPort);
-            agentLegacySrv.on('error', _onError.bind(null, settings.agent.legacyPort));
-            agentLegacySrv.on('listening', _onListening.bind(null, agentLegacySrv.address()));
-
-            agentMgr.attachLegacy(agentLegacySrv);
-        }
-
-        // Start agent server.
-        const agentServer = settings.agent.SSLOptions
-            ? https.createServer(settings.agent.SSLOptions) : http.createServer();
-
-        agentServer.listen(settings.agent.port);
-        agentServer.on('error', _onError.bind(null, settings.agent.port));
-        agentServer.on('listening', _onListening.bind(null, agentServer.address()));
-
-        agentMgr.attach(agentServer);
-
-        // Used for automated test.
-        if (process.send)
-            process.send('running');
-    }).catch((err) => {
-        console.error(err);
-
-        process.exit(1);
-    });

http://git-wip-us.apache.org/repos/asf/ignite/blob/48d4a925/modules/web-console/backend/app/mongo.js
----------------------------------------------------------------------
diff --git a/modules/web-console/backend/app/mongo.js b/modules/web-console/backend/app/mongo.js
index 7fe39f0..ba7ed09 100644
--- a/modules/web-console/backend/app/mongo.js
+++ b/modules/web-console/backend/app/mongo.js
@@ -24,7 +24,7 @@
  */
 module.exports = {
     implements: 'mongo',
-    inject: ['require(passport-local-mongoose)', 'settings', 'ignite_modules/mongo:*', 'require(mongoose)']
+    inject: ['require(passport-local-mongoose)', 'settings', 'ignite_modules/mongo:*', 'mongoose']
 };
 
 module.exports.factory = function(passportMongo, settings, pluginMongo, mongoose) {
@@ -564,6 +564,11 @@ module.exports.factory = function(passportMongo, settings, pluginMongo, mongoose
             trustManagers: [String]
         },
         rebalanceThreadPoolSize: Number,
+        odbc: {
+            odbcEnabled: Boolean,
+            endpointAddress: String,
+            maxOpenCursors: Number
+        },
         attributes: [{name: String, value: String}],
         collision: {
             kind: {type: String, enum: ['Noop', 'PriorityQueue', 'FifoQueue', 'JobStealing', 'Custom']},

http://git-wip-us.apache.org/repos/asf/ignite/blob/48d4a925/modules/web-console/backend/app/mongoose.js
----------------------------------------------------------------------
diff --git a/modules/web-console/backend/app/mongoose.js b/modules/web-console/backend/app/mongoose.js
new file mode 100644
index 0000000..7b6e7f3
--- /dev/null
+++ b/modules/web-console/backend/app/mongoose.js
@@ -0,0 +1,29 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+'use strict';
+
+// Fire me up!
+
+module.exports = {
+    implements: 'mongoose',
+    inject: ['require(mongoose)']
+};
+
+module.exports.factory = (mongoose) => {
+    return mongoose;
+};

http://git-wip-us.apache.org/repos/asf/ignite/blob/48d4a925/modules/web-console/backend/app/settings.js
----------------------------------------------------------------------
diff --git a/modules/web-console/backend/app/settings.js b/modules/web-console/backend/app/settings.js
index b3609e8..a79572e 100644
--- a/modules/web-console/backend/app/settings.js
+++ b/modules/web-console/backend/app/settings.js
@@ -45,13 +45,14 @@ module.exports.factory = function(nconf, fs) {
         return false;
     };
 
-    const mailConfig = nconf.get('mail') || {};
+    const mail = nconf.get('mail') || {};
+
+    mail.address = (username, email) => username ? '"' + username + '" <' + email + '>' : email;
 
     return {
         agent: {
             dists: 'agent_dists',
             port: _normalizePort(nconf.get('agentServer:port') || 3002),
-            legacyPort: _normalizePort(nconf.get('agentServer:legacyPort')),
             SSLOptions: nconf.get('agentServer:ssl') && {
                 key: fs.readFileSync(nconf.get('agentServer:key')),
                 cert: fs.readFileSync(nconf.get('agentServer:cert')),
@@ -68,10 +69,7 @@ module.exports.factory = function(nconf, fs) {
                 passphrase: nconf.get('server:keyPassphrase')
             }
         },
-        smtp: {
-            ...mailConfig,
-            address: (username, email) => username ? '"' + username + '" <' + email + '>' : email
-        },
+        mail,
         mongoUrl: nconf.get('mongodb:url') || 'mongodb://localhost/console',
         cookieTTL: 3600000 * 24 * 30,
         sessionSecret: nconf.get('server:sessionSecret') || 'keyboard cat',

http://git-wip-us.apache.org/repos/asf/ignite/blob/48d4a925/modules/web-console/backend/errors/AuthFailedException.js
----------------------------------------------------------------------
diff --git a/modules/web-console/backend/errors/AuthFailedException.js b/modules/web-console/backend/errors/AuthFailedException.js
index 3208e1d..2772fad 100644
--- a/modules/web-console/backend/errors/AuthFailedException.js
+++ b/modules/web-console/backend/errors/AuthFailedException.js
@@ -17,7 +17,7 @@
 
 'use strict';
 
-import AppErrorException from './AppErrorException';
+const AppErrorException = require('./AppErrorException');
 
 class AuthFailedException extends AppErrorException {
     constructor(message) {

http://git-wip-us.apache.org/repos/asf/ignite/blob/48d4a925/modules/web-console/backend/errors/DuplicateKeyException.js
----------------------------------------------------------------------
diff --git a/modules/web-console/backend/errors/DuplicateKeyException.js b/modules/web-console/backend/errors/DuplicateKeyException.js
index b228d0c..536d53d 100644
--- a/modules/web-console/backend/errors/DuplicateKeyException.js
+++ b/modules/web-console/backend/errors/DuplicateKeyException.js
@@ -17,7 +17,7 @@
 
 'use strict';
 
-import AppErrorException from './AppErrorException';
+const AppErrorException = require('./AppErrorException');
 
 class DuplicateKeyException extends AppErrorException {
     constructor(message) {

http://git-wip-us.apache.org/repos/asf/ignite/blob/48d4a925/modules/web-console/backend/errors/IllegalAccessError.js
----------------------------------------------------------------------
diff --git a/modules/web-console/backend/errors/IllegalAccessError.js b/modules/web-console/backend/errors/IllegalAccessError.js
index 4fcd2d4..bc07ef8 100644
--- a/modules/web-console/backend/errors/IllegalAccessError.js
+++ b/modules/web-console/backend/errors/IllegalAccessError.js
@@ -17,12 +17,12 @@
 
 'use strict';
 
-import AppErrorException from './AppErrorException';
+const AppErrorException = require('./AppErrorException');
 
 class IllegalAccessError extends AppErrorException {
     constructor(message) {
         super(message);
-        this.httpCode = 401;
+        this.httpCode = 403;
     }
 }
 

http://git-wip-us.apache.org/repos/asf/ignite/blob/48d4a925/modules/web-console/backend/errors/IllegalArgumentException.js
----------------------------------------------------------------------
diff --git a/modules/web-console/backend/errors/IllegalArgumentException.js b/modules/web-console/backend/errors/IllegalArgumentException.js
index 0487d05..41ccd9b 100644
--- a/modules/web-console/backend/errors/IllegalArgumentException.js
+++ b/modules/web-console/backend/errors/IllegalArgumentException.js
@@ -17,7 +17,7 @@
 
 'use strict';
 
-import AppErrorException from './AppErrorException';
+const AppErrorException = require('./AppErrorException');
 
 class IllegalArgumentException extends AppErrorException {
     constructor(message) {

http://git-wip-us.apache.org/repos/asf/ignite/blob/48d4a925/modules/web-console/backend/errors/MissingResourceException.js
----------------------------------------------------------------------
diff --git a/modules/web-console/backend/errors/MissingResourceException.js b/modules/web-console/backend/errors/MissingResourceException.js
index 799775b..bcfb408 100644
--- a/modules/web-console/backend/errors/MissingResourceException.js
+++ b/modules/web-console/backend/errors/MissingResourceException.js
@@ -17,7 +17,7 @@
 
 'use strict';
 
-import AppErrorException from './AppErrorException';
+const AppErrorException = require('./AppErrorException');
 
 class MissingResourceException extends AppErrorException {
     constructor(message) {

http://git-wip-us.apache.org/repos/asf/ignite/blob/48d4a925/modules/web-console/backend/errors/index.js
----------------------------------------------------------------------
diff --git a/modules/web-console/backend/errors/index.js b/modules/web-console/backend/errors/index.js
index 0af5cd5..2fadc12 100644
--- a/modules/web-console/backend/errors/index.js
+++ b/modules/web-console/backend/errors/index.js
@@ -19,17 +19,19 @@
 
 // Fire me up!
 
-import AppErrorException from './AppErrorException';
-import IllegalArgumentException from './IllegalArgumentException';
-import DuplicateKeyException from './DuplicateKeyException';
-import ServerErrorException from './ServerErrorException';
-import MissingResourceException from './MissingResourceException';
-import AuthFailedException from './AuthFailedException';
+const AppErrorException = require('./AppErrorException');
+const IllegalArgumentException = require('./IllegalArgumentException');
+const IllegalAccessError = require('./IllegalAccessError');
+const DuplicateKeyException = require('./DuplicateKeyException');
+const ServerErrorException = require('./ServerErrorException');
+const MissingResourceException = require('./MissingResourceException');
+const AuthFailedException = require('./AuthFailedException');
 
 module.exports = {
     implements: 'errors',
     factory: () => ({
         AppErrorException,
+        IllegalAccessError,
         IllegalArgumentException,
         DuplicateKeyException,
         ServerErrorException,

http://git-wip-us.apache.org/repos/asf/ignite/blob/48d4a925/modules/web-console/backend/index.js
----------------------------------------------------------------------
diff --git a/modules/web-console/backend/index.js b/modules/web-console/backend/index.js
index dcb3f41..3a8ada9 100644
--- a/modules/web-console/backend/index.js
+++ b/modules/web-console/backend/index.js
@@ -15,5 +15,90 @@
  * limitations under the License.
  */
 
-require('babel-core/register');
-require('./app/index.js');
+'use strict';
+
+const fs = require('fs');
+const path = require('path');
+const http = require('http');
+const https = require('https');
+
+const igniteModules = process.env.IGNITE_MODULES || './ignite_modules';
+
+let injector;
+
+try {
+    const igniteModulesInjector = path.resolve(path.join(igniteModules, 'backend', 'injector.js'));
+
+    fs.accessSync(igniteModulesInjector, fs.F_OK);
+
+    injector = require(igniteModulesInjector);
+} catch (ignore) {
+    injector = require(path.join(__dirname, './injector'));
+}
+
+/**
+ * Event listener for HTTP server "error" event.
+ */
+const _onError = (port, error) => {
+    if (error.syscall !== 'listen')
+        throw error;
+
+    const bind = typeof port === 'string' ? 'Pipe ' + port : 'Port ' + port;
+
+    // Handle specific listen errors with friendly messages.
+    switch (error.code) {
+        case 'EACCES':
+            console.error(bind + ' requires elevated privileges');
+            process.exit(1);
+
+            break;
+        case 'EADDRINUSE':
+            console.error(bind + ' is already in use');
+            process.exit(1);
+
+            break;
+        default:
+            throw error;
+    }
+};
+
+/**
+ * Event listener for HTTP server "listening" event.
+ */
+const _onListening = (addr) => {
+    const bind = typeof addr === 'string' ? 'pipe ' + addr : 'port ' + addr.port;
+
+    console.log('Start listening on ' + bind);
+};
+
+Promise.all([injector('settings'), injector('app'), injector('agent-manager'), injector('browser-manager')])
+    .then(([settings, app, agentMgr, browserMgr]) => {
+        // Start rest server.
+        const server = settings.server.SSLOptions
+            ? https.createServer(settings.server.SSLOptions) : http.createServer();
+
+        server.listen(settings.server.port);
+        server.on('error', _onError.bind(null, settings.server.port));
+        server.on('listening', _onListening.bind(null, server.address()));
+
+        app.listen(server);
+        browserMgr.attach(server);
+
+        // Start agent server.
+        const agentServer = settings.agent.SSLOptions
+            ? https.createServer(settings.agent.SSLOptions) : http.createServer();
+
+        agentServer.listen(settings.agent.port);
+        agentServer.on('error', _onError.bind(null, settings.agent.port));
+        agentServer.on('listening', _onListening.bind(null, agentServer.address()));
+
+        agentMgr.attach(agentServer);
+
+        // Used for automated test.
+        if (process.send)
+            process.send('running');
+    }).catch((err) => {
+        console.error(err);
+
+        process.exit(1);
+    });

http://git-wip-us.apache.org/repos/asf/ignite/blob/48d4a925/modules/web-console/backend/injector.js
----------------------------------------------------------------------
diff --git a/modules/web-console/backend/injector.js b/modules/web-console/backend/injector.js
index 62f8980..a5996b3 100644
--- a/modules/web-console/backend/injector.js
+++ b/modules/web-console/backend/injector.js
@@ -15,7 +15,7 @@
  * limitations under the License.
  */
 
-import fireUp from 'fire-up';
+const fireUp = require('fire-up');
 
 module.exports = fireUp.newInjector({
     basePath: __dirname,

http://git-wip-us.apache.org/repos/asf/ignite/blob/48d4a925/modules/web-console/backend/package.json
----------------------------------------------------------------------
diff --git a/modules/web-console/backend/package.json b/modules/web-console/backend/package.json
index 598dc00..d50136f 100644
--- a/modules/web-console/backend/package.json
+++ b/modules/web-console/backend/package.json
@@ -4,8 +4,8 @@
   "description": "Interactive Web console for configuration, executing SQL queries and monitoring of Apache Ignite Cluster",
   "private": true,
   "scripts": {
-    "ci-test": "cross-env NODE_ENV=test CONFIG_PATH='./test/config/settings.json' mocha -u tdd --require babel-core/register --reporter mocha-teamcity-reporter --recursive ./test/unit",
-    "test": "cross-env NODE_ENV=test CONFIG_PATH='./test/config/settings.json' mocha -u tdd --require babel-core/register  --recursive ./test/unit",
+    "ci-test": "cross-env NODE_ENV=test MOCHA_REPORTER=mocha-teamcity-reporter node ./test/index.js",
+    "test": "cross-env NODE_ENV=test CONFIG_PATH='./test/config/settings.json' node ./test/index.js",
     "eslint": "eslint --env node --format node_modules/eslint-friendly-formatter ./ -- --eff-by-issue",
     "start": "node ./index.js"
   },
@@ -21,7 +21,7 @@
   "homepage": "https://ignite.apache.org/",
   "engines": {
     "npm": "^3.x.x",
-    "node": "^4.x.x"
+    "node": "^6.5.x"
   },
   "os": [
     "darwin",
@@ -32,7 +32,6 @@
     "body-parser": "^1.15.0",
     "connect-mongo": "^1.1.0",
     "cookie-parser": "~1.4.0",
-    "es6-promise": "^3.0.2",
     "express": "^4.14.0",
     "express-session": "^1.12.0",
     "fire-up": "^1.0.0",
@@ -47,25 +46,17 @@
     "passport-local": "^1.0.0",
     "passport-local-mongoose": "^4.0.0",
     "passport.socketio": "^3.6.1",
-    "socket.io": "^1.4.5",
-    "ws": "^0.8.0"
+    "socket.io": "^1.4.5"
   },
   "devDependencies": {
-    "babel-core": "^6.7.6",
-    "babel-eslint": "^6.0.4",
-    "babel-plugin-add-module-exports": "^0.2.1",
-    "babel-plugin-transform-builtin-extend": "^1.1.0",
-    "babel-plugin-transform-runtime": "^6.7.5",
-    "babel-polyfill": "^6.7.4",
-    "babel-preset-es2015": "^6.9.0",
-    "babel-preset-stage-1": "^6.5.0",
-    "babel-runtime": "^6.6.1",
     "chai": "^3.5.0",
     "cross-env": "^1.0.7",
     "eslint": "^2.9.0",
     "eslint-friendly-formatter": "^2.0.5",
     "jasmine-core": "^2.4.1",
     "mocha": "~2.5.3",
-    "mocha-teamcity-reporter": "^1.0.0"
+    "mocha-teamcity-reporter": "^1.0.0",
+    "mockgoose": "^6.0.6",
+    "supertest": "^2.0.0"
   }
 }

http://git-wip-us.apache.org/repos/asf/ignite/blob/48d4a925/modules/web-console/backend/routes/demo.js
----------------------------------------------------------------------
diff --git a/modules/web-console/backend/routes/demo.js b/modules/web-console/backend/routes/demo.js
index 724b5c1..ad4be6e 100644
--- a/modules/web-console/backend/routes/demo.js
+++ b/modules/web-console/backend/routes/demo.js
@@ -19,10 +19,10 @@
 
 // Fire me up!
 
-import clusters from './demo/clusters.json';
-import caches from './demo/caches.json';
-import domains from './demo/domains.json';
-import igfss from './demo/igfss.json';
+const clusters = require('./demo/clusters.json');
+const caches = require('./demo/caches.json');
+const domains = require('./demo/domains.json');
+const igfss = require('./demo/igfss.json');
 
 module.exports = {
     implements: 'routes/demo',

http://git-wip-us.apache.org/repos/asf/ignite/blob/48d4a925/modules/web-console/backend/routes/public.js
----------------------------------------------------------------------
diff --git a/modules/web-console/backend/routes/public.js b/modules/web-console/backend/routes/public.js
index 5aad11a..590d395 100644
--- a/modules/web-console/backend/routes/public.js
+++ b/modules/web-console/backend/routes/public.js
@@ -21,35 +21,23 @@
 
 module.exports = {
     implements: 'routes/public',
-    inject: ['require(express)', 'require(passport)', 'settings', 'mongo', 'services/mails', 'services/users']
+    inject: ['require(express)', 'require(passport)', 'mongo', 'services/mails', 'services/users', 'services/auth']
 };
 
 /**
  *
  * @param express
  * @param passport
- * @param settings
  * @param mongo
  * @param mailsService
  * @param {UsersService} usersService
+ * @param {AuthService} authService
  * @returns {Promise}
  */
-module.exports.factory = function(express, passport, settings, mongo, mailsService, usersService) {
+module.exports.factory = function(express, passport, mongo, mailsService, usersService, authService) {
     return new Promise((factoryResolve) => {
         const router = new express.Router();
 
-        const _randomString = () => {
-            const possible = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789';
-            const possibleLen = possible.length;
-
-            let res = '';
-
-            for (let i = 0; i < settings.tokenLength; i++)
-                res += possible.charAt(Math.floor(Math.random() * possibleLen));
-
-            return res;
-        };
-
         // GET user.
         router.post('/user', (req, res) => {
             usersService.get(req.user, req.session.viewedUser)
@@ -107,60 +95,33 @@ module.exports.factory = function(express, passport, settings, mongo, mailsServi
          * Send e-mail to user with reset token.
          */
         router.post('/password/forgot', (req, res) => {
-            mongo.Account.findOne({email: req.body.email}).exec()
-                .then((user) => {
-                    if (!user)
-                        throw new Error('Account with that email address does not exists!');
-
-                    user.resetPasswordToken = _randomString();
-
-                    return user.save();
-                })
+            authService.resetPasswordToken(req.body.email)
                 .then((user) => mailsService.emailUserResetLink(req.origin(), user))
-                .then(() => res.status(200).send('An email has been sent with further instructions.'))
-                .catch((err) => {
-                    // TODO IGNITE-843 Send email to admin
-                    return res.status(401).send(err.message);
-                });
+                .then(() => 'An email has been sent with further instructions.')
+                .then(res.api.ok)
+                .catch(res.api.error);
         });
 
         /**
          * Change password with given token.
          */
         router.post('/password/reset', (req, res) => {
-            mongo.Account.findOne({resetPasswordToken: req.body.token}).exec()
-                .then((user) => {
-                    if (!user)
-                        throw new Error('Failed to find account with this token! Please check link from email.');
-
-                    return new Promise((resolve, reject) => {
-                        user.setPassword(req.body.password, (err, _user) => {
-                            if (err)
-                                return reject(new Error('Failed to reset password: ' + err.message));
+            const {token, password} = req.body;
 
-                            _user.resetPasswordToken = undefined; // eslint-disable-line no-undefined
-
-                            resolve(_user.save());
-                        });
-                    });
-                })
+            authService.resetPasswordByToken(token, password)
                 .then((user) => mailsService.emailPasswordChanged(req.origin(), user))
-                .then((user) => res.status(200).send(user.email))
-                .catch((err) => res.status(401).send(err.message));
+                .then((user) => user.email)
+                .then(res.api.ok)
+                .then(res.api.error);
         });
 
         /* GET reset password page. */
         router.post('/password/validate/token', (req, res) => {
             const token = req.body.token;
 
-            mongo.Account.findOne({resetPasswordToken: token}).exec()
-                .then((user) => {
-                    if (!user)
-                        throw new Error('Invalid token for password reset!');
-
-                    return res.json({token, email: user.email});
-                })
-                .catch((err) => res.status(401).send(err.message));
+            authService.validateResetToken(token)
+                .then(res.api.ok)
+                .catch(res.api.error);
         });
 
         factoryResolve(router);

http://git-wip-us.apache.org/repos/asf/ignite/blob/48d4a925/modules/web-console/backend/services/auth.js
----------------------------------------------------------------------
diff --git a/modules/web-console/backend/services/auth.js b/modules/web-console/backend/services/auth.js
index 9f7d77d..67a3f2f 100644
--- a/modules/web-console/backend/services/auth.js
+++ b/modules/web-console/backend/services/auth.js
@@ -21,25 +21,92 @@
 
 module.exports = {
     implements: 'services/auth',
-    inject: ['require(lodash)', 'mongo', 'services/spaces', 'errors']
+    inject: ['require(lodash)', 'mongo', 'settings', 'errors']
 };
 
 /**
  * @param _
  * @param mongo
- * @param {SpacesService} spacesService
+ * @param settings
  * @param errors
  * @returns {AuthService}
  */
-module.exports.factory = (_, mongo, spacesService, errors) => {
+
+module.exports.factory = (_, mongo, settings, errors) => {
     class AuthService {
-        // TODO IGNITE-3774: move implementation from public router.
-        static resetPassword() {
+        /**
+         * Generate token string.
+         * @param length - length of string
+         * @returns {string} - generated token
+         */
+        static generateResetToken(length) {
+            length = length || settings.tokenLength;
+            const possible = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789';
+            const possibleLen = possible.length;
+
+            let res = '';
+
+            for (let i = 0; i < length; i++)
+                res += possible.charAt(Math.floor(Math.random() * possibleLen));
+
+            return res;
+        }
+
+        /**
+         * Reset password reset token for user
+         * @param email - user email
+         * @returns {Promise.<mongo.Account>} - that resolves account found by email with new reset password token.
+         */
+        static resetPasswordToken(email) {
+            return mongo.Account.findOne({email}).exec()
+                .then((user) => {
+                    if (!user)
+                        throw new errors.MissingResourceException('Account with that email address does not exists!');
+
+                    user.resetPasswordToken = AuthService.generateResetToken(settings.tokenLength);
+
+                    return user.save();
+                });
+        }
+
+        /**
+         * Reset password by reset token.
+         * @param {string} token - reset token
+         * @param {string} newPassword - new password
+         * @returns {Promise.<mongo.Account>} - that resolves account with new password
+         */
+        static resetPasswordByToken(token, newPassword) {
+            return mongo.Account.findOne({resetPasswordToken: token}).exec()
+                .then((user) => {
+                    if (!user)
+                        throw new errors.MissingResourceException('Failed to find account with this token! Please check link from email.');
+
+                    return new Promise((resolve, reject) => {
+                        user.setPassword(newPassword, (err, _user) => {
+                            if (err)
+                                return reject(new errors.AppErrorException('Failed to reset password: ' + err.message));
+
+                            _user.resetPasswordToken = undefined; // eslint-disable-line no-undefined
 
+                            resolve(_user.save());
+                        });
+                    });
+                });
         }
 
-        static validateResetToken() {
+        /**
+         * Find account by token
+         * @param {string} token - reset token
+         * @returns {Promise.<{token, email}>} - that resolves token and user email
+         */
+        static validateResetToken(token) {
+            return mongo.Account.findOne({resetPasswordToken: token}).exec()
+                .then((user) => {
+                    if (!user)
+                        throw new errors.IllegalAccessError('Invalid token for password reset!');
 
+                    return {token, email: user.email};
+                });
         }
     }
 

http://git-wip-us.apache.org/repos/asf/ignite/blob/48d4a925/modules/web-console/backend/services/domains.js
----------------------------------------------------------------------
diff --git a/modules/web-console/backend/services/domains.js b/modules/web-console/backend/services/domains.js
index 3e4e129..791e229 100644
--- a/modules/web-console/backend/services/domains.js
+++ b/modules/web-console/backend/services/domains.js
@@ -117,6 +117,8 @@ module.exports.factory = (_, mongo, spacesService, cachesService, errors) => {
                     .then((cache) => {
                         domain.caches = [cache._id];
 
+                        generatedCaches.push(cache);
+
                         return _saveDomainModel(domain, savedDomains);
                     });
             }

http://git-wip-us.apache.org/repos/asf/ignite/blob/48d4a925/modules/web-console/backend/services/mails.js
----------------------------------------------------------------------
diff --git a/modules/web-console/backend/services/mails.js b/modules/web-console/backend/services/mails.js
index 0700985..3c22a95 100644
--- a/modules/web-console/backend/services/mails.js
+++ b/modules/web-console/backend/services/mails.js
@@ -43,18 +43,18 @@ module.exports.factory = (_, nodemailer, settings) => {
      */
     const send = (user, subject, html, sendErr) => {
         return new Promise((resolve, reject) => {
-            const transportConfig = settings.smtp;
+            const transportConfig = settings.mail;
 
             if (_.isEmpty(transportConfig.service) || _.isEmpty(transportConfig.auth.user) || _.isEmpty(transportConfig.auth.pass))
                 throw new Error('Failed to send email. SMTP server is not configured. Please ask webmaster to setup SMTP server!');
 
             const mailer = nodemailer.createTransport(transportConfig);
 
-            const sign = settings.smtp.sign ? `<br><br>--------------<br>${settings.smtp.sign}<br>` : '';
+            const sign = settings.mail.sign ? `<br><br>--------------<br>${settings.mail.sign}<br>` : '';
 
             const mail = {
-                from: settings.smtp.from,
-                to: settings.smtp.address(`${user.firstName} ${user.lastName}`, user.email),
+                from: settings.mail.from,
+                to: settings.mail.address(`${user.firstName} ${user.lastName}`, user.email),
                 subject,
                 html: html + sign
             };
@@ -77,9 +77,9 @@ module.exports.factory = (_, nodemailer, settings) => {
         static emailUserSignUp(host, user) {
             const resetLink = `${host}/password/reset?token=${user.resetPasswordToken}`;
 
-            return send(user, `Thanks for signing up for ${settings.smtp.greeting}.`,
+            return send(user, `Thanks for signing up for ${settings.mail.greeting}.`,
                 `Hello ${user.firstName} ${user.lastName}!<br><br>` +
-                `You are receiving this email because you have signed up to use <a href="${host}">${settings.smtp.greeting}</a>.<br><br>` +
+                `You are receiving this email because you have signed up to use <a href="${host}">${settings.mail.greeting}</a>.<br><br>` +
                 'If you have not done the sign up and do not know what this email is about, please ignore it.<br>' +
                 'You may reset the password by clicking on the following link, or paste this into your browser:<br><br>' +
                 `<a href="${resetLink}">${resetLink}</a>`);
@@ -110,7 +110,7 @@ module.exports.factory = (_, nodemailer, settings) => {
         static emailPasswordChanged(host, user) {
             return send(user, 'Your password has been changed',
                 `Hello ${user.firstName} ${user.lastName}!<br><br>` +
-                `This is a confirmation that the password for your account on <a href="${host}">${settings.smtp.greeting}</a> has just been changed.<br><br>`,
+                `This is a confirmation that the password for your account on <a href="${host}">${settings.mail.greeting}</a> has just been changed.<br><br>`,
                 'Password was changed, but failed to send confirmation email!');
         }
 
@@ -122,7 +122,7 @@ module.exports.factory = (_, nodemailer, settings) => {
         static emailUserDeletion(host, user) {
             return send(user, 'Your account was removed',
                 `Hello ${user.firstName} ${user.lastName}!<br><br>` +
-                `You are receiving this email because your account for <a href="${host}">${settings.smtp.greeting}</a> was removed.`,
+                `You are receiving this email because your account for <a href="${host}">${settings.mail.greeting}</a> was removed.`,
                 'Account was removed, but failed to send email notification to user!');
         }
     }

http://git-wip-us.apache.org/repos/asf/ignite/blob/48d4a925/modules/web-console/backend/services/sessions.js
----------------------------------------------------------------------
diff --git a/modules/web-console/backend/services/sessions.js b/modules/web-console/backend/services/sessions.js
index 4fa95a3..ff0e303 100644
--- a/modules/web-console/backend/services/sessions.js
+++ b/modules/web-console/backend/services/sessions.js
@@ -38,11 +38,13 @@ module.exports.factory = (_, mongo, errors) => {
          * @param {mongo.ObjectId|String} viewedUserId - id of user to become.
          */
         static become(session, viewedUserId) {
-            return mongo.Account.findById(viewedUserId).exec()
+            return mongo.Account.findById(viewedUserId).lean().exec()
                 .then((viewedUser) => {
                     if (!session.req.user.admin)
                         throw new errors.IllegalAccessError('Became this user is not permitted. Only administrators can perform this actions.');
 
+                    viewedUser.token = session.req.user.token;
+
                     session.viewedUser = viewedUser;
                 });
         }

http://git-wip-us.apache.org/repos/asf/ignite/blob/48d4a925/modules/web-console/backend/test/app/db.js
----------------------------------------------------------------------
diff --git a/modules/web-console/backend/test/app/db.js b/modules/web-console/backend/test/app/db.js
new file mode 100644
index 0000000..e07f887
--- /dev/null
+++ b/modules/web-console/backend/test/app/db.js
@@ -0,0 +1,66 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+'use strict';
+
+// Fire me up!
+
+const testAccounts = require('../data/accounts.json');
+const testClusters = require('../data/clusters.json');
+const testCaches = require('../data/caches.json');
+const testDomains = require('../data/domains.json');
+const testIgfss = require('../data/igfss.json');
+const testSpaces = require('../data/spaces.json');
+
+module.exports = {
+    implements: 'dbHelper',
+    inject: ['require(lodash)', 'mongo', 'mongoose']
+};
+
+module.exports.factory = (_, mongo, mongoose) => {
+    const prepareUserSpaces = () => Promise.all([mongo.Account.create(testAccounts), mongo.Space.create(testSpaces)]);
+    const prepareClusters = () => mongo.Cluster.create(testClusters);
+    const prepareDomains = () => mongo.DomainModel.create(testDomains);
+    const prepareCaches = () => mongo.Cache.create(testCaches);
+    const prepareIgfss = () => mongo.Igfs.create(testIgfss);
+
+    const drop = () => {
+        return Promise.all(_.map(mongoose.connection.collections, (collection) => collection.remove()));
+    };
+
+    const init = () => {
+        return drop()
+            .then(prepareUserSpaces)
+            .then(prepareClusters)
+            .then(prepareDomains)
+            .then(prepareCaches)
+            .then(prepareIgfss);
+    };
+
+    return {
+        drop,
+        init,
+        mocks: {
+            accounts: testAccounts,
+            clusters: testClusters,
+            caches: testCaches,
+            domains: testDomains,
+            igfss: testIgfss,
+            spaces: testSpaces
+        }
+    };
+};

http://git-wip-us.apache.org/repos/asf/ignite/blob/48d4a925/modules/web-console/backend/test/app/httpAgent.js
----------------------------------------------------------------------
diff --git a/modules/web-console/backend/test/app/httpAgent.js b/modules/web-console/backend/test/app/httpAgent.js
new file mode 100644
index 0000000..1394dc5
--- /dev/null
+++ b/modules/web-console/backend/test/app/httpAgent.js
@@ -0,0 +1,50 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+'use strict';
+
+// Fire me up!
+
+module.exports = {
+    implements: 'agentFactory',
+    inject: ['app', 'require(http)', 'require(supertest)']
+};
+
+module.exports.factory = (app, http, request) => {
+    const express = app.listen(http.createServer());
+    let authAgentInstance = null;
+
+    return {
+        authAgent: ({email, password}) => {
+            if (authAgentInstance)
+                return Promise.resolve(authAgentInstance);
+
+            return new Promise((resolve, reject) => {
+                authAgentInstance = request.agent(express);
+                authAgentInstance.post('/signin')
+                    .send({email, password})
+                    .end((err, res) => {
+                        if (res.status === 401 || err)
+                            return reject(err);
+
+                        resolve(authAgentInstance);
+                    });
+            });
+        },
+        guestAgent: () => Promise.resolve(request.agent(express))
+    };
+};

http://git-wip-us.apache.org/repos/asf/ignite/blob/48d4a925/modules/web-console/backend/test/app/mockgoose.js
----------------------------------------------------------------------
diff --git a/modules/web-console/backend/test/app/mockgoose.js b/modules/web-console/backend/test/app/mockgoose.js
new file mode 100644
index 0000000..4944f90
--- /dev/null
+++ b/modules/web-console/backend/test/app/mockgoose.js
@@ -0,0 +1,30 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+'use strict';
+
+// Fire me up!
+
+module.exports = {
+    implements: 'mongoose:mock',
+    inject: ['require(mongoose)', 'require(mockgoose)']
+};
+
+module.exports.factory = (mongoose, mockgoose) => {
+    return mockgoose(mongoose)
+            .then(() => mongoose);
+};

http://git-wip-us.apache.org/repos/asf/ignite/blob/48d4a925/modules/web-console/backend/test/data/accounts.json
----------------------------------------------------------------------
diff --git a/modules/web-console/backend/test/data/accounts.json b/modules/web-console/backend/test/data/accounts.json
index e5b7f98..9dcb0ed 100644
--- a/modules/web-console/backend/test/data/accounts.json
+++ b/modules/web-console/backend/test/data/accounts.json
@@ -1,8 +1,9 @@
 [
   {
-    "_id" : "57725443e6d604c05dab9ded",
+    "_id" : "000000000000000000000001",
     "salt" : "ca8b49c2eacd498a0973de30c0873c166ed99fa0605981726aedcc85bee17832",
     "hash" : "c052c87e454cd0875332719e1ce085ccd92bedb73c8f939ba45d387f724da97128280643ad4f841d929d48de802f48f4a27b909d2dc806d957d38a1a4049468ce817490038f00ac1416aaf9f8f5a5c476730b46ea22d678421cd269869d4ba9d194f73906e5d5a4fec5229459e20ebda997fb95298067126f6c15346d886d44b67def03bf3ffe484b2e4fa449985de33a0c12e4e1da4c7d71fe7af5d138433f703d8c7eeebbb3d57f1a89659010a1f1d3cd4fbc524abab07860daabb08f08a28b8bfc64ecde2ea3c103030d0d54fc24d9c02f92ee6b3aa1bcd5c70113ab9a8045faea7dd2dc59ec4f9f69fcf634232721e9fb44012f0e8c8fdf7c6bf642db6867ef8e7877123e1bc78af7604fee2e34ad0191f8b97613ea458e0fca024226b7055e08a4bdb256fabf0a203a1e5b6a6c298fb0c60308569cefba779ce1e41fb971e5d1745959caf524ab0bedafce67157922f9c505cea033f6ed28204791470d9d08d31ce7e8003df8a3a05282d4d60bfe6e2f7de06f4b18377dac0fe764ed683c9b2553e75f8280c748aa166fef6f89190b1c6d369ab86422032171e6f9686de42ac65708e63bf018a043601d85bc5c820c7ad1d51ded32e59cdaa629a3f7ae325bbc931f9f21d90c9204effdbd53721a60c8b180dd8c236133e287a47ccc9e5072eb6593771e435e4d5196
 d50d6ddb32c226651c6503387895c5ad025f69fd3",
+    "password": "a",
     "email" : "a@a",
     "firstName" : "TestFirstName",
     "lastName" : "TestLastName",
@@ -15,4 +16,4 @@
     "__v" : 0,
     "resetPasswordToken" : "892rnLbEnVp1FP75Jgpi"
   }
-]
\ No newline at end of file
+]

http://git-wip-us.apache.org/repos/asf/ignite/blob/48d4a925/modules/web-console/backend/test/data/caches.json
----------------------------------------------------------------------
diff --git a/modules/web-console/backend/test/data/caches.json b/modules/web-console/backend/test/data/caches.json
index f7a8690..697d414 100644
--- a/modules/web-console/backend/test/data/caches.json
+++ b/modules/web-console/backend/test/data/caches.json
@@ -1,5 +1,7 @@
 [
   {
+    "_id" : "000000000000000000000001",
+    "space": "000000000000000000000001",
     "name": "CarCache",
     "cacheMode": "PARTITIONED",
     "atomicityMode": "ATOMIC",
@@ -13,10 +15,12 @@
         "dialect": "H2"
       }
     },
-    "domains": [],
-    "clusters": []
+    "domains": ["000000000000000000000001", "000000000000000000000002", "000000000000000000000003", "000000000000000000000004", "000000000000000000000005"],
+    "clusters": ["000000000000000000000001", "000000000000000000000002"]
   },
   {
+    "_id" : "000000000000000000000002",
+    "space": "000000000000000000000001",
     "name": "ParkingCache",
     "cacheMode": "PARTITIONED",
     "atomicityMode": "ATOMIC",
@@ -30,10 +34,12 @@
         "dialect": "H2"
       }
     },
-    "domains": [],
-    "clusters": []
+    "domains": ["000000000000000000000001", "000000000000000000000002", "000000000000000000000003", "000000000000000000000004", "000000000000000000000005"],
+    "clusters": ["000000000000000000000001", "000000000000000000000002"]
   },
   {
+    "_id" : "000000000000000000000003",
+    "space": "000000000000000000000001",
     "name": "CountryCache",
     "cacheMode": "PARTITIONED",
     "atomicityMode": "ATOMIC",
@@ -47,10 +53,12 @@
         "dialect": "H2"
       }
     },
-    "domains": [],
-    "clusters": []
+    "domains": ["000000000000000000000001", "000000000000000000000002", "000000000000000000000003", "000000000000000000000004", "000000000000000000000005"],
+    "clusters": ["000000000000000000000002"]
   },
   {
+    "_id" : "000000000000000000000004",
+    "space": "000000000000000000000001",
     "name": "DepartmentCache",
     "cacheMode": "PARTITIONED",
     "atomicityMode": "ATOMIC",
@@ -64,10 +72,12 @@
         "dialect": "H2"
       }
     },
-    "domains": [],
-    "clusters": []
+    "domains": ["000000000000000000000001", "000000000000000000000002", "000000000000000000000003", "000000000000000000000004", "000000000000000000000005"],
+    "clusters": ["000000000000000000000002"]
   },
   {
+    "_id" : "000000000000000000000005",
+    "space": "000000000000000000000001",
     "name": "EmployeeCache",
     "cacheMode": "PARTITIONED",
     "atomicityMode": "ATOMIC",
@@ -81,7 +91,7 @@
         "dialect": "H2"
       }
     },
-    "domains": [],
-    "clusters": []
+    "domains": ["000000000000000000000001", "000000000000000000000002", "000000000000000000000003", "000000000000000000000004", "000000000000000000000005"],
+    "clusters": ["000000000000000000000002"]
   }
 ]

http://git-wip-us.apache.org/repos/asf/ignite/blob/48d4a925/modules/web-console/backend/test/data/clusters.json
----------------------------------------------------------------------
diff --git a/modules/web-console/backend/test/data/clusters.json b/modules/web-console/backend/test/data/clusters.json
index 014b519..8e16e76 100644
--- a/modules/web-console/backend/test/data/clusters.json
+++ b/modules/web-console/backend/test/data/clusters.json
@@ -1,5 +1,7 @@
 [
   {
+    "_id" : "000000000000000000000001",
+    "space": "000000000000000000000001",
     "name": "cluster-igfs",
     "connector": {
       "noDelay": true
@@ -7,8 +9,8 @@
     "communication": {
       "tcpNoDelay": true
     },
-    "igfss": [],
-    "caches": [],
+    "igfss": ["000000000000000000000001"],
+    "caches": ["000000000000000000000001", "000000000000000000000002"],
     "binaryConfiguration": {
       "compactFooter": true,
       "typeConfigurations": []
@@ -24,6 +26,8 @@
     }
   },
   {
+    "_id" : "000000000000000000000002",
+    "space": "000000000000000000000001",
     "name": "cluster-caches",
     "connector": {
       "noDelay": true
@@ -32,7 +36,7 @@
       "tcpNoDelay": true
     },
     "igfss": [],
-    "caches": [],
+    "caches": ["000000000000000000000001", "000000000000000000000002", "000000000000000000000003", "000000000000000000000004", "000000000000000000000005"],
     "binaryConfiguration": {
       "compactFooter": true,
       "typeConfigurations": []

http://git-wip-us.apache.org/repos/asf/ignite/blob/48d4a925/modules/web-console/backend/test/data/domains.json
----------------------------------------------------------------------
diff --git a/modules/web-console/backend/test/data/domains.json b/modules/web-console/backend/test/data/domains.json
index 980d8d1..e2662db 100644
--- a/modules/web-console/backend/test/data/domains.json
+++ b/modules/web-console/backend/test/data/domains.json
@@ -1,5 +1,7 @@
 [
   {
+    "_id" : "000000000000000000000001",
+    "space": "000000000000000000000001",
     "keyType": "Integer",
     "valueType": "model.Parking",
     "queryMetadata": "Configuration",
@@ -42,6 +44,8 @@
     "caches": []
   },
   {
+    "_id" : "000000000000000000000002",
+    "space": "000000000000000000000001",
     "keyType": "Integer",
     "valueType": "model.Department",
     "queryMetadata": "Configuration",
@@ -84,6 +88,8 @@
     "caches": []
   },
   {
+    "_id" : "000000000000000000000003",
+    "space": "000000000000000000000001",
     "keyType": "Integer",
     "valueType": "model.Employee",
     "queryMetadata": "Configuration",
@@ -221,6 +227,8 @@
     "caches": []
   },
   {
+    "_id" : "000000000000000000000004",
+    "space": "000000000000000000000001",
     "keyType": "Integer",
     "valueType": "model.Country",
     "queryMetadata": "Configuration",
@@ -263,6 +271,8 @@
     "caches": []
   },
   {
+    "_id" : "000000000000000000000005",
+    "space": "000000000000000000000001",
     "keyType": "Integer",
     "valueType": "model.Car",
     "queryMetadata": "Configuration",

http://git-wip-us.apache.org/repos/asf/ignite/blob/48d4a925/modules/web-console/backend/test/data/igfss.json
----------------------------------------------------------------------
diff --git a/modules/web-console/backend/test/data/igfss.json b/modules/web-console/backend/test/data/igfss.json
index cd128a6..c1f0645 100644
--- a/modules/web-console/backend/test/data/igfss.json
+++ b/modules/web-console/backend/test/data/igfss.json
@@ -1,10 +1,12 @@
 [
   {
+    "_id" : "000000000000000000000001",
+    "space": "000000000000000000000001",
     "ipcEndpointEnabled": true,
     "fragmentizerEnabled": true,
     "name": "igfs",
     "dataCacheName": "igfs-data",
     "metaCacheName": "igfs-meta",
-    "clusters": []
+    "clusters": ["000000000000000000000001"]
   }
 ]

http://git-wip-us.apache.org/repos/asf/ignite/blob/48d4a925/modules/web-console/backend/test/data/spaces.json
----------------------------------------------------------------------
diff --git a/modules/web-console/backend/test/data/spaces.json b/modules/web-console/backend/test/data/spaces.json
new file mode 100644
index 0000000..519f7fe
--- /dev/null
+++ b/modules/web-console/backend/test/data/spaces.json
@@ -0,0 +1,14 @@
+[
+  {
+    "_id": "000000000000000000000001",
+    "name": "Personal space",
+    "owner": "000000000000000000000001",
+    "demo": false
+  },
+  {
+    "_id": "000000000000000000000002",
+    "name": "Demo space",
+    "owner": "000000000000000000000001",
+    "demo": true
+  }
+]

http://git-wip-us.apache.org/repos/asf/ignite/blob/48d4a925/modules/web-console/backend/test/index.js
----------------------------------------------------------------------
diff --git a/modules/web-console/backend/test/index.js b/modules/web-console/backend/test/index.js
new file mode 100644
index 0000000..4519219
--- /dev/null
+++ b/modules/web-console/backend/test/index.js
@@ -0,0 +1,35 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+const Mocha = require('mocha');
+const glob = require('glob');
+const path = require('path');
+
+const mocha = new Mocha({ui: 'tdd', reporter: process.env.MOCHA_REPORTER || 'spec'});
+const testPath = ['./test/unit/**/*.js', './test/routes/**/*.js'];
+
+if (process.env.IGNITE_MODULES)
+    testPath.push(path.join(process.env.IGNITE_MODULES, 'backend', 'test', 'unit', '**', '*.js'));
+
+testPath
+    .map((mask) => glob.sync(mask))
+    .reduce((acc, items) => acc.concat(items), [])
+    .map(mocha.addFile.bind(mocha));
+
+const runner = mocha.run();
+
+runner.on('end', (failures) => process.exit(failures));

http://git-wip-us.apache.org/repos/asf/ignite/blob/48d4a925/modules/web-console/backend/test/injector.js
----------------------------------------------------------------------
diff --git a/modules/web-console/backend/test/injector.js b/modules/web-console/backend/test/injector.js
index 8d44d31..bdeca91 100644
--- a/modules/web-console/backend/test/injector.js
+++ b/modules/web-console/backend/test/injector.js
@@ -15,17 +15,34 @@
  * limitations under the License.
  */
 
-import path from 'path';
-import fireUp from 'fire-up';
+const fs = require('fs');
+const path = require('path');
+const fireUp = require('fire-up');
 
-module.exports = fireUp.newInjector({
-    basePath: path.join(__dirname, '../'),
-    modules: [
-        './app/**/*.js',
-        './config/**/*.js',
-        './errors/**/*.js',
-        './middlewares/**/*.js',
-        './routes/**/*.js',
-        './services/**/*.js'
-    ]
-});
+const igniteModules = process.env.IGNITE_MODULES || './ignite_modules';
+
+let injector;
+
+try {
+    const igniteModulesInjector = path.resolve(path.join(igniteModules, 'backend', 'test', 'injector.js'));
+
+    fs.accessSync(igniteModulesInjector, fs.F_OK);
+
+    injector = require(igniteModulesInjector);
+} catch (ignore) {
+    injector = fireUp.newInjector({
+        basePath: path.join(__dirname, '../'),
+        modules: [
+            './app/**/*.js',
+            './config/**/*.js',
+            './errors/**/*.js',
+            './middlewares/**/*.js',
+            './routes/**/*.js',
+            './services/**/*.js',
+            './test/app/*.js'
+        ],
+        use: ['mongoose:mock']
+    });
+}
+
+module.exports = injector;

http://git-wip-us.apache.org/repos/asf/ignite/blob/48d4a925/modules/web-console/backend/test/routes/clusters.js
----------------------------------------------------------------------
diff --git a/modules/web-console/backend/test/routes/clusters.js b/modules/web-console/backend/test/routes/clusters.js
new file mode 100644
index 0000000..5dd7a60
--- /dev/null
+++ b/modules/web-console/backend/test/routes/clusters.js
@@ -0,0 +1,83 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+const assert = require('chai').assert;
+const injector = require('../injector');
+const mongoose = require('mongoose');
+
+let agentFactory;
+let db;
+
+suite('routes.clusters', () => {
+    suiteSetup(() => {
+        return Promise.all([injector('agentFactory'), injector('dbHelper')])
+            .then(([_agent, _db]) => {
+                agentFactory = _agent;
+                db = _db;
+            });
+    });
+
+    setup(() => {
+        return db.init();
+    });
+
+    test('Save cluster model', (done) => {
+        const newCluster = Object.assign({}, db.mocks.clusters[0], {name: 'newClusterName'});
+
+        agentFactory.authAgent(db.mocks.accounts[0])
+            .then((agent) => {
+                agent.post('/configuration/clusters/save')
+                    .send(newCluster)
+                    .expect(200)
+                    .expect((res) => {
+                        assert.isNotNull(res.body);
+                        assert.isTrue(mongoose.Types.ObjectId.isValid(res.body));
+                    })
+                    .end(done);
+            })
+            .catch(done);
+    });
+
+    test('Remove cluster model', (done) => {
+        return agentFactory.authAgent(db.mocks.accounts[0])
+            .then((agent) => {
+                agent.post('/configuration/clusters/remove')
+                    .send({_id: db.mocks.clusters[0]._id})
+                    .expect(200)
+                    .expect((res) => {
+                        assert.isNotNull(res.body);
+                        assert.equal(res.body.rowsAffected, 1);
+                    })
+                    .end(done);
+            })
+            .catch(done);
+    });
+
+    test('Remove all clusters', (done) => {
+        return agentFactory.authAgent(db.mocks.accounts[0])
+            .then((agent) => {
+                agent.post('/configuration/clusters/remove/all')
+                    .expect(200)
+                    .expect((res) => {
+                        assert.isNotNull(res.body);
+                        assert.equal(res.body.rowsAffected, db.mocks.clusters.length);
+                    })
+                    .end(done);
+            })
+            .catch(done);
+    });
+});

http://git-wip-us.apache.org/repos/asf/ignite/blob/48d4a925/modules/web-console/backend/test/routes/public.js
----------------------------------------------------------------------
diff --git a/modules/web-console/backend/test/routes/public.js b/modules/web-console/backend/test/routes/public.js
new file mode 100644
index 0000000..3c573c5
--- /dev/null
+++ b/modules/web-console/backend/test/routes/public.js
@@ -0,0 +1,68 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+const assert = require('chai').assert;
+const injector = require('../injector');
+
+const testAccounts = require('../data/accounts.json');
+
+let agentFactory;
+let db;
+
+suite('routes.public', () => {
+    suiteSetup(() => {
+        return Promise.all([injector('agentFactory'), injector('dbHelper')])
+            .then(([_agent, _db]) => {
+                agentFactory = _agent;
+                db = _db;
+            });
+    });
+
+    setup(() => {
+        return db.init();
+    });
+
+    test('Login success', (done) => {
+        const user = testAccounts[0];
+
+        agentFactory.guestAgent()
+            .then((agent) => {
+                agent.post('/signin')
+                    .send({email: user.email, password: user.password})
+                    .expect(200)
+                    .expect((res) => {
+                        assert.isNotNull(res.headers['set-cookie']);
+                        assert.match(res.headers['set-cookie'], /connect\.sid/);
+                    })
+                    .end(done);
+            })
+            .catch(done);
+    });
+
+    test('Login fail', (done) => {
+        const user = testAccounts[0];
+
+        agentFactory.guestAgent()
+            .then((agent) => {
+                agent.post('/signin')
+                    .send({email: user.email, password: 'notvalidpassword'})
+                    .expect(401)
+                    .end(done);
+            })
+            .catch(done);
+    });
+});

http://git-wip-us.apache.org/repos/asf/ignite/blob/48d4a925/modules/web-console/backend/test/unit/AuthService.test.js
----------------------------------------------------------------------
diff --git a/modules/web-console/backend/test/unit/AuthService.test.js b/modules/web-console/backend/test/unit/AuthService.test.js
new file mode 100644
index 0000000..eec60c4
--- /dev/null
+++ b/modules/web-console/backend/test/unit/AuthService.test.js
@@ -0,0 +1,107 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+const assert = require('chai').assert;
+const injector = require('../injector');
+const testAccounts = require('../data/accounts.json');
+
+let authService;
+let errors;
+let db;
+
+suite('AuthServiceTestsSuite', () => {
+    suiteSetup(() => {
+        return Promise.all([injector('services/auth'),
+            injector('errors'),
+            injector('dbHelper')])
+            .then(([_authService, _errors, _db]) => {
+                authService = _authService;
+                errors = _errors;
+                db = _db;
+            });
+    });
+
+    setup(() => {
+        return db.init();
+    });
+
+    test('Check token generator', () => {
+        const tokenLength = 16;
+        const token1 = authService.generateResetToken(tokenLength);
+        const token2 = authService.generateResetToken(tokenLength);
+
+        assert.equal(token1.length, tokenLength);
+        assert.equal(token2.length, tokenLength);
+        assert.notEqual(token1, token2);
+    });
+
+
+    test('Reset password token for non existing user', (done) => {
+        authService.resetPasswordToken('non-exisitng@email.ee')
+            .catch((err) => {
+                assert.instanceOf(err, errors.MissingResourceException);
+                done();
+            });
+    });
+
+    test('Reset password token for existing user', (done) => {
+        authService.resetPasswordToken(testAccounts[0].email)
+            .then((account) => {
+                assert.notEqual(account.resetPasswordToken.length, 0);
+                assert.notEqual(account.resetPasswordToken, testAccounts[0].resetPasswordToken);
+            })
+            .then(done)
+            .catch(done);
+    });
+
+    test('Reset password by token for non existing user', (done) => {
+        authService.resetPasswordByToken('0')
+            .catch((err) => {
+                assert.instanceOf(err, errors.MissingResourceException);
+                done();
+            });
+    });
+
+    test('Reset password by token for existing user', (done) => {
+        authService.resetPasswordByToken(testAccounts[0].resetPasswordToken, 'NewUniquePassword$1')
+            .then((account) => {
+                assert.isUndefined(account.resetPasswordToken);
+                assert.notEqual(account.hash, 0);
+                assert.notEqual(account.hash, testAccounts[0].hash);
+            })
+            .then(done)
+            .catch(done);
+    });
+
+    test('Validate user for non existing reset token', (done) => {
+        authService.validateResetToken('Non existing token')
+            .catch((err) => {
+                assert.instanceOf(err, errors.IllegalAccessError);
+                done();
+            });
+    });
+
+    test('Validate reset token', (done) => {
+        authService.validateResetToken(testAccounts[0].resetPasswordToken)
+            .then(({token, email}) => {
+                assert.equal(email, testAccounts[0].email);
+                assert.equal(token, testAccounts[0].resetPasswordToken);
+            })
+            .then(done)
+            .catch(done);
+    });
+});

http://git-wip-us.apache.org/repos/asf/ignite/blob/48d4a925/modules/web-console/backend/test/unit/CacheService.test.js
----------------------------------------------------------------------
diff --git a/modules/web-console/backend/test/unit/CacheService.test.js b/modules/web-console/backend/test/unit/CacheService.test.js
index 1442775..980f47a 100644
--- a/modules/web-console/backend/test/unit/CacheService.test.js
+++ b/modules/web-console/backend/test/unit/CacheService.test.js
@@ -15,11 +15,10 @@
  * limitations under the License.
  */
 
-
-import {assert} from 'chai';
-import injector from '../injector';
-import testCaches from '../data/caches.json';
-import testAccounts from '../data/accounts.json';
+const assert = require('chai').assert;
+const injector = require('../injector');
+const testCaches = require('../data/caches.json');
+const testAccounts = require('../data/accounts.json');
 
 let cacheService;
 let mongo;
@@ -79,7 +78,7 @@ suite('CacheServiceTestsSuite', () => {
 
         cacheService.merge(testCaches[0])
             .then((cache) => {
-                const cacheBeforeMerge = {...testCaches[0], _id: cache._id, name: newName};
+                const cacheBeforeMerge = Object.assign({}, testCaches[0], {_id: cache._id, name: newName});
 
                 return cacheService.merge(cacheBeforeMerge);
             })
@@ -92,11 +91,12 @@ suite('CacheServiceTestsSuite', () => {
     });
 
     test('Create duplicated cache', (done) => {
+        const dupleCache = Object.assign({}, testCaches[0], {_id: null});
+
         cacheService.merge(testCaches[0])
-            .then(() => cacheService.merge(testCaches[0]))
+            .then(() => cacheService.merge(dupleCache))
             .catch((err) => {
                 assert.instanceOf(err, errors.DuplicateKeyException);
-
                 done();
             });
     });
@@ -145,7 +145,7 @@ suite('CacheServiceTestsSuite', () => {
         prepareUserSpaces()
             .then(([accounts, spaces]) => {
                 const currentUser = accounts[0];
-                const userCache = {...testCaches[0], space: spaces[0][0]._id};
+                const userCache = Object.assign({}, testCaches[0], {space: spaces[0][0]._id});
 
                 return cacheService.merge(userCache)
                     .then(() => cacheService.removeAll(currentUser._id, false));
@@ -160,7 +160,7 @@ suite('CacheServiceTestsSuite', () => {
     test('Get all caches by space', (done) => {
         prepareUserSpaces()
             .then(([accounts, spaces]) => {
-                const userCache = {...testCaches[0], space: spaces[0][0]._id};
+                const userCache = Object.assign({}, testCaches[0], {space: spaces[0][0]._id});
 
                 return cacheService.merge(userCache)
                     .then((cache) => {