From c2f43ae204f3243e5807b4967c998619787e9e90 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bj=C3=B6rn=20Ricks?= Date: Mon, 6 May 2019 12:47:26 +0200 Subject: [PATCH 01/14] Differentiate between active and passive logout Add different function to Gmp object to allow an active logout by a user. The passive logout will be called for session timeout. --- gsa/src/gmp/gmp.js | 23 ++++++++++++----------- 1 file changed, 12 insertions(+), 11 deletions(-) diff --git a/gsa/src/gmp/gmp.js b/gsa/src/gmp/gmp.js index 03ab9b548e..27d60b3248 100644 --- a/gsa/src/gmp/gmp.js +++ b/gsa/src/gmp/gmp.js @@ -124,7 +124,7 @@ class Gmp { }); } - logout() { + doLogout() { if (this.isLoggedIn()) { const url = this.buildUrl('logout'); const args = {token: this.settings.token}; @@ -135,26 +135,27 @@ class Gmp { args, transform: DefaultTransform, }) - .then(xhr => { - this.clearToken(); - log.debug('Logged out successfully'); - return xhr; - }) .catch(err => { - this.clearToken(); log.error('Error on logout', err); + }) + .then(() => { + this.logout(); }); - for (const listener of this._logoutListeners) { - listener(); - } - return promise; } return Promise.resolve(); } + logout() { + this.clearToken(); + + for (const listener of this._logoutListeners) { + listener(); + } + } + isLoggedIn() { return !isEmpty(this.settings.token); } From 200cff0e82111a1e349e54a5aa8722a8cdf9d5fd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bj=C3=B6rn=20Ricks?= Date: Mon, 6 May 2019 13:02:13 +0200 Subject: [PATCH 02/14] Allow to pass http object to Gmp for testing --- gsa/src/gmp/gmp.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/gsa/src/gmp/gmp.js b/gsa/src/gmp/gmp.js index 27d60b3248..0342f8e10f 100644 --- a/gsa/src/gmp/gmp.js +++ b/gsa/src/gmp/gmp.js @@ -75,7 +75,7 @@ import {BROWSER_LANGUAGE} from './locale/languages'; const log = logger.getLogger('gmp'); class Gmp { - constructor(settings = {}) { + constructor(settings = {}, http) { this.settings = settings; logger.init(this.settings); @@ -84,7 +84,7 @@ class Gmp { this.log = logger; - this.http = new GmpHttp(this.settings); + this.http = isDefined(http) ? http : new GmpHttp(this.settings); this._login = new LoginCommand(this.http); From 0cba7b9b58e648d158293ff8d40722a04a0aec74 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bj=C3=B6rn=20Ricks?= Date: Mon, 6 May 2019 13:03:21 +0200 Subject: [PATCH 03/14] Add tests for Gmp logout/login handling --- gsa/src/gmp/__tests__/gmp.js | 266 +++++++++++++++++++++++++++++++++++ 1 file changed, 266 insertions(+) create mode 100644 gsa/src/gmp/__tests__/gmp.js diff --git a/gsa/src/gmp/__tests__/gmp.js b/gsa/src/gmp/__tests__/gmp.js new file mode 100644 index 0000000000..c11880e391 --- /dev/null +++ b/gsa/src/gmp/__tests__/gmp.js @@ -0,0 +1,266 @@ +/* Copyright (C) 2019 Greenbone Networks GmbH + * + * SPDX-License-Identifier: GPL-2.0-or-later + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + */ +import DefaultTransform from '../http/transform/default'; + +import Gmp from '../gmp'; + +describe('Gmp tests', () => { + describe('isLoggedIn tests', () => { + test('should return false if user has no token', () => { + const gmp = new Gmp(); + + expect(gmp.isLoggedIn()).toEqual(false); + }); + + test('should return false if user has empty token', () => { + const gmp = new Gmp({token: ''}); + + expect(gmp.isLoggedIn()).toEqual(false); + }); + + test('should return true if user has token', () => { + const gmp = new Gmp({token: 'foo'}); + + expect(gmp.isLoggedIn()).toEqual(true); + }); + }); + + describe('login tests', () => { + test('should login user', () => { + const request = jest.fn().mockResolvedValue({ + data: { + token: 'foo', + }, + }); + const settings = {}; + + const http = { + request, + }; + + const gmp = new Gmp(settings, http); + + return gmp.login('foo', 'bar').then(() => { + expect(request).toHaveBeenCalledWith('post', { + data: { + cmd: 'login', + login: 'foo', + password: 'bar', + }, + }); + expect(gmp.isLoggedIn()).toEqual(true); + }); + }); + + test('should not login if request fails', () => { + const request = jest.fn().mockRejectedValue({ + message: 'An error', + }); + const settings = {}; + + const http = { + request, + }; + + const gmp = new Gmp(settings, http); + + return gmp.login('foo', 'bar').catch(error => { + expect(request).toHaveBeenCalledWith('post', { + data: { + cmd: 'login', + login: 'foo', + password: 'bar', + }, + }); + expect(error.message).toEqual('An error'); + expect(gmp.isLoggedIn()).toEqual(false); + }); + }); + }); + + describe('clearToken tests', () => { + test('should reset token', () => { + const settings = {token: 'foo'}; + const gmp = new Gmp(settings); + + expect(gmp.isLoggedIn()).toEqual(true); + + gmp.clearToken(); + + expect(gmp.isLoggedIn()).toEqual(false); + expect(settings.token).toBeUndefined(); + }); + }); + + describe('logout tests', () => { + test('should reset token', () => { + const settings = {token: 'foo'}; + const gmp = new Gmp(settings); + + expect(gmp.isLoggedIn()).toEqual(true); + + gmp.logout(); + + expect(gmp.isLoggedIn()).toEqual(false); + expect(settings.token).toBeUndefined(); + }); + + test('should call logout handlers if logged in', () => { + const settings = {token: 'foo'}; + const gmp = new Gmp(settings); + const handler = jest.fn(); + const unsub = gmp.subscribeToLogout(handler); + + expect(gmp.isLoggedIn()).toEqual(true); + + gmp.logout(); + + expect(gmp.isLoggedIn()).toEqual(false); + expect(handler).toHaveBeenCalled(); + + unsub(); + }); + + test('should call logout handlers if logged out', () => { + const settings = {}; + const gmp = new Gmp(settings); + const handler = jest.fn(); + const unsub = gmp.subscribeToLogout(handler); + + expect(gmp.isLoggedIn()).toEqual(false); + + gmp.logout(); + + expect(gmp.isLoggedIn()).toEqual(false); + expect(handler).toHaveBeenCalled(); + + unsub(); + }); + }); + + describe('doLogout tests', () => { + test('should logout user', () => { + const request = jest.fn().mockResolvedValue({ + data: { + token: 'foo', + }, + }); + const settings = {token: 'foo', server: 'localhost'}; + + const http = { + request, + }; + + const gmp = new Gmp(settings, http); + + expect(gmp.isLoggedIn()).toEqual(true); + + return gmp.doLogout().then(() => { + expect(request).toHaveBeenCalledWith('get', { + args: { + token: 'foo', + }, + transform: DefaultTransform, + url: 'http://localhost/logout', + }); + expect(gmp.isLoggedIn()).toEqual(false); + expect(settings.token).toBeUndefined(); + }); + }); + + test('should notify handler on logout success', () => { + const request = jest.fn().mockResolvedValue({ + data: {}, + }); + const settings = {token: 'foo', server: 'localhost'}; + + const http = { + request, + }; + + const handler = jest.fn(); + + const gmp = new Gmp(settings, http); + + gmp.subscribeToLogout(handler); + + expect(gmp.isLoggedIn()).toEqual(true); + + return gmp.doLogout().then(() => { + expect(gmp.isLoggedIn()).toEqual(false); + expect(settings.token).toBeUndefined(); + expect(handler).toHaveBeenCalled(); + }); + }); + + test('should ignore logout api call failure', () => { + const request = jest.fn().mockRejectedValue({ + message: 'foo', + }); + const settings = { + loglevel: 'silent', + token: 'foo', + server: 'localhost', + }; + + const http = { + request, + }; + + const handler = jest.fn(); + + const gmp = new Gmp(settings, http); + + gmp.subscribeToLogout(handler); + + expect(gmp.isLoggedIn()).toEqual(true); + + return gmp.doLogout().then(() => { + expect(gmp.isLoggedIn()).toEqual(false); + expect(settings.token).toBeUndefined(); + expect(handler).toHaveBeenCalled(); + }); + }); + + test('should not do logout if not logged int', () => { + const request = jest.fn().mockResolvedValue({ + data: {}, + }); + const settings = {server: 'localhost'}; + + const http = { + request, + }; + + const handler = jest.fn(); + + const gmp = new Gmp(settings, http); + + gmp.subscribeToLogout(handler); + + expect(gmp.isLoggedIn()).toEqual(false); + + return gmp.doLogout().then(() => { + expect(gmp.isLoggedIn()).toEqual(false); + expect(settings.token).toBeUndefined(); + expect(handler).not.toHaveBeenCalled(); + }); + }); + }); +}); From f5c4735eeb8078760bba78ede5a12f4bca0ba224 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bj=C3=B6rn=20Ricks?= Date: Mon, 6 May 2019 13:04:23 +0200 Subject: [PATCH 04/14] Don't clear token directly anymore The token will be cleared when calling gmp.logout now. --- gsa/src/web/authorized.js | 2 -- 1 file changed, 2 deletions(-) diff --git a/gsa/src/web/authorized.js b/gsa/src/web/authorized.js index 47bc9c099f..e94d809eb6 100644 --- a/gsa/src/web/authorized.js +++ b/gsa/src/web/authorized.js @@ -60,8 +60,6 @@ class Authorized extends React.Component { toLoginPage() { const {gmp, history} = this.props; - gmp.clearToken(); // ensure gmp.isLoggedIn returns false - history.replace('/login', { next: this.props.location.pathname, }); From 3b0e12752e95303fe2a168207ff788d1775b5c83 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bj=C3=B6rn=20Ricks?= Date: Mon, 6 May 2019 13:05:20 +0200 Subject: [PATCH 05/14] Call doLogout for active logouts by the user --- gsa/src/web/components/bar/titlebar.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gsa/src/web/components/bar/titlebar.js b/gsa/src/web/components/bar/titlebar.js index 72a35223a1..8c5d37a47a 100644 --- a/gsa/src/web/components/bar/titlebar.js +++ b/gsa/src/web/components/bar/titlebar.js @@ -103,7 +103,7 @@ class Titlebar extends React.Component { event.preventDefault(); - gmp.logout().then(() => { + gmp.doLogout().then(() => { history.push('/login?type=logout'); }); } From 32d12b4abe635412fbb41f096d9e8eecf6d853b4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bj=C3=B6rn=20Ricks?= Date: Mon, 6 May 2019 14:48:18 +0200 Subject: [PATCH 06/14] Remove unused methods from Rejection class Only isError is used currently. --- gsa/src/gmp/http/rejection.js | 8 -------- 1 file changed, 8 deletions(-) diff --git a/gsa/src/gmp/http/rejection.js b/gsa/src/gmp/http/rejection.js index 7ff096ccf0..ded74f56d2 100644 --- a/gsa/src/gmp/http/rejection.js +++ b/gsa/src/gmp/http/rejection.js @@ -37,18 +37,10 @@ class Rejection { this.stack = error.stack; } - isCancel() { - return this.reason === Rejection.REASON_CANCEL; - } - isError() { return this.reason === Rejection.REASON_ERROR; } - isTimeout() { - return this.reason === Rejection.REASON_TIMEOUT; - } - setMessage(message) { this.message = message; return this; From 5a3768ab3c579e3bb95ade551aa1e3e4305274b2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bj=C3=B6rn=20Ricks?= Date: Mon, 6 May 2019 14:49:01 +0200 Subject: [PATCH 07/14] Add new constant class variable to Rejection Add a REASON_UNAUTHORIZED class variable to Rejection. --- gsa/src/gmp/http/rejection.js | 1 + 1 file changed, 1 insertion(+) diff --git a/gsa/src/gmp/http/rejection.js b/gsa/src/gmp/http/rejection.js index ded74f56d2..1196f27513 100644 --- a/gsa/src/gmp/http/rejection.js +++ b/gsa/src/gmp/http/rejection.js @@ -22,6 +22,7 @@ class Rejection { static REASON_ERROR = 'error'; static REASON_TIMEOUT = 'timeout'; static REASON_CANCEL = 'cancel'; + static REASON_UNAUTHORIZED = 'unauthorized'; constructor(xhr, reason = Rejection.REASON_ERROR, message = '', error) { this.name = 'Rejection'; From 35e7af33bc5cd2b66482489641b068650d8105a2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bj=C3=B6rn=20Ricks?= Date: Mon, 6 May 2019 14:50:04 +0200 Subject: [PATCH 08/14] Set rejection reason depending on http status Set rejection reason to unauthorized if http status was 401. --- gsa/src/gmp/http/http.js | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/gsa/src/gmp/http/http.js b/gsa/src/gmp/http/http.js index 0c11598e43..393af69562 100644 --- a/gsa/src/gmp/http/http.js +++ b/gsa/src/gmp/http/http.js @@ -177,7 +177,11 @@ class Http { } promise.catch(request => { - const rej = new Rejection(request, Rejection.REASON_ERROR); + const {status} = request; + const rej = new Rejection( + request, + status === 401 ? Rejection.REASON_UNAUTHORIZED : Rejection.REASON_ERROR, + ); try { reject(this.transformRejection(rej, options)); } catch (error) { From e6ada31b989145572eceb9beb13775a0028db211 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bj=C3=B6rn=20Ricks?= Date: Mon, 6 May 2019 14:51:02 +0200 Subject: [PATCH 09/14] Ensure returning a rejection to upper layers Always pass a rejection if an http error had occurred. If the error couldn't be transformed e.g. due to unexpected or invalid xml ensure to only log the error and return the original rejection. --- gsa/src/gmp/http/http.js | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/gsa/src/gmp/http/http.js b/gsa/src/gmp/http/http.js index 393af69562..15ad91486d 100644 --- a/gsa/src/gmp/http/http.js +++ b/gsa/src/gmp/http/http.js @@ -185,7 +185,8 @@ class Http { try { reject(this.transformRejection(rej, options)); } catch (error) { - reject(error); + log.eror('Could not transform rejection', error, rej); + reject(rej); } }); } From 2a7bc7932fc2f0197418e0c036aaddfab1886f7c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bj=C3=B6rn=20Ricks?= Date: Mon, 6 May 2019 14:52:44 +0200 Subject: [PATCH 10/14] Add tests for Rejection class --- gsa/src/gmp/http/__tests__/rejection.js | 96 +++++++++++++++++++++++++ 1 file changed, 96 insertions(+) create mode 100644 gsa/src/gmp/http/__tests__/rejection.js diff --git a/gsa/src/gmp/http/__tests__/rejection.js b/gsa/src/gmp/http/__tests__/rejection.js new file mode 100644 index 0000000000..44a40388bd --- /dev/null +++ b/gsa/src/gmp/http/__tests__/rejection.js @@ -0,0 +1,96 @@ +/* Copyright (C) 2019 Greenbone Networks GmbH + * + * SPDX-License-Identifier: GPL-2.0-or-later + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + */ +import Rejection from '../rejection'; + +describe('Rejection tests', () => { + test('should create error rejection by default', () => { + const rejection = new Rejection(); + + expect(rejection.reason).toEqual(Rejection.REASON_ERROR); + expect(rejection.message).toEqual(''); + expect(rejection.xhr).toBeUndefined(); + expect(rejection.error).toBeUndefined(); + expect(rejection.stack).toBeDefined(); + expect(rejection.isError()).toEqual(true); + }); + + test('should create error rejection', () => { + const xhr = {foo: 'bar'}; + const error = new Error('foobar'); + const rejection = new Rejection( + xhr, + Rejection.REASON_ERROR, + 'an error', + error, + ); + + expect(rejection.reason).toEqual(Rejection.REASON_ERROR); + expect(rejection.message).toEqual('an error'); + expect(rejection.xhr).toEqual(xhr); + expect(rejection.error).toEqual(error); + expect(rejection.stack).toBeDefined(); + expect(rejection.isError()).toEqual(true); + }); + + test('should create unauthorized rejection', () => { + const xhr = {foo: 'bar'}; + const rejection = new Rejection(xhr, Rejection.REASON_UNAUTHORIZED); + + expect(rejection.reason).toEqual(Rejection.REASON_UNAUTHORIZED); + expect(rejection.message).toEqual(''); + expect(rejection.xhr).toEqual(xhr); + expect(rejection.error).toBeUndefined(); + expect(rejection.stack).toBeDefined(); + expect(rejection.isError()).toEqual(false); + }); + + test('should create cancel rejection', () => { + const xhr = {foo: 'bar'}; + const rejection = new Rejection(xhr, Rejection.REASON_CANCEL, 'foo'); + + expect(rejection.reason).toEqual(Rejection.REASON_CANCEL); + expect(rejection.message).toEqual('foo'); + expect(rejection.xhr).toEqual(xhr); + expect(rejection.error).toBeUndefined(); + expect(rejection.stack).toBeDefined(); + expect(rejection.isError()).toEqual(false); + }); + + test('should create timeout rejection', () => { + const xhr = {foo: 'bar'}; + const rejection = new Rejection(xhr, Rejection.REASON_TIMEOUT, 'foo'); + + expect(rejection.reason).toEqual(Rejection.REASON_TIMEOUT); + expect(rejection.message).toEqual('foo'); + expect(rejection.xhr).toEqual(xhr); + expect(rejection.error).toBeUndefined(); + expect(rejection.stack).toBeDefined(); + expect(rejection.isError()).toEqual(false); + }); + + test('should allow to change message', () => { + const rejection = new Rejection({}, Rejection.REASON_ERROR, 'foo'); + + expect(rejection.message).toEqual('foo'); + + rejection.setMessage('bar'); + + expect(rejection.message).toEqual('bar'); + }); +}); From b7340308abdc303554fdc55a8f4ff6406bd6ebc8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bj=C3=B6rn=20Ricks?= Date: Mon, 6 May 2019 14:55:25 +0200 Subject: [PATCH 11/14] Don't put expected errors in redux store for entities Expected errors are rejections caused by being unauthorized, cancelation or timeouts. --- gsa/src/web/store/entities/utils/reducers.js | 32 +++++++++++++++----- 1 file changed, 24 insertions(+), 8 deletions(-) diff --git a/gsa/src/web/store/entities/utils/reducers.js b/gsa/src/web/store/entities/utils/reducers.js index c3d6cccc50..4b3fd2412d 100644 --- a/gsa/src/web/store/entities/utils/reducers.js +++ b/gsa/src/web/store/entities/utils/reducers.js @@ -16,10 +16,20 @@ * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. */ +import {isDefined} from 'gmp/utils/identity'; + import {types} from './actions'; import {filterIdentifier} from 'web/store/utils'; +/** + * Return trues if error is not a Rejection class or if Rejection class has a + * reason of error + * + * @returns Boolean + */ +const isError = error => !isDefined(error.isError) || error.isError(); + const initialState = { byId: {}, errors: {}, @@ -67,10 +77,13 @@ export const createReducer = entityType => { delete state[filterString]; return state; case types.ENTITIES_LOADING_ERROR: - return { - ...state, - [filterString]: action.error, - }; + if (isError(action.error)) { + return { + ...state, + [filterString]: action.error, + }; + } + return state; case types.ENTITY_LOADING_SUCCESS: state = { ...state, @@ -78,10 +91,13 @@ export const createReducer = entityType => { delete state[action.id]; return state; case types.ENTITY_LOADING_ERROR: - return { - ...state, - [action.id]: action.error, - }; + if (isError(action.error)) { + return { + ...state, + [action.id]: action.error, + }; + } + return state; default: return state; } From 68e7458c293f332016583d5cdc72bc53f4d66261 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bj=C3=B6rn=20Ricks?= Date: Mon, 6 May 2019 15:22:01 +0200 Subject: [PATCH 12/14] Add tests for expected errors when loading entities --- .../entities/utils/__tests__/reducers.js | 92 +++++++++++++++++++ 1 file changed, 92 insertions(+) diff --git a/gsa/src/web/store/entities/utils/__tests__/reducers.js b/gsa/src/web/store/entities/utils/__tests__/reducers.js index 1dfb65b720..9cdccd56af 100644 --- a/gsa/src/web/store/entities/utils/__tests__/reducers.js +++ b/gsa/src/web/store/entities/utils/__tests__/reducers.js @@ -20,6 +20,8 @@ import {isFunction} from 'gmp/utils/identity'; import Filter from 'gmp/models/filter'; +import Rejection from 'gmp/http/rejection'; + import {filterIdentifier} from 'web/store/utils'; import {createEntitiesActions, createEntityActions} from '../actions'; @@ -453,6 +455,58 @@ describe('entities reducers test', () => { [filterId]: {}, }); }); + + test('should not reduce expected errors', () => { + const actions = createEntitiesActions('foo'); + const reducer = createReducer('foo'); + const filter = Filter.fromString('name=bar'); + const filterId = filterIdentifier(filter); + const otherFilter = Filter.fromString('name=foo'); + const otherFilterId = filterIdentifier(otherFilter); + const rejection = new Rejection({}, Rejection.REASON_UNAUTHORIZED); + const action = actions.error(rejection, filter); + const state = { + byId: { + lorem: { + id: 'lorem', + }, + ipsum: { + id: 'ipsum', + }, + }, + errors: { + [otherFilterId]: 'Another error', + }, + isLoading: { + [otherFilterId]: true, + }, + [otherFilterId]: { + ids: ['lorem', 'ipsum'], + }, + }; + + expect(reducer(state, action)).toEqual({ + byId: { + lorem: { + id: 'lorem', + }, + ipsum: { + id: 'ipsum', + }, + }, + errors: { + [otherFilterId]: 'Another error', + }, + isLoading: { + [otherFilterId]: true, + [filterId]: false, + }, + [otherFilterId]: { + ids: ['lorem', 'ipsum'], + }, + [filterId]: {}, + }); + }); }); describe('reducing entity loading requests', () => { @@ -783,6 +837,44 @@ describe('entities reducers test', () => { }, }); }); + + test('should not reduce expected errors', () => { + const id = 'a1'; + const actions = createEntityActions('foo'); + const rejection = new Rejection({}, Rejection.REASON_UNAUTHORIZED); + const action = actions.error(id, rejection); + const reducer = createReducer('foo'); + const state = { + byId: { + baz: { + id: 'baz', + old: 'mydata', + }, + }, + errors: { + baz: 'Another error', + }, + isLoading: { + bar: true, + }, + }; + + expect(reducer(state, action)).toEqual({ + byId: { + baz: { + id: 'baz', + old: 'mydata', + }, + }, + errors: { + baz: 'Another error', + }, + isLoading: { + [id]: false, + bar: true, + }, + }); + }); }); }); From 397e95bb958ae0bfc7b8e89f9d9deea3248a44b7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bj=C3=B6rn=20Ricks?= Date: Mon, 6 May 2019 15:30:15 +0200 Subject: [PATCH 13/14] Add changelog entry --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 05dbec8e78..d19c833e40 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -38,6 +38,7 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/). - Cleanup get_report function in gsad [#1263](https://github.com/greenbone/gsa/pull/1263) ### Fixed +- Don't show error message after re-login [#1366](https://github.com/greenbone/gsa/pull/1366) - Fix creating permissions in Roles dialog [#1365](https://github.com/greenbone/gsa/pull/1365) - Fix cloning permission for Roles [#1361](https://github.com/greenbone/gsa/pull/1361) - Use correct loaded filter in entities container [#1359](https://github.com/greenbone/gsa/pull/1359) From 191e5e677e25fea835608eab7b99b5c5453250c5 Mon Sep 17 00:00:00 2001 From: Steffen Waterkamp <32056637+swaterkamp@users.noreply.github.com> Date: Tue, 7 May 2019 09:38:58 +0200 Subject: [PATCH 14/14] Apply suggestions from code review Co-Authored-By: bjoernricks --- gsa/src/gmp/http/http.js | 2 +- gsa/src/web/store/entities/utils/reducers.js | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/gsa/src/gmp/http/http.js b/gsa/src/gmp/http/http.js index 15ad91486d..3034f72b87 100644 --- a/gsa/src/gmp/http/http.js +++ b/gsa/src/gmp/http/http.js @@ -185,7 +185,7 @@ class Http { try { reject(this.transformRejection(rej, options)); } catch (error) { - log.eror('Could not transform rejection', error, rej); + log.error('Could not transform rejection', error, rej); reject(rej); } }); diff --git a/gsa/src/web/store/entities/utils/reducers.js b/gsa/src/web/store/entities/utils/reducers.js index 4b3fd2412d..f193468cc5 100644 --- a/gsa/src/web/store/entities/utils/reducers.js +++ b/gsa/src/web/store/entities/utils/reducers.js @@ -23,7 +23,7 @@ import {types} from './actions'; import {filterIdentifier} from 'web/store/utils'; /** - * Return trues if error is not a Rejection class or if Rejection class has a + * Return true if error is not a Rejection class or if Rejection class has a * reason of error * * @returns Boolean