diff --git a/src/helpers/hideEmail/hideEmail.test.js b/src/helpers/hideEmail/hideEmail.test.js new file mode 100644 index 000000000..42707380f --- /dev/null +++ b/src/helpers/hideEmail/hideEmail.test.js @@ -0,0 +1,8 @@ +import hideEmail from './hideEmail'; + +describe('hideEmail', () => { + it('masks email addresses', () => { + expect(hideEmail('test@example.com')).toEqual('te**@e******.***'); + expect(hideEmail('monkey@bagel.co.uk')).toEqual('mo****@b****.*****'); + }); +}); diff --git a/src/loginServices.js b/src/loginServices.js index 5293cd35f..d60617ed6 100644 --- a/src/loginServices.js +++ b/src/loginServices.js @@ -224,7 +224,7 @@ function dispatchLocale(url, store, tenant) { mode: 'cors', }) .then((response) => { - if (response.status === 200) { + if (response.ok) { response.json().then((json) => { if (json.configs?.length) { const localeValues = JSON.parse(json.configs[0].value); @@ -237,6 +237,7 @@ function dispatchLocale(url, store, tenant) { } }); } + return response; }); } @@ -303,7 +304,7 @@ export function getPlugins(okapiUrl, store, tenant) { mode: 'cors', }) .then((response) => { - if (response.status < 400) { + if (response.ok) { response.json().then((json) => { const configs = json.configs?.reduce((acc, val) => ({ ...acc, @@ -333,9 +334,7 @@ export function getBindings(okapiUrl, store, tenant) { }) .then((response) => { let bindings = {}; - if (response.status >= 400) { - store.dispatch(setBindings(bindings)); - } else { + if (response.ok) { response.json().then((json) => { const configs = json.configs; if (Array.isArray(configs) && configs.length > 0) { @@ -350,6 +349,8 @@ export function getBindings(okapiUrl, store, tenant) { } store.dispatch(setBindings(bindings)); }); + } else { + store.dispatch(setBindings(bindings)); } return response; }); @@ -578,14 +579,14 @@ export function createOkapiSession(okapiUrl, store, tenant, token, data) { export function getSSOEnabled(okapiUrl, store, tenant) { return fetch(`${okapiUrl}/saml/check`, { headers: { 'X-Okapi-Tenant': tenant, 'Accept': 'application/json' } }) .then((response) => { - if (response.status >= 400) { - store.dispatch(checkSSO(false)); - return null; - } else { + if (response.ok) { return response.json() .then((json) => { store.dispatch(checkSSO(json.active)); }); + } else { + store.dispatch(checkSSO(false)); + return null; } }) .catch(() => { @@ -601,7 +602,7 @@ export function getSSOEnabled(okapiUrl, store, tenant) { * @param {object} resp fetch Response */ function processSSOLoginResponse(resp) { - if (resp.status < 400) { + if (resp.ok) { resp.json().then((json) => { const form = document.getElementById('ssoForm'); if (json.bindingMethod === 'POST') { diff --git a/src/loginServices.test.js b/src/loginServices.test.js index bec0d559f..f5ec3246a 100644 --- a/src/loginServices.test.js +++ b/src/loginServices.test.js @@ -2,8 +2,12 @@ import localforage from 'localforage'; import { createOkapiSession, + getBindings, + getLocale, + getPlugins, getOkapiSession, getTokenExpiry, + getUserLocale, handleLoginError, loadTranslations, logout, @@ -24,10 +28,10 @@ import { clearOkapiToken, setCurrentPerms, setLocale, - // setTimezone, - // setCurrency, - // setPlugins, - // setBindings, + setTimezone, + setCurrency, + setPlugins, + setBindings, // setTranslations, setAuthError, // checkSSO, @@ -53,6 +57,7 @@ const mockFetchSuccess = (data) => { global.fetch = jest.fn().mockImplementation(() => ( Promise.resolve({ ok: true, + status: 200, json: () => Promise.resolve(data), headers: new Map(), }) @@ -541,3 +546,69 @@ describe('logout', () => { }); }); }); + +describe('getLocale', () => { + it('dispatches setTimezone, setCurrency', async () => { + const value = { timezone: 'America/New_York', currency: 'USD' }; + mockFetchSuccess({ configs: [{ value: JSON.stringify(value) }] }); + const store = { + dispatch: jest.fn(), + getState: () => ({ okapi: { } }), + }; + await getLocale('url', store, 'tenant'); + expect(store.dispatch).toHaveBeenCalledWith(setTimezone(value.timezone)); + expect(store.dispatch).toHaveBeenCalledWith(setCurrency(value.currency)); + mockFetchCleanUp(); + }); +}); + +describe('getUserLocale', () => { + it('dispatches setTimezone, setCurrency', async () => { + const value = { locale: 'en-US', timezone: 'America/New_York', currency: 'USD' }; + mockFetchSuccess({ configs: [{ value: JSON.stringify(value) }] }); + const store = { + dispatch: jest.fn(), + getState: () => ({ okapi: { } }), + }; + await getUserLocale('url', store, 'tenant'); + expect(store.dispatch).toHaveBeenCalledWith(setTimezone(value.timezone)); + expect(store.dispatch).toHaveBeenCalledWith(setCurrency(value.currency)); + mockFetchCleanUp(); + }); +}); + +describe('getPlugins', () => { + it('dispatches setPlugins', async () => { + const configs = [ + { configName: 'find-user', value: '@folio/plugin-hello-waldo' }, + { configName: 'find-water', value: '@folio/plugin-dowsing-rod' }, + ]; + mockFetchSuccess({ configs }); + const store = { + dispatch: jest.fn(), + getState: () => ({ okapi: { } }), + }; + await getPlugins('url', store, 'tenant'); + + const mappedConfigs = configs.reduce((acc, val) => ({ + ...acc, + [val.configName]: val.value, + }), {}); + expect(store.dispatch).toHaveBeenCalledWith(setPlugins(mappedConfigs)); + mockFetchCleanUp(); + }); +}); + +describe('getBindings', () => { + it('dispatches setBindings', async () => { + const value = { key: 'value' }; + mockFetchSuccess({ configs: [{ value: JSON.stringify(value) }] }); + const store = { + dispatch: jest.fn(), + getState: () => ({ okapi: { } }), + }; + await getBindings('url', store, 'tenant'); + expect(store.dispatch).toHaveBeenCalledWith(setBindings(value)); + mockFetchCleanUp(); + }); +}); diff --git a/src/okapiActions.test.js b/src/okapiActions.test.js index 9ac82f56d..0c5986ec3 100644 --- a/src/okapiActions.test.js +++ b/src/okapiActions.test.js @@ -1,9 +1,85 @@ import { + checkSSO, + clearCurrentUser, + clearOkapiToken, + clearRtrTimeout, + setAuthError, + setBindings, + setCurrency, + setCurrentPerms, + setCurrentUser, setIsAuthenticated, + setLocale, setLoginData, + setOkapiReady, + setOkapiToken, + setPlugins, + setRtrTimeout, + setServerDown, + setSessionData, + setSinglePlugin, + setTimezone, + setTokenExpiration, + setTranslations, + toggleRtrModal, updateCurrentUser, } from './okapiActions'; + +describe('checkSSO', () => { + it('sets ssoEnabled', () => { + expect(checkSSO('monkey').ssoEnabled).toBe('monkey'); + }); +}); + +describe('clearCurrentUser', () => { + it('returns an action with a type', () => { + expect(clearCurrentUser()).toHaveProperty('type'); + }); +}); + +describe('clearOkapiToken', () => { + it('returns an action with a type', () => { + expect(clearOkapiToken()).toHaveProperty('type'); + }); +}); + +describe('clearRtrTimeout', () => { + it('returns an action with a type', () => { + expect(clearRtrTimeout()).toHaveProperty('type'); + }); +}); + +describe('setAuthError', () => { + it('sets message', () => { + expect(setAuthError('message').message).toBe('message'); + }); +}); + +describe('setBindings', () => { + it('sets bindings', () => { + expect(setBindings('bindings').bindings).toBe('bindings'); + }); +}); + +describe('setCurrency', () => { + it('sets currency', () => { + expect(setCurrency('currency').currency).toBe('currency'); + }); +}); + +describe('setCurrentPerms', () => { + it('sets currentPerms', () => { + expect(setCurrentPerms('currentPerms').currentPerms).toBe('currentPerms'); + }); +}); + +describe('setCurrentUser', () => { + it('sets currentUser', () => { + expect(setCurrentUser('currentUser').currentUser).toBe('currentUser'); + }); +}); + describe('setIsAuthenticated', () => { it('handles truthy values', () => { expect(setIsAuthenticated('truthy').isAuthenticated).toBe(true); @@ -18,6 +94,12 @@ describe('setIsAuthenticated', () => { }); }); +describe('setLocale', () => { + it('sets locale', () => { + expect(setLocale('locale').locale).toBe('locale'); + }); +}); + describe('setLoginData', () => { it('receives given data in "loginData"', () => { const av = { monkey: 'bagel' }; @@ -25,6 +107,74 @@ describe('setLoginData', () => { }); }); +describe('setOkapiReady', () => { + it('returns an action with a type', () => { + expect(setOkapiReady()).toHaveProperty('type'); + }); +}); + +describe('setOkapiToken', () => { + it('sets token', () => { + expect(setOkapiToken('token').token).toBe('token'); + }); +}); + +describe('setPlugins', () => { + it('sets plugins', () => { + expect(setPlugins('plugins').plugins).toBe('plugins'); + }); +}); + +describe('setRtrTimeout', () => { + it('sets rtrTimeout', () => { + expect(setRtrTimeout('rtrTimeout').rtrTimeout).toBe('rtrTimeout'); + }); +}); + +describe('setServerDown', () => { + it('returns an action with a type', () => { + expect(setServerDown()).toHaveProperty('type'); + }); +}); + +describe('setSessionData', () => { + it('sets session', () => { + expect(setSessionData('session').session).toBe('session'); + }); +}); + +describe('setSinglePlugin', () => { + it('sets name and value', () => { + const ret = setSinglePlugin('name', 'value'); + expect(ret.name).toBe('name'); + expect(ret.value).toBe('value'); + }); +}); + +describe('setTimezone', () => { + it('sets timezone', () => { + expect(setTimezone('timezone').timezone).toBe('timezone'); + }); +}); + +describe('setTokenExpiration', () => { + it('sets tokenExpiration', () => { + expect(setTokenExpiration('tokenExpiration').tokenExpiration).toBe('tokenExpiration'); + }); +}); + +describe('setTranslations', () => { + it('sets translations', () => { + expect(setTranslations('translations').translations).toBe('translations'); + }); +}); + +describe('toggleRtrModal', () => { + it('sets isVisible', () => { + expect(toggleRtrModal(true).isVisible).toBe(true); + }); +}); + describe('updateCurrentUser', () => { it('receives given data in "data"', () => { const av = { monkey: 'bagel' }; diff --git a/src/okapiReducer.test.js b/src/okapiReducer.test.js index 5c0cb8021..e677a884b 100644 --- a/src/okapiReducer.test.js +++ b/src/okapiReducer.test.js @@ -1,10 +1,37 @@ +import { + checkSSO, + clearCurrentUser, + clearOkapiToken, + clearRtrTimeout, + setAuthError, + setBindings, + setCurrency, + setCurrentPerms, + setCurrentUser, + setIsAuthenticated, + setLocale, + setLoginData, + setOkapiReady, + setOkapiToken, + setPlugins, + setRtrTimeout, + setServerDown, + setSessionData, + setSinglePlugin, + setTimezone, + setTokenExpiration, + setTranslations, + toggleRtrModal, + updateCurrentUser, +} from './okapiActions'; + import okapiReducer from './okapiReducer'; describe('okapiReducer', () => { describe('SET_IS_AUTHENTICATED', () => { it('sets isAuthenticated to true', () => { const isAuthenticated = true; - const o = okapiReducer({}, { type: 'SET_IS_AUTHENTICATED', isAuthenticated: true }); + const o = okapiReducer({}, setIsAuthenticated(true)); expect(o).toMatchObject({ isAuthenticated }); }); @@ -14,7 +41,7 @@ describe('okapiReducer', () => { rtrTimeout: 123, }; const ct = jest.spyOn(window, 'clearTimeout'); - const o = okapiReducer(state, { type: 'SET_IS_AUTHENTICATED', isAuthenticated: false }); + const o = okapiReducer(state, setIsAuthenticated(false)); expect(o.isAuthenticated).toBe(false); expect(o.rtrModalIsVisible).toBe(false); expect(o.rtrTimeout).toBe(undefined); @@ -24,14 +51,14 @@ describe('okapiReducer', () => { it('SET_LOGIN_DATA', () => { const loginData = 'loginData'; - const o = okapiReducer({}, { type: 'SET_LOGIN_DATA', loginData }); + const o = okapiReducer({}, setLoginData(loginData)); expect(o).toMatchObject({ loginData }); }); it('UPDATE_CURRENT_USER', () => { const initialState = { funky: 'chicken' }; const data = { monkey: 'bagel' }; - const o = okapiReducer(initialState, { type: 'UPDATE_CURRENT_USER', data }); + const o = okapiReducer(initialState, updateCurrentUser(data)); expect(o).toMatchObject({ ...initialState, currentUser: { ...data } }); }); @@ -51,7 +78,7 @@ describe('okapiReducer', () => { }, tenant: 'institutional', }; - const o = okapiReducer(initialState, { type: 'SET_SESSION_DATA', session }); + const o = okapiReducer(initialState, setSessionData(session)); const { user, perms, ...rest } = session; expect(o).toMatchObject({ ...initialState, @@ -70,7 +97,7 @@ describe('okapiReducer', () => { const newState = { rtrTimeout: 997 }; - const o = okapiReducer(state, { type: 'SET_RTR_TIMEOUT', rtrTimeout: newState.rtrTimeout }); + const o = okapiReducer(state, setRtrTimeout(newState.rtrTimeout)); expect(o).toMatchObject(newState); expect(ct).toHaveBeenCalledWith(state.rtrTimeout); @@ -83,14 +110,129 @@ describe('okapiReducer', () => { rtrTimeout: 991, }; - const o = okapiReducer(state, { type: 'CLEAR_RTR_TIMEOUT' }); + const o = okapiReducer(state, clearRtrTimeout()); expect(o).toMatchObject({}); expect(ct).toHaveBeenCalledWith(state.rtrTimeout); }); it('TOGGLE_RTR_MODAL', () => { const rtrModalIsVisible = true; - const o = okapiReducer({}, { type: 'TOGGLE_RTR_MODAL', isVisible: true }); + const o = okapiReducer({}, toggleRtrModal(true)); expect(o).toMatchObject({ rtrModalIsVisible }); }); + + it('SET_OKAPI_TOKEN', () => { + const token = 't'; + const o = okapiReducer({}, setOkapiToken(token)); + expect(o).toMatchObject({ token }); + }); + + it('CLEAR_OKAPI_TOKEN', () => { + const o = okapiReducer({}, clearOkapiToken()); + expect(o).toMatchObject({ token: null }); + }); + + it('SET_CURRENT_USER', () => { + const state = { currentUser: 'fred' }; + const currentUser = 'george'; + const o = okapiReducer(state, setCurrentUser(currentUser)); + expect(o).toMatchObject({ currentUser }); + }); + + it('SET_LOCALE', () => { + const state = { locale: 'fred' }; + const locale = 'george'; + const o = okapiReducer(state, setLocale(locale)); + expect(o).toMatchObject({ locale }); + }); + + it('SET_TIMEZONE', () => { + const state = { timezone: 'fred' }; + const timezone = 'george'; + const o = okapiReducer(state, setTimezone(timezone)); + expect(o).toMatchObject({ timezone }); + }); + + it('SET_CURRENCY', () => { + const state = { currency: 'fred' }; + const currency = 'george'; + const o = okapiReducer(state, setCurrency(currency)); + expect(o).toMatchObject({ currency }); + }); + + it('SET_PLUGINS', () => { + const state = { plugins: 'fred' }; + const plugins = 'george'; + const o = okapiReducer(state, setPlugins(plugins)); + expect(o).toMatchObject({ plugins }); + }); + + it('SET_SINGLE_PLUGIN', () => { + const state = { plugins: { tortured: 'poets' } }; + const o = okapiReducer(state, setSinglePlugin('tortured', 'apostrophes are nice')); + expect(o).toMatchObject({ plugins: { tortured: 'apostrophes are nice' } }); + }); + + it('SET_BINDINGS', () => { + const state = { bindings: 'fred' }; + const bindings = 'george'; + const o = okapiReducer(state, setBindings(bindings)); + expect(o).toMatchObject({ bindings }); + }); + + it('SET_CURRENT_PERMS', () => { + const state = { currentPerms: 'fred' }; + const currentPerms = 'george'; + const o = okapiReducer(state, setCurrentPerms(currentPerms)); + expect(o).toMatchObject({ currentPerms }); + }); + + it('SET_TOKEN_EXPIRATION', () => { + const state = { loginData: { tokenExpiration: 'fred' } }; + const tokenExpiration = 'george'; + const o = okapiReducer(state, setTokenExpiration(tokenExpiration)); + expect(o).toMatchObject({ loginData: { tokenExpiration } }); + }); + + it('CLEAR_CURRENT_USER', () => { + const state = { currentUser: 'fred', currentPerms: 'all the perms' }; + const o = okapiReducer(state, clearCurrentUser()); + expect(o).toMatchObject({ currentUser: {}, currentPerms: {} }); + }); + + it('SET_AUTH_FAILURE', () => { + const state = { authFailure: 'fred' }; + const authFailure = 'george'; + const o = okapiReducer(state, setAuthError(authFailure)); + expect(o).toMatchObject({ authFailure }); + }); + + it('SET_TRANSLATIONS', () => { + const state = { translations: 'fred' }; + const translations = 'george'; + const o = okapiReducer(state, setTranslations(translations)); + expect(o).toMatchObject({ translations }); + }); + + it('CHECK_SSO', () => { + const state = { ssoEnabled: 'fred' }; + const ssoEnabled = 'george'; + const o = okapiReducer(state, checkSSO(ssoEnabled)); + expect(o).toMatchObject({ ssoEnabled }); + }); + + it('OKAPI_READY', () => { + const state = { okapiReady: false }; + const okapiReady = true; + const o = okapiReducer(state, setOkapiReady()); + expect(o).toMatchObject({ okapiReady }); + }); + + it('SERVER_DOWN', () => { + const state = { serverDown: false }; + const serverDown = true; + const o = okapiReducer(state, setServerDown()); + expect(o).toMatchObject({ serverDown }); + }); }); + diff --git a/src/processBadResponse.test.js b/src/processBadResponse.test.js new file mode 100644 index 000000000..ff086694c --- /dev/null +++ b/src/processBadResponse.test.js @@ -0,0 +1,119 @@ +import processBadResponse from './processBadResponse'; + +import { defaultErrors } from './constants'; +import { setAuthError } from './okapiActions'; + +describe('processBadResponse', () => { + it('shows default error if response processing throws', async () => { + const dispatch = jest.fn(); + const response = { + status: 404, + json: '', // response.json is not a function + }; + + const message = setAuthError([defaultErrors.DEFAULT_LOGIN_CLIENT_ERROR]); + + await processBadResponse(dispatch, response); + expect(dispatch).toHaveBeenCalledWith(message); + }); + + it('shows default error without defaultClientError', async () => { + const dispatch = jest.fn(); + const response = { + json: () => Promise.resolve({ + errorMessage: 'monkey', + }) + }; + const message = setAuthError([defaultErrors.DEFAULT_LOGIN_CLIENT_ERROR]); + + await processBadResponse(dispatch, response); + expect(dispatch).toHaveBeenCalledWith(message); + }); + + it('shows login server error given 404', async () => { + const dispatch = jest.fn(); + const message = setAuthError([defaultErrors.DEFAULT_LOGIN_SERVER_ERROR]); + const response = { + status: 404, + json: () => Promise.resolve({}), + }; + + await processBadResponse(dispatch, response); + expect(dispatch).toHaveBeenCalledWith(message); + }); + + it('shows login server error given 500', async () => { + const dispatch = jest.fn(); + const message = setAuthError([defaultErrors.DEFAULT_LOGIN_SERVER_ERROR]); + const response = { + status: 500, + json: () => Promise.resolve({}), + }; + + await processBadResponse(dispatch, response); + expect(dispatch).toHaveBeenCalledWith(message); + }); + + it('shows given error given 400', async () => { + const dispatch = jest.fn(); + const message = setAuthError(['monkey']); + const response = { + status: 400, + json: () => Promise.resolve({}), + }; + + await processBadResponse(dispatch, response, 'monkey'); + expect(dispatch).toHaveBeenCalledWith(message); + }); + + it('shows login error given 422 and errors', async () => { + const dispatch = jest.fn(); + const error = 'thunder-chicken'; + const message = setAuthError([error]); + const response = { + status: 422, + json: () => Promise.resolve({ errors: [error] }), + }; + + await processBadResponse(dispatch, response, message); + expect(dispatch).toHaveBeenCalledWith(message); + }); + + it('shows login error given 422 and error string', async () => { + const dispatch = jest.fn(); + const error = 'funky-chicken'; + const message = setAuthError([error]); + const response = { + status: 422, + json: () => Promise.resolve(JSON.stringify({ errors: [error] })), + }; + + await processBadResponse(dispatch, response, message); + expect(dispatch).toHaveBeenCalledWith(message); + }); + + it('shows login error given 422 and string', async () => { + const dispatch = jest.fn(); + const response = { + status: 422, + json: () => Promise.resolve(JSON.stringify({ no: 'error' })), + }; + + const message = setAuthError([defaultErrors.DEFAULT_LOGIN_CLIENT_ERROR]); + await processBadResponse(dispatch, response, message); + expect(dispatch).toHaveBeenCalledWith(message); + }); + + it('shows login error given 422 and string', async () => { + const dispatch = jest.fn(); + const response = { + status: 422, + json: () => Promise.resolve('nope, not JSON'), + }; + + const message = setAuthError([defaultErrors.DEFAULT_LOGIN_CLIENT_ERROR]); + await processBadResponse(dispatch, response, message); + expect(dispatch).toHaveBeenCalledWith(message); + }); +}); +