From 0e6a73f4eb63bc421a4f76780846b7fcb7c90e2c Mon Sep 17 00:00:00 2001 From: Ivan Diaz Date: Tue, 19 Jul 2016 10:45:35 -0300 Subject: [PATCH 1/4] Ivan - Add local storage class [skip ci] --- client/src/lib-app/local-store.js | 39 +++++++++++++++++++++++++++++ client/src/lib-app/session-store.js | 6 ++--- client/src/stores/user-store.js | 4 +-- 3 files changed, 43 insertions(+), 6 deletions(-) create mode 100644 client/src/lib-app/local-store.js diff --git a/client/src/lib-app/local-store.js b/client/src/lib-app/local-store.js new file mode 100644 index 00000000..172aa696 --- /dev/null +++ b/client/src/lib-app/local-store.js @@ -0,0 +1,39 @@ +import LocalStorage from 'localstorage'; + +class LocalStore { + + constructor() { + this.setItem = LocalStorage.setItem; + this.getItem = LocalStorage.getItem; + } + + initialize() { + if (this.isRememberDataExpired()) { + this.clearRememberData(); + } + + if (!this.getItem('language')) { + this.setItem('language', 'english'); + } + } + + storeRememberData({token, userId, expiration}) { + this.setItem('rememberData', { + token, + userId, + expiration + }); + } + + isRememberDataExpired() { + let rememberData = this.getItem('rememberData'); + + return rememberData && rememberData.expirationDate > 2016 + } + + clearRememberData() { + this.setItem('rememberData', null); + } +} + +export default new LocalStore(); \ No newline at end of file diff --git a/client/src/lib-app/session-store.js b/client/src/lib-app/session-store.js index f6a05b77..a2397226 100644 --- a/client/src/lib-app/session-store.js +++ b/client/src/lib-app/session-store.js @@ -1,11 +1,9 @@ import SessionStorage from 'sessionstorage'; - +import LocalStore from 'lib-app/local-store'; class SessionStore { static initialize() { - if (!SessionStorage.getItem('language')) { - SessionStorage.setItem('language', 'english'); - } + } static createSession(userId, token) { diff --git a/client/src/stores/user-store.js b/client/src/stores/user-store.js index 93118c36..dc40df3c 100644 --- a/client/src/stores/user-store.js +++ b/client/src/stores/user-store.js @@ -29,8 +29,8 @@ const UserStore = Reflux.createStore({ path: 'user/logout', onSuccess: function () { SessionStore.closeSession(); - this.trigger('LOGOUT'); CommonActions.loggedOut(); + this.trigger('LOGOUT'); }.bind(this) }); }, @@ -41,8 +41,8 @@ const UserStore = Reflux.createStore({ handleLoginSuccess(result) { SessionStore.createSession(result.data.userId, result.data.token); - this.trigger('LOGIN_SUCCESS'); CommonActions.logged(); + this.trigger('LOGIN_SUCCESS'); }, handleLoginFail() { From 0caa5cb4f9ceafc105bc4436eba9dad02622da0d Mon Sep 17 00:00:00 2001 From: ivan Date: Tue, 19 Jul 2016 15:49:03 -0300 Subject: [PATCH 2/4] Ivan - Update localStorage [skip ci] --- client/package.json | 1 + client/src/data/fixtures/user-fixtures.js | 16 +++++---- client/src/lib-app/api-call.js | 1 + client/src/lib-app/local-store.js | 37 +++++++++++++------ client/src/lib-app/session-store.js | 43 ++++++++++++++--------- client/src/stores/user-store.js | 39 ++++++++++++++++---- 6 files changed, 96 insertions(+), 41 deletions(-) diff --git a/client/package.json b/client/package.json index d6e54b3b..9d90a117 100644 --- a/client/package.json +++ b/client/package.json @@ -55,6 +55,7 @@ "app-module-path": "^1.0.3", "classnames": "^2.1.3", "jquery": "^2.1.4", + "localStorage": "^1.0.3", "lodash": "^3.10.0", "messageformat": "^0.2.2", "react": "^15.0.1", diff --git a/client/src/data/fixtures/user-fixtures.js b/client/src/data/fixtures/user-fixtures.js index f98981f2..1f63652c 100644 --- a/client/src/data/fixtures/user-fixtures.js +++ b/client/src/data/fixtures/user-fixtures.js @@ -5,19 +5,21 @@ module.exports = [ response: function (data) { let response; - if (data.password === 'invalid') { - response = { - status: 'fail', - message: 'Invalid Credientals' - }; - } else { + if (data.password === 'valid' || (data.rememberToken === 'aa41efe0a1b3eeb9bf303e4561ff8392' && data.userId === 12)) { response = { status: 'success', data: { 'userId': 12, - 'token': 'cc6b4921e6733d6aafe284ec0d7be57e' + 'token': 'cc6b4921e6733d6aafe284ec0d7be57e', + 'rememberToken': (data.remember) ? 'aa41efe0a1b3eeb9bf303e4561ff8392' : null, + 'rememberExpiration': (data.remember) ? 2018 : 0 } }; + } else { + response = { + status: 'fail', + message: 'Invalid Credientals' + }; } return response; diff --git a/client/src/lib-app/api-call.js b/client/src/lib-app/api-call.js index 2db55bb8..49228397 100644 --- a/client/src/lib-app/api-call.js +++ b/client/src/lib-app/api-call.js @@ -11,6 +11,7 @@ function processData (data) { module.exports = { call: function ({path, data, onSuccess, onFail}) { APIUtils.post(root + path, processData(data)).then(function (result) { + console.log(result); if (result.status === 'success') { onSuccess && onSuccess(result); } else { diff --git a/client/src/lib-app/local-store.js b/client/src/lib-app/local-store.js index 172aa696..21eac775 100644 --- a/client/src/lib-app/local-store.js +++ b/client/src/lib-app/local-store.js @@ -1,10 +1,9 @@ -import LocalStorage from 'localstorage'; +import LocalStorage from 'localStorage'; class LocalStore { constructor() { - this.setItem = LocalStorage.setItem; - this.getItem = LocalStorage.getItem; + this.storage = LocalStorage; } initialize() { @@ -18,21 +17,37 @@ class LocalStore { } storeRememberData({token, userId, expiration}) { - this.setItem('rememberData', { - token, - userId, - expiration - }); + this.setItem('rememberData-token', token); + this.setItem('rememberData-userId', userId); + this.setItem('rememberData-expiration', expiration); } isRememberDataExpired() { - let rememberData = this.getItem('rememberData'); + let rememberData = this.getRememberData(); - return rememberData && rememberData.expirationDate > 2016 + return rememberData.expiration < 2016; + } + + getRememberData() { + return { + token: this.getItem('rememberData-token'), + userId: parseInt(this.getItem('rememberData-userId')), + expiration: parseInt(this.getItem('rememberData-expiration')) + }; } clearRememberData() { - this.setItem('rememberData', null); + this.setItem('rememberData-token', null); + this.setItem('rememberData-userId', null); + this.setItem('rememberData-expiration', null); + } + + getItem(key) { + return this.storage.getItem(key); + } + + setItem(key, value) { + return this.storage.setItem(key, value); } } diff --git a/client/src/lib-app/session-store.js b/client/src/lib-app/session-store.js index a2397226..90d2ca16 100644 --- a/client/src/lib-app/session-store.js +++ b/client/src/lib-app/session-store.js @@ -1,33 +1,42 @@ import SessionStorage from 'sessionstorage'; -import LocalStore from 'lib-app/local-store'; class SessionStore { - static initialize() { - + constructor() { + this.storage = SessionStorage; } - static createSession(userId, token) { - SessionStorage.setItem('userId', userId); - SessionStorage.setItem('token', token); + createSession(userId, token) { + this.setItem('userId', userId); + this.setItem('token', token); } - static getSessionData() { + getSessionData() { return { - userId: SessionStorage.getItem('userId'), - token: SessionStorage.getItem('token') + userId: this.getItem('userId'), + token: this.getItem('token') }; } - static isLoggedIn() { - return !!SessionStorage.getItem('userId'); + isLoggedIn() { + return !!this.getItem('userId'); } - static closeSession() { - SessionStorage.removeItem('userId'); - SessionStorage.removeItem('token'); + closeSession() { + this.removeItem('userId'); + this.removeItem('token'); + } + + getItem(key) { + return this.storage.getItem(key); + } + + setItem(key, value) { + return this.storage.setItem(key, value); + } + + removeItem(key) { + return this.storage.removeItem(key); } } -SessionStore.initialize(); - -export default SessionStore; \ No newline at end of file +export default new SessionStore(); \ No newline at end of file diff --git a/client/src/stores/user-store.js b/client/src/stores/user-store.js index dc40df3c..d5189ef8 100644 --- a/client/src/stores/user-store.js +++ b/client/src/stores/user-store.js @@ -1,6 +1,7 @@ const Reflux = require('reflux'); const API = require('lib-app/api-call'); -const SessionStore = require('lib-app/session-store'); +const sessionStore = require('lib-app/session-store'); +const localStore = require('lib-app/local-store'); const UserActions = require('actions/user-actions'); const CommonActions = require('actions/common-actions'); @@ -13,14 +14,18 @@ const UserStore = Reflux.createStore({ this.listenTo(UserActions.checkLoginStatus, this.checkLoginStatus); this.listenTo(UserActions.login, this.loginUser); this.listenTo(UserActions.logout, this.logoutUser); + + if (!this.isLoggedIn()) { + this.loginIfRememberExists(); + } }, loginUser(loginData) { API.call({ path: 'user/login', data: loginData, - onSuccess: this.handleLoginSuccess, - onFail: this.handleLoginFail + onSuccess: (loginData.remember) ? this.handleLoginSuccessWithRemember : this.handleLoginSuccess, + onFail: (loginData.isAutomatic) ? null : this.handleLoginFail }); }, @@ -28,7 +33,7 @@ const UserStore = Reflux.createStore({ API.call({ path: 'user/logout', onSuccess: function () { - SessionStore.closeSession(); + sessionStore.closeSession(); CommonActions.loggedOut(); this.trigger('LOGOUT'); }.bind(this) @@ -36,11 +41,33 @@ const UserStore = Reflux.createStore({ }, isLoggedIn() { - return SessionStore.isLoggedIn(); + return sessionStore.isLoggedIn(); + }, + + loginIfRememberExists() { + let rememberData = localStore.getRememberData(); + + if (!localStore.isRememberDataExpired()) { + UserActions.login({ + userId: rememberData.userId, + rememberToken: rememberData.token, + isAutomatic: true + }); + } + }, + + handleLoginSuccessWithRemember(result) { + localStore.storeRememberData({ + token: result.data.rememberToken, + userId: result.data.userId, + expiration: result.data.rememberExpiration + }); + + this.handleLoginSuccess(result) }, handleLoginSuccess(result) { - SessionStore.createSession(result.data.userId, result.data.token); + sessionStore.createSession(result.data.userId, result.data.token); CommonActions.logged(); this.trigger('LOGIN_SUCCESS'); }, From b3cd09ba003e4194745c25187d00fb6427d934a9 Mon Sep 17 00:00:00 2001 From: ivan Date: Wed, 20 Jul 2016 01:31:07 -0300 Subject: [PATCH 3/4] Ivan - Update remember logic using local storage [skip ci] --- client/src/app/index.js | 8 ++- .../app/main/dashboard/dashboard-layout.js | 4 +- client/src/data/fixtures/user-fixtures.js | 14 ++++- client/src/lib-app/api-call.js | 19 +++--- client/src/lib-app/date.js | 10 +++ client/src/lib-app/local-store.js | 54 ---------------- client/src/lib-app/session-store.js | 40 ++++++++++-- client/src/stores/user-store.js | 61 +++++++++++-------- server/controllers/user/check-session.php | 20 ++++++ 9 files changed, 135 insertions(+), 95 deletions(-) create mode 100644 client/src/lib-app/date.js delete mode 100644 client/src/lib-app/local-store.js create mode 100644 server/controllers/user/check-session.php diff --git a/client/src/app/index.js b/client/src/app/index.js index 7a01f3da..e4bec24b 100644 --- a/client/src/app/index.js +++ b/client/src/app/index.js @@ -1,6 +1,7 @@ import React from 'react'; import {render} from 'react-dom' import Router from 'react-router'; +import UserStore from 'stores/user-store'; import routes from './Routes'; @@ -13,4 +14,9 @@ if (noFixtures === 'disabled') { require('lib-app/fixtures-loader'); } -render(routes, document.getElementById('app')); +let onSessionInit = function () { + render(routes, document.getElementById('app')); +}; + +UserStore.initSession().then(onSessionInit, onSessionInit); + diff --git a/client/src/app/main/dashboard/dashboard-layout.js b/client/src/app/main/dashboard/dashboard-layout.js index d8bfa4bd..f7e13d1b 100644 --- a/client/src/app/main/dashboard/dashboard-layout.js +++ b/client/src/app/main/dashboard/dashboard-layout.js @@ -14,12 +14,12 @@ const DashboardLayout = React.createClass({ }, render() { - return ( + return (UserStore.isLoggedIn()) ? (
{this.props.children}
- ); + ) : null; } }); diff --git a/client/src/data/fixtures/user-fixtures.js b/client/src/data/fixtures/user-fixtures.js index 1f63652c..fe8be0c0 100644 --- a/client/src/data/fixtures/user-fixtures.js +++ b/client/src/data/fixtures/user-fixtures.js @@ -27,12 +27,24 @@ module.exports = [ }, { path: 'user/logout', - time: 1000, + time: 100, response: function () { return { status: 'success', data: {} }; } + }, + { + path: 'user/check-session', + time: 100, + response: function () { + return { + status: 'success', + data: { + sessionActive: true + } + }; + } } ]; diff --git a/client/src/lib-app/api-call.js b/client/src/lib-app/api-call.js index 49228397..89d26ce1 100644 --- a/client/src/lib-app/api-call.js +++ b/client/src/lib-app/api-call.js @@ -9,14 +9,17 @@ function processData (data) { } module.exports = { - call: function ({path, data, onSuccess, onFail}) { - APIUtils.post(root + path, processData(data)).then(function (result) { - console.log(result); - if (result.status === 'success') { - onSuccess && onSuccess(result); - } else { - onFail && onFail(result); - } + call: function ({path, data}) { + return new Promise(function (resolve, reject) { + APIUtils.post(root + path, processData(data)).then(function (result) { + console.log(result); + + if (result.status === 'success') { + resolve(result); + } else if (reject) { + reject(result); + } + }); }); } }; \ No newline at end of file diff --git a/client/src/lib-app/date.js b/client/src/lib-app/date.js new file mode 100644 index 00000000..f14beb2b --- /dev/null +++ b/client/src/lib-app/date.js @@ -0,0 +1,10 @@ +export default { + getCurrentDate() { + let date = new Date(); + let yyyy = date.getFullYear().toString(); + let mm = (date.getMonth()+1).toString(); // getMonth() is zero-based + let dd = date.getDate().toString(); + + return (yyyy + (mm[1]?mm:"0"+mm[0]) + (dd[1]?dd:"0"+dd[0])) * 1; + } +} \ No newline at end of file diff --git a/client/src/lib-app/local-store.js b/client/src/lib-app/local-store.js deleted file mode 100644 index 21eac775..00000000 --- a/client/src/lib-app/local-store.js +++ /dev/null @@ -1,54 +0,0 @@ -import LocalStorage from 'localStorage'; - -class LocalStore { - - constructor() { - this.storage = LocalStorage; - } - - initialize() { - if (this.isRememberDataExpired()) { - this.clearRememberData(); - } - - if (!this.getItem('language')) { - this.setItem('language', 'english'); - } - } - - storeRememberData({token, userId, expiration}) { - this.setItem('rememberData-token', token); - this.setItem('rememberData-userId', userId); - this.setItem('rememberData-expiration', expiration); - } - - isRememberDataExpired() { - let rememberData = this.getRememberData(); - - return rememberData.expiration < 2016; - } - - getRememberData() { - return { - token: this.getItem('rememberData-token'), - userId: parseInt(this.getItem('rememberData-userId')), - expiration: parseInt(this.getItem('rememberData-expiration')) - }; - } - - clearRememberData() { - this.setItem('rememberData-token', null); - this.setItem('rememberData-userId', null); - this.setItem('rememberData-expiration', null); - } - - getItem(key) { - return this.storage.getItem(key); - } - - setItem(key, value) { - return this.storage.setItem(key, value); - } -} - -export default new LocalStore(); \ No newline at end of file diff --git a/client/src/lib-app/session-store.js b/client/src/lib-app/session-store.js index 90d2ca16..f6f0bb2e 100644 --- a/client/src/lib-app/session-store.js +++ b/client/src/lib-app/session-store.js @@ -1,8 +1,13 @@ -import SessionStorage from 'sessionstorage'; +import LocalStorage from 'localStorage'; +import date from 'lib-app/date'; class SessionStore { constructor() { - this.storage = SessionStorage; + this.storage = LocalStorage; + + if (!this.getItem('language')) { + this.setItem('language', 'english'); + } } createSession(userId, token) { @@ -18,7 +23,7 @@ class SessionStore { } isLoggedIn() { - return !!this.getItem('userId'); + return !!this.getItem('token'); } closeSession() { @@ -26,6 +31,33 @@ class SessionStore { this.removeItem('token'); } + storeRememberData({token, userId, expiration}) { + this.setItem('rememberData-token', token); + this.setItem('rememberData-userId', userId); + this.setItem('rememberData-expiration', expiration); + } + + isRememberDataExpired() { + let rememberData = this.getRememberData(); + + return rememberData.expiration < date.getCurrentDate(); + } + + getRememberData() { + return { + token: this.getItem('rememberData-token'), + userId: this.getItem('rememberData-userId'), + expiration: this.getItem('rememberData-expiration') + }; + } + + clearRememberData() { + this.removeItem('rememberData-token'); + this.removeItem('rememberData-userId'); + this.removeItem('rememberData-expiration'); + } + + getItem(key) { return this.storage.getItem(key); } @@ -35,7 +67,7 @@ class SessionStore { } removeItem(key) { - return this.storage.removeItem(key); + this.storage.removeItem(key); } } diff --git a/client/src/stores/user-store.js b/client/src/stores/user-store.js index d5189ef8..213123cc 100644 --- a/client/src/stores/user-store.js +++ b/client/src/stores/user-store.js @@ -1,7 +1,6 @@ const Reflux = require('reflux'); const API = require('lib-app/api-call'); const sessionStore = require('lib-app/session-store'); -const localStore = require('lib-app/local-store'); const UserActions = require('actions/user-actions'); const CommonActions = require('actions/common-actions'); @@ -14,29 +13,43 @@ const UserStore = Reflux.createStore({ this.listenTo(UserActions.checkLoginStatus, this.checkLoginStatus); this.listenTo(UserActions.login, this.loginUser); this.listenTo(UserActions.logout, this.logoutUser); + }, + + initSession() { + return API.call({ + path: 'user/check-session', + data: {} + }).then(this.tryLoginIfSessionIsInactive); + }, - if (!this.isLoggedIn()) { - this.loginIfRememberExists(); + tryLoginIfSessionIsInactive(result) { + if (!result.data.sessionActive) { + if (sessionStore.isRememberDataExpired()) { + return this.logoutUser(); + } else { + return this.loginWithRememberData(); + } } }, loginUser(loginData) { - API.call({ + let onSuccessLogin = (loginData.remember) ? this.handleLoginSuccessWithRemember : this.handleLoginSuccess; + let onFailedLogin = (loginData.isAutomatic) ? null : this.handleLoginFail; + + return API.call({ path: 'user/login', - data: loginData, - onSuccess: (loginData.remember) ? this.handleLoginSuccessWithRemember : this.handleLoginSuccess, - onFail: (loginData.isAutomatic) ? null : this.handleLoginFail - }); + data: loginData + }).then(onSuccessLogin, onFailedLogin); }, logoutUser() { - API.call({ - path: 'user/logout', - onSuccess: function () { - sessionStore.closeSession(); - CommonActions.loggedOut(); - this.trigger('LOGOUT'); - }.bind(this) + return API.call({ + path: 'user/logout' + }).then(() => { + sessionStore.closeSession(); + sessionStore.clearRememberData(); + CommonActions.loggedOut(); + this.trigger('LOGOUT'); }); }, @@ -44,20 +57,18 @@ const UserStore = Reflux.createStore({ return sessionStore.isLoggedIn(); }, - loginIfRememberExists() { - let rememberData = localStore.getRememberData(); + loginWithRememberData() { + let rememberData = sessionStore.getRememberData(); - if (!localStore.isRememberDataExpired()) { - UserActions.login({ - userId: rememberData.userId, - rememberToken: rememberData.token, - isAutomatic: true - }); - } + return this.loginUser({ + userId: rememberData.userId, + rememberToken: rememberData.token, + isAutomatic: true + }); }, handleLoginSuccessWithRemember(result) { - localStore.storeRememberData({ + sessionStore.storeRememberData({ token: result.data.rememberToken, userId: result.data.userId, expiration: result.data.rememberExpiration diff --git a/server/controllers/user/check-session.php b/server/controllers/user/check-session.php new file mode 100644 index 00000000..88cc8fd3 --- /dev/null +++ b/server/controllers/user/check-session.php @@ -0,0 +1,20 @@ + 'any', + 'requestData' => [] + ]; + } + + public function handler() { + $session = Session::getInstance(); + + Response::respondSuccess([ + 'sessionActive' => $session->sessionExists() + ]); + } +} From 9535aae0eab668054194db37364b536bbe4c75e5 Mon Sep 17 00:00:00 2001 From: Ivan Diaz Date: Fri, 22 Jul 2016 01:37:32 -0300 Subject: [PATCH 4/4] Ivan - Add SessionStore and UserStore tests [skip ci] --- client/package.json | 3 +- client/src/lib-app/__mocks__/api-call-mock.js | 4 +- .../lib-app/__mocks__/session-store-mock.js | 4 + .../lib-app/__tests__/session-store-test.js | 110 ++++++++++++++ client/src/lib-app/session-store.js | 1 - .../src/stores/__tests__/user-store-test.js | 141 +++++++++++++++--- 6 files changed, 237 insertions(+), 26 deletions(-) create mode 100644 client/src/lib-app/__tests__/session-store-test.js diff --git a/client/package.json b/client/package.json index 9d90a117..be1c6fae 100644 --- a/client/package.json +++ b/client/package.json @@ -64,7 +64,6 @@ "react-google-recaptcha": "^0.5.2", "react-motion": "^0.3.0", "react-router": "^2.4.0", - "reflux": "^0.4.1", - "sessionstorage": "0.0.1" + "reflux": "^0.4.1" } } diff --git a/client/src/lib-app/__mocks__/api-call-mock.js b/client/src/lib-app/__mocks__/api-call-mock.js index 3fcd2003..f7de46f3 100644 --- a/client/src/lib-app/__mocks__/api-call-mock.js +++ b/client/src/lib-app/__mocks__/api-call-mock.js @@ -1,3 +1,5 @@ export default { - call: stub() + call: stub().returns(new Promise(function (resolve) { + resolve(); + })) }; \ No newline at end of file diff --git a/client/src/lib-app/__mocks__/session-store-mock.js b/client/src/lib-app/__mocks__/session-store-mock.js index 5ceed298..c75b4045 100644 --- a/client/src/lib-app/__mocks__/session-store-mock.js +++ b/client/src/lib-app/__mocks__/session-store-mock.js @@ -1,6 +1,10 @@ export default { createSession: stub(), getSessionData: stub().returns({}), + clearRememberData: stub(), + storeRememberData: stub(), + getRememberData: stub(), + isRememberDataExpired: stub().returns(false), isLoggedIn: stub().returns(false), closeSession: stub() }; \ No newline at end of file diff --git a/client/src/lib-app/__tests__/session-store-test.js b/client/src/lib-app/__tests__/session-store-test.js new file mode 100644 index 00000000..883d38a3 --- /dev/null +++ b/client/src/lib-app/__tests__/session-store-test.js @@ -0,0 +1,110 @@ +const LocalStorageMock = { + getItem: stub(), + setItem: stub(), + removeItem: stub() +}; +const date = { getCurrentDate: stub().returns(20160505)}; +const sessionStore = requireUnit('lib-app/session-store', { + 'localStorage': LocalStorageMock, + 'lib-app/date': date +}); + +describe('sessionStore library', function () { + + beforeEach(function () { + LocalStorageMock.getItem = stub(); + LocalStorageMock.setItem = stub(); + LocalStorageMock.removeItem = stub(); + }); + + it('should get, set and remove items from LocalStorage', function () { + sessionStore.getItem('SOME_KEY'); + expect(LocalStorageMock.getItem).to.have.been.calledWith('SOME_KEY'); + + sessionStore.setItem('SOME_KEY', 'SOME_VALUE'); + expect(LocalStorageMock.setItem).to.have.been.calledWith('SOME_KEY', 'SOME_VALUE'); + + sessionStore.removeItem('SOME_KEY'); + expect(LocalStorageMock.removeItem).to.have.been.calledWith('SOME_KEY'); + }); + + it('should create session correctly', function () { + sessionStore.createSession(14, 'TOKEN'); + + expect(LocalStorageMock.setItem).to.have.been.calledWith('userId', 14); + expect(LocalStorageMock.setItem).to.have.been.calledWith('token', 'TOKEN'); + }); + + it('should return session data', function () { + LocalStorageMock.getItem = function (key) { + if (key === 'userId') return 'USER_ID'; + if (key === 'token') return 'TOKEN'; + }; + let sessionData = sessionStore.getSessionData(); + + expect(sessionData.userId).to.equal('USER_ID'); + expect(sessionData.token).to.equal('TOKEN'); + + LocalStorageMock.getItem = stub().returns('ITEM'); + }); + + it('should inform if it is logged in', function () { + LocalStorageMock.getItem = stub().returns('TOKEN'); + expect(sessionStore.isLoggedIn()).to.equal(true); + + LocalStorageMock.getItem = stub().returns(null); + expect(sessionStore.isLoggedIn()).to.equal(false); + }); + + it('should clear session data if session is closed', function () { + sessionStore.closeSession(); + + expect(LocalStorageMock.removeItem).to.have.been.calledWith('userId'); + expect(LocalStorageMock.removeItem).to.have.been.calledWith('token'); + }); + + it('should store remember data', function () { + sessionStore.storeRememberData({ + token: 'SOME_TOKEN', + userId: 12, + expiration: 20160623 + }); + + expect(LocalStorageMock.setItem).to.have.been.calledWith('rememberData-token', 'SOME_TOKEN'); + expect(LocalStorageMock.setItem).to.have.been.calledWith('rememberData-userId', 12); + expect(LocalStorageMock.setItem).to.have.been.calledWith('rememberData-expiration', 20160623); + }); + + it('should inform if remember expired', function () { + LocalStorageMock.getItem = (key) => (key === 'rememberData-expiration') ? 20160505 : null; + date.getCurrentDate.returns(20160506); + expect(sessionStore.isRememberDataExpired()).to.equal(true); + + LocalStorageMock.getItem = (key) => (key === 'rememberData-expiration') ? 20160505 : null; + date.getCurrentDate.returns(20160503); + expect(sessionStore.isRememberDataExpired()).to.equal(false); + }); + + it('should return all remember data', function () { + LocalStorageMock.getItem = function (key) { + if (key === 'rememberData-userId') return 'USER_ID'; + if (key === 'rememberData-token') return 'TOKEN'; + if (key === 'rememberData-expiration') return 'EXPIRATION'; + }; + let rememberData = sessionStore.getRememberData(); + + expect(rememberData.userId).to.equal('USER_ID'); + expect(rememberData.token).to.equal('TOKEN'); + expect(rememberData.expiration).to.equal('EXPIRATION'); + + LocalStorageMock.getItem = stub().returns('ITEM'); + }); + + it('should clear remember data', function () { + sessionStore.clearRememberData(); + + expect(LocalStorageMock.removeItem).to.have.been.calledWith('rememberData-userId'); + expect(LocalStorageMock.removeItem).to.have.been.calledWith('rememberData-token'); + expect(LocalStorageMock.removeItem).to.have.been.calledWith('rememberData-expiration'); + }); +}); \ No newline at end of file diff --git a/client/src/lib-app/session-store.js b/client/src/lib-app/session-store.js index f6f0bb2e..8fbce685 100644 --- a/client/src/lib-app/session-store.js +++ b/client/src/lib-app/session-store.js @@ -57,7 +57,6 @@ class SessionStore { this.removeItem('rememberData-expiration'); } - getItem(key) { return this.storage.getItem(key); } diff --git a/client/src/stores/__tests__/user-store-test.js b/client/src/stores/__tests__/user-store-test.js index 5ccc6790..f6fb225c 100644 --- a/client/src/stores/__tests__/user-store-test.js +++ b/client/src/stores/__tests__/user-store-test.js @@ -16,6 +16,13 @@ const UserStore = requireUnit('stores/user-store', { }); describe('UserStore', function () { + it ('should inform is the user is logged based on SessionStores\' info', function () { + SessionStore.isLoggedIn.returns(true); + expect(UserStore.isLoggedIn()).to.equal(true); + SessionStore.isLoggedIn.returns(false); + expect(UserStore.isLoggedIn()).to.equal(false); + }); + describe('when login user', function () { it('should call /user/login api path', function () { let mockLoginData = {email: 'mock', password: 'mock'}; @@ -23,9 +30,7 @@ describe('UserStore', function () { UserStore.loginUser(mockLoginData); expect(API.call).to.have.been.calledWith({ path: 'user/login', - data: mockLoginData, - onSuccess: sinon.match.func, - onFail: sinon.match.func + data: mockLoginData }); }); @@ -42,10 +47,13 @@ describe('UserStore', function () { spy(UserStore, 'trigger'); CommonActions.logged.reset(); SessionStore.createSession.reset(); - API.call = ({onSuccess}) => {onSuccess(mockSuccessData)}; + API.call.returns({ + then: (resolve) => {resolve(mockSuccessData)} + }); UserStore.loginUser(mockLoginData); + expect(SessionStore.storeRememberData).to.have.not.been.called; expect(SessionStore.createSession).to.have.been.calledWith(12, 'RANDOM_TOKEN'); expect(UserStore.trigger).to.have.been.calledWith('LOGIN_SUCCESS'); expect(CommonActions.logged).to.have.been.called; @@ -54,38 +62,68 @@ describe('UserStore', function () { it('should trigger fail event if login fails', function () { let mockLoginData = {email: 'mock', password: 'mock'}; - let mockSuccessData = { - status: 'success', - data: { - userId: 12, - token: 'RANDOM_TOKEN' - } - }; spy(UserStore, 'trigger'); - API.call = ({onFail}) => {onFail(mockSuccessData)}; + API.call.returns({ + then: (resolve, reject) => {reject()} + }); UserStore.loginUser(mockLoginData); expect(UserStore.trigger).to.have.been.calledWith('LOGIN_FAIL'); UserStore.trigger.restore(); }); + + it('should store remember data if remember is true', function () { + let mockLoginData = {email: 'mock', password: 'mock', remember: true}; + let mockSuccessData = { + status: 'success', + data: { + userId: 12, + token: 'RANDOM_TOKEN', + rememberToken: 'RANDOM_TOKEN_2', + rememberExpiration: 20150822 + } + }; + + spy(UserStore, 'trigger'); + CommonActions.logged.reset(); + SessionStore.createSession.reset(); + API.call.returns({ + then: (resolve) => {resolve(mockSuccessData)} + }); + + UserStore.loginUser(mockLoginData); + + expect(SessionStore.storeRememberData).to.have.been.calledWith({ + token: 'RANDOM_TOKEN_2', + userId: 12, + expiration: 20150822 + }); + expect(SessionStore.createSession).to.have.been.calledWith(12, 'RANDOM_TOKEN'); + expect(UserStore.trigger).to.have.been.calledWith('LOGIN_SUCCESS'); + expect(CommonActions.logged).to.have.been.called; + UserStore.trigger.restore(); + }); }); describe('when login out', function () { it('should call /user/logout api path', function () { - API.call = stub(); + API.call = stub().returns({ + then: (resolve) => {resolve()} + }); UserStore.logoutUser(); expect(API.call).to.have.been.calledWith({ - path: 'user/logout', - onSuccess: sinon.match.func + path: 'user/logout' }); }); it('should delete session, trigger LOGOUT event and inform common action of logout', function () { - API.call = ({onSuccess}) => {onSuccess()}; + API.call = stub().returns({ + then: (resolve) => {resolve()} + }); spy(UserStore, 'trigger'); UserStore.logoutUser(); @@ -96,10 +134,69 @@ describe('UserStore', function () { }) }); - it ('should inform is the user is logged based on SessionStores\' info', function () { - SessionStore.isLoggedIn.returns(true); - expect(UserStore.isLoggedIn()).to.equal(true); - SessionStore.isLoggedIn.returns(false); - expect(UserStore.isLoggedIn()).to.equal(false); - }); + describe('when calling initSession', function () {{ + + it('should check if session is active in the API', function () { + let mockSuccessData = { + status: 'success', + data: { + sessionActive: true + } + }; + API.call = stub().returns({ + then: (resolve) => {resolve(mockSuccessData)} + }); + + UserStore.initSession(); + + expect(API.call).to.have.been.calledWith({ + path: 'user/check-session', + data: {} + }); + }); + + describe('and no session is active', function () { + beforeEach(function() { + let mockSuccessData = { + status: 'success', + data: { + sessionActive: false + } + }; + API.call = stub().returns({ + then: (resolve) => {resolve(mockSuccessData)} + }); + }); + + it('should log out and delete remember data if expired', function () { + SessionStore.isRememberDataExpired.returns(true); + SessionStore.clearRememberData.reset(); + + UserStore.initSession(); + + expect(SessionStore.clearRememberData).to.have.been.called; + expect(SessionStore.closeSession).to.have.been.called; + expect(CommonActions.loggedOut).to.have.been.called; + }); + + it('should login with remember data', function () { + SessionStore.isRememberDataExpired.returns(false); + SessionStore.getRememberData.returns({ + userId: 'REMEMBER_USER_ID', + token: 'REMEMBER_TOKEN', + expiration: 20160721 + }); + + UserStore.initSession(); + + expect(API.call).to.have.been.calledWithMatch({ + path: 'user/login', + data: { + userId: 'REMEMBER_USER_ID', + rememberToken: 'REMEMBER_TOKEN' + } + }); + }); + }); + }}) });