Merge "[CORD-1133] E2E GUI Tests"
diff --git a/Dockerfile.xos-gui-extension-builder b/Dockerfile.xos-gui-extension-builder
index ea41050..f04fa3e 100644
--- a/Dockerfile.xos-gui-extension-builder
+++ b/Dockerfile.xos-gui-extension-builder
@@ -1,6 +1,6 @@
# To build use: docker build -t xosproject/xos-gui-extension-builder .
-FROM node:argon
+FROM node:7.9.0
# Set environment vars
ENV CODE_SOURCE .
diff --git a/conf/browsersync.conf.js b/conf/browsersync.conf.js
index 391f688..3f262ba 100644
--- a/conf/browsersync.conf.js
+++ b/conf/browsersync.conf.js
@@ -1,6 +1,7 @@
const conf = require('./gulp.conf');
const proxy = require('./proxy').proxy;
const extensionsProxy = require('./proxy').extensionsProxy;
+const socketProxy = require('./proxy').socketProxy;
module.exports = function () {
return {
@@ -10,9 +11,12 @@
conf.paths.src
],
middleware: function(req, res, next){
- if (req.url.indexOf('xosapi') !== -1 || req.url.indexOf('socket.io') !== -1) {
+ if (req.url.indexOf('xosapi') !== -1) {
proxy.web(req, res);
}
+ else if (req.url.indexOf('socket.io') !== -1) {
+ socketProxy.web(req, res);
+ }
else if (req.url.indexOf('extensions') !== -1) {
extensionsProxy.web(req, res);
}
diff --git a/conf/proxy.js b/conf/proxy.js
index 8af184d..d45a27b 100644
--- a/conf/proxy.js
+++ b/conf/proxy.js
@@ -3,21 +3,34 @@
const target = process.env.PROXY || '192.168.46.100';
const proxy = httpProxy.createProxyServer({
- target: `http://${target}:9101`
+ target: `http://${target}`
});
const extensionsProxy = httpProxy.createProxyServer({
target: `http://${target}/xos/`
});
+const socketProxy = httpProxy.createProxyServer({
+ target: `http://${target}/`
+});
+
proxy.on('error', function(error, req, res) {
- res.writeHead(500, {
- 'Content-Type': 'text/plain'
- });
+ res.writeHead(500, {'Content-Type': 'text/plain'});
console.error('[Proxy]', error);
});
+extensionsProxy.on('error', function(error, req, res) {
+ res.writeHead(500, {'Content-Type': 'text/plain'});
+ console.error('[extensionsProxy]', error);
+});
+
+socketProxy.on('error', function(error, req, res) {
+ res.writeHead(500, {'Content-Type': 'text/plain'});
+ console.error('[socketProxy]', error);
+});
+
module.exports = {
proxy,
- extensionsProxy
+ extensionsProxy,
+ socketProxy
};
\ No newline at end of file
diff --git a/package.json b/package.json
index 40be132..2307f81 100644
--- a/package.json
+++ b/package.json
@@ -1,5 +1,5 @@
{
- "version": "2.0.0",
+ "version": "3.0.0",
"dependencies": {
"angular": "1.6.3",
"angular-animate": "1.6.3",
diff --git a/src/app/core/loader/loader.spec.ts b/src/app/core/loader/loader.spec.ts
index 1bcff9c..d4b88dd 100644
--- a/src/app/core/loader/loader.spec.ts
+++ b/src/app/core/loader/loader.spec.ts
@@ -3,6 +3,7 @@
import {xosLoader} from './loader';
let loaded = true;
+let authenticated = true;
const MockConfig = {
lastVisitedUrl: '/test'
@@ -17,6 +18,15 @@
onboard: null
};
+const MockAuth = {
+ isAuthenticated: jasmine.createSpy('isAuthenticated')
+ .and.callFake(() => authenticated)
+};
+
+const MockState = {
+ go: jasmine.createSpy('state.go')
+};
+
describe('The XosLoader component', () => {
beforeEach(() => {
angular
@@ -24,6 +34,8 @@
.value('XosConfig', MockConfig)
.value('XosModelDiscoverer', MockDiscover)
.value('XosOnboarder', MockOnboarder)
+ .value('AuthService', MockAuth)
+ .value('$state', MockState)
.component('xosLoader', xosLoader);
angular.mock.module('loader');
});
@@ -99,6 +111,24 @@
});
});
+ describe('when user is not authenticated', () => {
+
+ beforeEach(() => {
+ loaded = false;
+ authenticated = false;
+ compileElement();
+ isolatedScope.run();
+ });
+
+ it('should redirect to the login page', () => {
+ expect(MockState.go).toHaveBeenCalledWith('xos.login');
+ });
+
+ afterEach(() => {
+ authenticated = true;
+ });
+ });
+
describe('when models are not loaded', () => {
beforeEach(() => {
diff --git a/src/app/core/loader/loader.ts b/src/app/core/loader/loader.ts
index 07a9875..f12b17b 100644
--- a/src/app/core/loader/loader.ts
+++ b/src/app/core/loader/loader.ts
@@ -1,23 +1,26 @@
import {IXosModelDiscovererService} from '../../datasources/helpers/model-discoverer.service';
import {IXosOnboarder} from '../../extender/services/onboard.service';
+import {IXosAuthService} from '../../datasources/rest/auth.rest';
class LoaderCtrl {
static $inject = [
'$log',
'$rootScope',
'$location',
'$timeout',
+ '$state',
+ 'AuthService',
'XosConfig',
'XosModelDiscoverer',
`XosOnboarder`
];
- public aaaaa = 'ciao';
-
constructor (
private $log: ng.ILogService,
private $rootScope: ng.IScope,
private $location: ng.ILocationService,
private $timeout: ng.ITimeoutService,
+ private $state: ng.ui.IStateService,
+ private XosAuthService: IXosAuthService,
private XosConfig: any,
private XosModelDiscoverer: IXosModelDiscovererService,
private XosOnboarder: IXosOnboarder
@@ -30,6 +33,9 @@
if (this.XosModelDiscoverer.areModelsLoaded()) {
this.moveOnTo(this.XosConfig.lastVisitedUrl);
}
+ else if (!this.XosAuthService.isAuthenticated()) {
+ this.$state.go('xos.login');
+ }
else {
this.XosModelDiscoverer.discover()
// NOTE loading XOS Models
@@ -48,7 +54,6 @@
})
.finally(() => {
// NOTE it is in a timeout as the searchService is loaded after that
- // we navigate to another page
this.$timeout(() => {
this.$rootScope.$emit('xos.core.modelSetup');
}, 500);
diff --git a/src/app/core/services/helpers/config.helpers.ts b/src/app/core/services/helpers/config.helpers.ts
index 1cd9e60..c495ba5 100644
--- a/src/app/core/services/helpers/config.helpers.ts
+++ b/src/app/core/services/helpers/config.helpers.ts
@@ -269,10 +269,6 @@
item.$save()
.then((res) => {
- if (res.status === 403 || res.status === 405 || res.status === 404 || res.status === 500) {
- // TODO understand why 405 does not go directly in catch (it may be related to ng-rest-gw)
- throw new Error();
- }
formCfg.feedback = {
show: true,
message: `${model.name} succesfully saved`,
@@ -282,8 +278,13 @@
this.toastr.success(`${model.name} succesfully saved`);
})
.catch(err => {
- // TODO keep the edited model
- this.toastr.error(`Error while saving ${model.name}`);
+ formCfg.feedback = {
+ show: true,
+ message: `Error while saving ${model.name}: ${err.error}. ${err.specific_error || ''}`,
+ type: 'danger',
+ closeBtn: true
+ };
+ this.toastr.error(err.specific_error || '', `Error while saving ${model.name}: ${err.error}`);
});
};
diff --git a/src/app/datasources/websocket/global.ts b/src/app/datasources/websocket/global.ts
index a820269..7e5dad7 100644
--- a/src/app/datasources/websocket/global.ts
+++ b/src/app/datasources/websocket/global.ts
@@ -23,27 +23,38 @@
'$log'
];
+
private _events: Subject<IWSEvent> = new Subject<IWSEvent>();
- private socket;
- constructor(
- private AppConfig: IXosAppConfig,
- private $log: ng.ILogService
- ) {
- this.socket = io(this.AppConfig.websocketClient);
- this.socket.on('event', (data: IWSEvent): void => {
- this.$log.debug(`[WebSocket] Received Event for: ${data.model} [${data.msg.pk}]`);
- this._events.next(data);
+ private socket;
+ constructor(
+ private AppConfig: IXosAppConfig,
+ private $log: ng.ILogService
+ ) {
+ // NOTE list of field that are not useful to the UI
+ const ignoredFields: string[] = ['created', 'updated', 'backend_register'];
- // NOTE update observers of parent classes
- if (data.msg.object.class_names && angular.isString(data.msg.object.class_names)) {
- const models = data.msg.object.class_names.split(',');
- _.forEach(models, (m: string) => {
- data.model = m;
- this._events.next(data);
- });
- }
+ this.socket = io(this.AppConfig.websocketClient);
+ this.socket.on('event', (data: IWSEvent): void => {
+ this.$log.debug(`[WebSocket] Received Event for: ${data.model} [${data.msg.pk}]`);
- });
+ if (data.msg.changed_fields.length === 0 || _.intersection(data.msg.changed_fields, ignoredFields).length === data.msg.changed_fields.length) {
+ // NOTE means that the only updated fields does not change anything in the UI, so don't send events around
+ this.$log.debug(`[WebSocket] Ignoring Event for: ${data.model} [${data.msg.pk}]`);
+ return;
+ }
+
+ this._events.next(data);
+
+ // NOTE update observers of parent classes
+ if (data.msg.object.class_names && angular.isString(data.msg.object.class_names)) {
+ const models = data.msg.object.class_names.split(',');
+ _.forEach(models, (m: string) => {
+ data.model = m;
+ this._events.next(data);
+ });
+ }
+
+ });
}
list() {
return this._events.asObservable();
diff --git a/src/interceptors.ts b/src/interceptors.ts
index 8a11d08..a1b5df8 100644
--- a/src/interceptors.ts
+++ b/src/interceptors.ts
@@ -11,17 +11,16 @@
switch (res.status) {
case -1:
case 401:
- case 500:
$cookies.remove('sessionid', {path: '/'});
$state.go('login');
return $q.reject(res);
default:
- return res;
+ return $q.reject(res);
}
};
return {
- response: checkLogin,
+ // response: checkLogin,
responseError: checkLogin
};
}
@@ -39,7 +38,7 @@
};
}
-export function NoHyperlinksInterceptor() {
+export function NoHyperlinksInterceptor($q: ng.IQService) {
return {
request: (req) => {
if (req.url.indexOf('.html') === -1) {
@@ -68,6 +67,9 @@
res.data = res.data;
}
return res;
+ },
+ responseError: (res) => {
+ return $q.reject(res.data);
}
};
}
diff --git a/typings.json b/typings.json
index a830275..709ff96 100644
--- a/typings.json
+++ b/typings.json
@@ -5,11 +5,12 @@
"angular-mocks": "github:DefinitelyTyped/DefinitelyTyped/angularjs/angular-mocks.d.ts#dc9dabe74a5be62613b17a3605309783a12ff28a",
"angular-resource": "registry:dt/angular-resource#1.5.0+20161114123626",
"angular-ui-router": "registry:dt/angular-ui-router#1.1.5+20160707113237",
- "es6-shim": "registry:dt/es6-shim#0.31.2+20160602141504",
+ "es6-shim": "registry:dt/es6-shim#0.31.2+20160726072212",
"jasmine": "github:DefinitelyTyped/DefinitelyTyped/jasmine/jasmine.d.ts#dc9dabe74a5be62613b17a3605309783a12ff28a",
"jasmine-jquery": "registry:dt/jasmine-jquery#1.5.8+20161128184045",
"jquery": "registry:dt/jquery#1.10.0+20161119044246",
"require": "registry:dt/require#2.1.20+20160316155526",
+ "rx": "npm:rx/ts/rx.all.d.ts",
"socket.io-client": "registry:dt/socket.io-client#1.4.4+20160317120654"
},
"dependencies": {