Skip to content

Commit

Permalink
test - login saga tests
Browse files Browse the repository at this point in the history
  • Loading branch information
tony-blockchain committed Jun 19, 2018
1 parent b6914f1 commit 93aef94
Show file tree
Hide file tree
Showing 2 changed files with 269 additions and 42 deletions.
37 changes: 20 additions & 17 deletions packages/blockchain-wallet-v4-frontend/src/data/auth/sagas.js
Expand Up @@ -10,8 +10,12 @@ import * as C from 'services/AlertService'
import { askSecondPasswordEnhancer, promptForSecondPassword, forceSyncWallet } from 'services/SagaService'
import { Types } from 'blockchain-wallet-v4/src'

export const logLocation = 'auth/sagas'
export const defaultLoginErrorMessage = 'Error logging into your wallet'
// TODO: make this a global error constant
export const wrongWalletPassErrorMessage = 'wrong_wallet_password'

export default ({ api, coreSagas }) => {
const logLocation = 'auth/sagas'
const upgradeWallet = function * () {
try {
let password = yield call(promptForSecondPassword)
Expand Down Expand Up @@ -136,14 +140,14 @@ export default ({ api, coreSagas }) => {
try {
yield call(coreSagas.wallet.fetchWalletSaga, { guid, session, password })
yield call(loginRoutineSaga, mobileLogin)
} catch (e) {
if (e.auth_type > 0) {
yield put(actions.auth.setAuthType(e.auth_type))
} catch (error) {
if (error && error.auth_type > 0) {
yield put(actions.auth.setAuthType(error.auth_type))
yield put(actions.alerts.displayInfo(C.TWOFA_REQUIRED_INFO))
yield put(actions.auth.loginFailure())
} else {
yield put(actions.auth.loginFailure('wrong_wallet_password'))
yield put(actions.logs.logErrorMessage(logLocation, 'login', e))
yield put(actions.auth.loginFailure(wrongWalletPassErrorMessage))
yield put(actions.logs.logErrorMessage(logLocation, 'login', error))
}
}
} else {
Expand All @@ -152,18 +156,16 @@ export default ({ api, coreSagas }) => {
} else if (initialError.isRight && initialError.value) {
// general error
yield put(actions.auth.loginFailure(initialError.value))
} else if (error && error.auth_type > 0) {
// 2fa required
// dispatch state change to show form
yield put(actions.auth.loginFailure())
yield put(actions.auth.setAuthType(error.auth_type))
yield put(actions.alerts.displayInfo(C.TWOFA_REQUIRED_INFO))
} else {
// 2FA errors
if (error.auth_type > 0) { // 2fa required
// dispatch state change to show form
yield put(actions.auth.loginFailure())
yield put(actions.auth.setAuthType(error.auth_type))
yield put(actions.alerts.displayInfo(C.TWOFA_REQUIRED_INFO))
} else if (error.message) {
yield put(actions.auth.loginFailure(error.message))
} else {
yield put(actions.auth.loginFailure(error || 'Error logging into your wallet'))
}
const errorMessage =
prop('message', error) || error || defaultLoginErrorMessage
yield put(actions.auth.loginFailure(errorMessage))
}
}
}
Expand Down Expand Up @@ -321,6 +323,7 @@ export default ({ api, coreSagas }) => {
logoutClearReduxStore,
loginRoutineSaga,
mobileLogin,
pollingSession,
register,
remindGuid,
reset2fa,
Expand Down
274 changes: 249 additions & 25 deletions packages/blockchain-wallet-v4-frontend/src/data/auth/sagas.spec.js
@@ -1,49 +1,273 @@
import { call, put, select } from 'redux-saga/effects'
import { put, select } from 'redux-saga/effects'

import { expectSaga, testSaga } from 'redux-saga-test-plan'
import { dissocPath, curry } from 'ramda'

import * as selectors from '../selectors.js'
import * as actions from '../actions.js'
import authSagas from './sagas'
import authSagas, {
defaultLoginErrorMessage,
wrongWalletPassErrorMessage,
logLocation
} from './sagas'
import * as C from 'services/AlertService'

// NB: can be used as an utilite in multiple saga tests
const assertDisplayAction = curry((expectedAction, incomingAction) => {
const dissocId = dissocPath(['PUT', 'action', 'payload', 'id'])
expect(dissocId(incomingAction)).toEqual(dissocId(put(expectedAction)))
})

describe('authSagas', () => {
let api = { obtainSessionToken: () => {} }
let coreSagas = { wallet: { fetchWalletSaga: () => {} } }

describe('*login', () => {
let { login, loginRoutineSaga } = authSagas({ api, coreSagas })
let payload = {guid: '123abc456def', password: 'blockchain', code: undefined, sharedKey: undefined, mobileLogin: undefined}
let gen = login({ payload })
describe('login flow', () => {
let { login, loginRoutineSaga, pollingSession } = authSagas({
api,
coreSagas
})
const guid = '123abc456def'
const password = 'blockchain'
const sessionIdStub = 'id'
let payload = {
guid,
password,
code: undefined,
sharedKey: undefined,
mobileLogin: undefined
}
let saga = testSaga(login, { payload })

it('should select getSession', () => {
expect(gen.next().value).toEqual(select(selectors.session.getSession, '123abc456def'))
it('should select session', () => {
saga.next().select(selectors.session.getSession, guid)
})
it('should call obtainSessionToken', () => {
expect(gen.next().value).toEqual(call(api.obtainSessionToken))

it('should call obtainSessionToken if session was not selected', () => {
saga.next().call(api.obtainSessionToken)
})

it('should put saveSession', () => {
expect(gen.next('sessionToken').value).toEqual(put(actions.session.saveSession({'123abc456def': 'sessionToken'})))
saga
.next(sessionIdStub)
.put(actions.session.saveSession({ [guid]: sessionIdStub }))
})

it('should cache guid', () => {
expect(gen.next('123abc456def').value).toEqual(put(actions.cache.guidEntered('123abc456def')))
saga.next().put(actions.cache.guidEntered(guid))
})
it('should put login loading', () => {
expect(gen.next().value).toEqual(put(actions.auth.loginLoading()))

it('should change login loading state', () => {
saga.next().put(actions.auth.loginLoading())
})

it('should fetch wallet', () => {
const { guid, password, code, sharedKey } = payload
expect(gen.next().value).toEqual(call(coreSagas.wallet.fetchWalletSaga, { guid, password, code, sharedKey, session: 'sessionToken' }))
saga.next().call(coreSagas.wallet.fetchWalletSaga, {
guid,
password,
code,
sharedKey,
session: sessionIdStub
})
})

it('should call login routine', () => {
const { mobileLogin } = payload
expect(gen.next().value).toEqual(call(loginRoutineSaga, mobileLogin))
})
describe('catch', () => {
let gen = login({ payload })
gen.next()
gen.next()
gen.next('sessionToken')
gen.next('123abc456def')
expect(gen.next().value).toEqual(put(actions.auth.loginLoading()))
expect(gen.throw({ message: 'error' }).value).toEqual(put(actions.auth.loginFailure('error')))
saga
.next()
.call(loginRoutineSaga, mobileLogin)
.next()
.isDone()
})

it('should not call obtainSessionToken if session was selected and use selected session in further calls', () => {
const { guid, password, code, sharedKey } = payload
return expectSaga(login, { payload })
.provide([[select(selectors.session.getSession, guid), sessionIdStub]])
.not.call(api.obtainSessionToken)
.put(actions.session.saveSession({ [guid]: sessionIdStub }))
.call(coreSagas.wallet.fetchWalletSaga, {
guid,
password,
code,
sharedKey,
session: sessionIdStub
})
.run()
})

describe('error handling', () => {
const beforeError = 'beforeError'
beforeAll(() => {
saga.restart()
// Pass through steps before error
saga
.next()
.next()
.next(sessionIdStub)
.next(guid)
.next()
.save(beforeError)
})

describe('authorization error flow', () => {
const beforeAuth = 'beforeAuth'
beforeAll(() => {
saga.restore(beforeError).save(beforeError)
})

it('should display info that authorization is required', () => {
const message = 'error'
saga
.throw(JSON.stringify({ authorization_required: message }))
.inspect(
assertDisplayAction(
actions.alerts.displayInfo(C.AUTHORIZATION_REQUIRED_INFO)
)
)
})

it('should poll for session', () => {
saga
.next()
.call(pollingSession, sessionIdStub)
.save(beforeAuth)
})

it('should show session error if not authorized', () => {
saga
.next()
.inspect(
assertDisplayAction(
actions.alerts.displayError(C.WALLET_SESSION_ERROR)
)
)
.next()
.isDone()
})

describe('authorized flow', () => {
const before2FAError = 'before2FAError'
it('should fetch wallet', () => {
saga
.restore(beforeAuth)
.next(true)
.call(coreSagas.wallet.fetchWalletSaga, {
guid,
password,
session: sessionIdStub
})
})

it('should call logine routine', () => {
const { mobileLogin } = payload
saga.next().call(loginRoutineSaga, mobileLogin)
})

it('should follow 2FA low on auth error', () => {
const authType = 1
saga
.save(before2FAError)
.throw({ auth_type: authType })
.put(actions.auth.setAuthType(authType))
.next()
.inspect(
assertDisplayAction(
actions.alerts.displayInfo(C.TWOFA_REQUIRED_INFO)
)
)
.next()
.put(actions.auth.loginFailure())
.next()
.isDone()
})

it('should show wrong password message & log on other errors', () => {
const error = null

saga
.restore(before2FAError)
.throw(error)
.put(actions.auth.loginFailure(wrongWalletPassErrorMessage))
.next()
.put(actions.logs.logErrorMessage(logLocation, 'login', error))
.next()
.isDone()
})
})
})

describe('initial error', () => {
beforeAll(() => {
saga.restore(beforeError).save(beforeError)
})

it('should trigger login failure', () => {
const message = 'error'
saga
.throw(JSON.stringify({ initial_error: message }))
.put(actions.auth.loginFailure(message))
.next()
.isDone()
})
})

describe('2FA errros', () => {
const authType = 1
beforeAll(() => {
saga.restore(beforeError).save(beforeError)
})

it('should trigger login failure', () => {
saga.throw({ auth_type: authType }).put(actions.auth.loginFailure())
})

it('should set authType to one provided by error', () => {
saga.next().put(actions.auth.setAuthType(authType))
})

it('should display 2fa required info', () => {
saga
.next()
.inspect(
assertDisplayAction(
actions.alerts.displayInfo(C.TWOFA_REQUIRED_INFO)
)
)
.next()
.isDone()
})
})

describe('unknown errros', () => {
const errorMessage = 'error'
beforeEach(() => {
saga.restore(beforeError).save(beforeError)
})

it('should put loginFailure action with message when error hash it', () => {
saga
.throw({ message: errorMessage })
.put(actions.auth.loginFailure(errorMessage))
})

it('should put loginFailure action with error when error is a string', () => {
saga
.throw(errorMessage)
.put(actions.auth.loginFailure(errorMessage))
.next()
.isDone()
})

it('should put loginFailure action with default error message', () => {
saga
.throw(null)
.put(actions.auth.loginFailure(defaultLoginErrorMessage))
.next()
.isDone()
})
})
})
})
})

0 comments on commit 93aef94

Please sign in to comment.