Merge pull request #100 from Human-Connection/refactor_login_method_and_setup_unit_tests

Refactor login method and setup unit tests
This commit is contained in:
Grzegorz Leoniec 2018-12-24 16:46:09 +01:00 committed by GitHub
commit 241a14f09c
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
3 changed files with 195 additions and 56 deletions

View File

@ -44,6 +44,19 @@ const logout = () => {
cy.location('pathname').should('contain', '/login') // we're out cy.location('pathname').should('contain', '/login') // we're out
} }
const lastColumnIsSortedInDescendingOrder = () => {
cy.get('tbody')
.find('tr td:last-child')
.then(last_column => {
cy.wrap(last_column)
const values = last_column
.map((i, td) => parseInt(td.textContent))
.toArray()
const ordered_descending = values.slice(0).sort((a, b) => b - a)
return cy.wrap(values).should('deep.eq', ordered_descending)
})
}
Given('I am logged in', () => { Given('I am logged in', () => {
login('admin@example.org', 1234) login('admin@example.org', 1234)
}) })
@ -52,11 +65,11 @@ Given('we have a selection of tags and categories as well as posts', () => {
// TODO: use db factories instead of seed data // TODO: use db factories instead of seed data
}) })
Given('my account has the following details:', (table) => { Given('my account has the following details:', table => {
// TODO: use db factories instead of seed data // TODO: use db factories instead of seed data
}) })
Given('my user account has the role {string}', (role) => { Given('my user account has the role {string}', role => {
// TODO: use db factories instead of seed data // TODO: use db factories instead of seed data
}) })
@ -127,28 +140,22 @@ When('I navigate to the administration dashboard', () => {
.click() .click()
}) })
When('I click on {string}', linkOrButton => { When(`I click on {string}`, linkOrButton => {
cy.contains(linkOrButton).click() cy.contains(linkOrButton).click()
}) })
Then('I can see a list of categories ordered by post count:', table => { Then('I can see a list of categories ordered by post count:', table => {
// TODO: match the table in the feature with the html table // TODO: match the table in the feature with the html table
cy.get('thead').find('tr th').should('have.length', 3) cy.get('thead')
const last_column = cy.get('tbody').find('tr td:last-child').then((last_column) => { .find('tr th')
cy.wrap(last_column) .should('have.length', 3)
const values = last_column.map((i, td) => parseInt(td.textContent)).toArray() lastColumnIsSortedInDescendingOrder()
const ordered_descending = values.slice(0).sort((a,b) => b - a)
return cy.wrap(values).should('deep.eq', ordered_descending)
})
}) })
Then('I can see a list of tags ordered by user and post count:', table => { Then('I can see a list of tags ordered by user and post count:', table => {
// TODO: match the table in the feature with the html table // TODO: match the table in the feature with the html table
cy.get('thead').find('tr th').should('have.length', 4) cy.get('thead')
const last_column = cy.get('tbody').find('tr td:last-child').then((last_column) => { .find('tr th')
cy.wrap(last_column) .should('have.length', 4)
const values = last_column.map((i, td) => parseInt(td.textContent)).toArray() lastColumnIsSortedInDescendingOrder()
const ordered_descending = values.slice(0).sort((a,b) => b - a)
return cy.wrap(values).should('deep.eq', ordered_descending)
})
}) })

View File

@ -13,11 +13,6 @@ export const mutations = {
SET_USER(state, user) { SET_USER(state, user) {
state.user = user || null state.user = user || null
}, },
SET_USER_SETTINGS(state, userSettings) {
// state.user = Object.assign(state.user, {
// userSettings: Object.assign(this.getters['auth/userSettings'], userSettings)
// })
},
SET_TOKEN(state, token) { SET_TOKEN(state, token) {
state.token = token || null state.token = token || null
}, },
@ -27,15 +22,15 @@ export const mutations = {
} }
export const getters = { export const getters = {
isAuthenticated(state) {
return !!state.token
},
isLoggedIn(state) { isLoggedIn(state) {
return !!(state.user && state.token) return !!(state.user && state.token)
}, },
pending(state) { pending(state) {
return !!state.pending return !!state.pending
}, },
isVerified(state) {
return !!state.user && state.user.isVerified && !!state.user.name
},
isAdmin(state) { isAdmin(state) {
return !!state.user && state.user.role === 'admin' return !!state.user && state.user.role === 'admin'
}, },
@ -51,20 +46,6 @@ export const getters = {
token(state) { token(state) {
return state.token return state.token
} }
// userSettings(state, getters, rootState, rootGetters) {
// const userSettings = (state.user && state.user.userSettings) ? state.user.userSettings : {}
//
// const defaultLanguage = (state.user && state.user.language) ? state.user.language : rootGetters['i18n/locale']
// let contentLanguages = !isEmpty(userSettings.contentLanguages) ? userSettings.contentLanguages : []
// if (isEmpty(contentLanguages)) {
// contentLanguages = userSettings.uiLanguage ? [userSettings.uiLanguage] : [defaultLanguage]
// }
//
// return Object.assign({
// uiLanguage: defaultLanguage,
// contentLanguages: contentLanguages
// }, userSettings)
// }
} }
export const actions = { export const actions = {
@ -97,10 +78,8 @@ export const actions = {
return getters.isLoggedIn return getters.isLoggedIn
}, },
async login({ commit }, { email, password }) { async login({ commit }, { email, password }) {
commit('SET_PENDING', true)
try { try {
commit('SET_PENDING', true)
commit('SET_USER', null)
commit('SET_TOKEN', null)
const res = await this.app.apolloProvider.defaultClient const res = await this.app.apolloProvider.defaultClient
.mutate({ .mutate({
mutation: gql(` mutation: gql(`
@ -120,22 +99,15 @@ export const actions = {
}) })
.then(({ data }) => data && data.login) .then(({ data }) => data && data.login)
if (res && res.token) { await this.app.$apolloHelpers.onLogin(res.token)
await this.app.$apolloHelpers.onLogin(res.token) commit('SET_TOKEN', res.token)
commit('SET_TOKEN', res.token) const userData = Object.assign({}, res)
delete res.token delete userData.token
commit('SET_USER', res) commit('SET_USER', userData)
commit('SET_PENDING', false)
return true
} else {
commit('SET_PENDING', false)
throw new Error('THERE IS AN ERROR')
}
} catch (err) { } catch (err) {
commit('SET_USER', null)
commit('SET_TOKEN', null)
commit('SET_PENDING', false)
throw new Error(err) throw new Error(err)
} finally {
commit('SET_PENDING', false)
} }
}, },
async logout({ commit }) { async logout({ commit }) {

160
store/auth.test.js Normal file
View File

@ -0,0 +1,160 @@
import { getters, mutations, actions } from './auth.js'
let state
let commit
const token =
'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpZCI6InUzIiwic2x1ZyI6Implbm55LXJvc3RvY2siLCJuYW1lIjoiSmVubnkgUm9zdG9jayIsImF2YXRhciI6Imh0dHBzOi8vczMuYW1hem9uYXdzLmNvbS91aWZhY2VzL2ZhY2VzL3R3aXR0ZXIvbXV0dV9rcmlzaC8xMjguanBnIiwiZW1haWwiOiJ1c2VyQGV4YW1wbGUub3JnIiwicm9sZSI6InVzZXIiLCJpYXQiOjE1NDUxNDQ2ODgsImV4cCI6MTYzMTU0NDY4OCwiYXVkIjoiaHR0cDovL2xvY2FsaG9zdDozMDAwIiwiaXNzIjoiaHR0cDovL2xvY2FsaG9zdDo0MDAwIiwic3ViIjoidTMifQ.s5_JeQN9TaUPfymAXPOpbMAwhmTIg9cnOvNEcj4z75k'
const successfulLoginResponse = {
data: {
login: {
id: 'u3',
name: 'Jenny Rostock',
slug: 'jenny-rostock',
email: 'user@example.org',
avatar:
'https://s3.amazonaws.com/uifaces/faces/twitter/mutu_krish/128.jpg',
role: 'user',
token
}
}
}
const incorrectPasswordResponse = {
data: {
login: null
},
errors: [
{
message: 'Incorrect password.',
locations: [
{
line: 2,
column: 3
}
],
path: ['login']
}
]
}
beforeEach(() => {
commit = jest.fn()
})
describe('getters', () => {
describe('isAuthenticated', () => {
describe('given JWT Bearer token', () => {
test('true', () => {
state = { token }
expect(getters.isAuthenticated(state)).toBe(true)
})
})
})
})
describe('actions', () => {
let action
describe('login', () => {
describe('given valid credentials and a successful response', () => {
beforeEach(async () => {
const response = Object.assign({}, successfulLoginResponse)
const mutate = jest.fn(() => Promise.resolve(response))
const onLogin = jest.fn(() => Promise.resolve())
const module = {
app: {
apolloProvider: { defaultClient: { mutate } },
$apolloHelpers: { onLogin }
}
}
action = actions.login.bind(module)
await action(
{ commit },
{ email: 'user@example.org', password: '1234' }
)
})
afterEach(() => {
action = null
})
it('saves the JWT Bearer token', () => {
expect(commit.mock.calls).toEqual(
expect.arrayContaining([['SET_TOKEN', token]])
)
})
it('saves user data without token', () => {
expect(commit.mock.calls).toEqual(
expect.arrayContaining([
[
'SET_USER',
{
id: 'u3',
name: 'Jenny Rostock',
slug: 'jenny-rostock',
email: 'user@example.org',
avatar:
'https://s3.amazonaws.com/uifaces/faces/twitter/mutu_krish/128.jpg',
role: 'user'
}
]
])
)
})
it('saves pending flags in order', () => {
expect(commit.mock.calls).toEqual(
expect.arrayContaining([
['SET_PENDING', true],
['SET_PENDING', false]
])
)
})
})
describe('given invalid credentials and incorrect password response', () => {
let onLogin
let mutate
beforeEach(() => {
mutate = jest.fn(() => Promise.reject('This error is expected.'))
onLogin = jest.fn(() => Promise.resolve())
const module = {
app: {
apolloProvider: { defaultClient: { mutate } },
$apolloHelpers: { onLogin }
}
}
action = actions.login.bind(module)
})
afterEach(() => {
action = null
})
it('populates error messages', async () => {
expect(
action({ commit }, { email: 'user@example.org', password: 'wrong' })
).rejects.toThrowError('This error is expected.')
expect(mutate).toHaveBeenCalled()
expect(onLogin).not.toHaveBeenCalled()
})
it('saves pending flags in order', async () => {
try {
await action(
{ commit },
{ email: 'user@example.org', password: 'wrong' }
)
} catch (err) {} // ignore
expect(commit.mock.calls).toEqual(
expect.arrayContaining([
['SET_PENDING', true],
['SET_PENDING', false]
])
)
})
})
})
})