Merge pull request #233 from Human-Connection/212-change-password

[User profile] Change password
This commit is contained in:
Martin Döring 2019-03-25 13:11:21 +01:00 committed by GitHub
commit ecbdfdfb55
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
7 changed files with 327 additions and 8 deletions

View File

@ -5,7 +5,7 @@ import { getLangByName } from '../../support/helpers'
let lastPost = {}
const loginCredentials = {
let loginCredentials = {
email: 'peterpan@example.org',
password: '1234'
}
@ -244,3 +244,48 @@ Then(
cy.get('.error').should('contain', message)
}
)
Given('my user account has the following login credentials:', table => {
loginCredentials = table.hashes()[0]
cy.debug()
cy.factory().create('User', loginCredentials)
})
When('I fill the password form with:', table => {
table = table.rowsHash()
cy.get('input[id=oldPassword]')
.type(table['Your old password'])
.get('input[id=newPassword]')
.type(table['Your new passsword'])
.get('input[id=confirmPassword]')
.type(table['Confirm new password'])
})
When('submit the form', () => {
cy.get('form').submit()
})
Then('I cannot login anymore with password {string}', password => {
cy.reload()
const { email } = loginCredentials
cy.visit(`/login`)
cy.get('input[name=email]')
.trigger('focus')
.type(email)
cy.get('input[name=password]')
.trigger('focus')
.type(password)
cy.get('button[name=submit]')
.as('submitButton')
.click()
cy.get('.iziToast-wrapper').should('contain', 'Incorrect email address or password.')
})
Then('I can login successfully with password {string}', password => {
cy.reload()
cy.login({
...loginCredentials,
...{password}
})
cy.get('.iziToast-wrapper').should('contain', "You are logged in!")
})

View File

@ -0,0 +1,31 @@
Feature: Change password
As a user
I want to change my password in my settings
For security, e.g. if I exposed my password by accident
Login via email and password is a well-known authentication procedure and you
can assure to the server that you are who you claim to be. Either if you
exposed your password by acccident and you want to invalidate the exposed
password or just out of an good habit, you want to change your password.
Background:
Given my user account has the following login credentials:
| email | password |
| user@example.org | exposed |
And I am logged in
Scenario: Change my password
Given I am on the "settings" page
And I click on "Security"
When I fill the password form with:
| Your old password | exposed |
| Your new passsword | secure |
| Confirm new password | secure |
And submit the form
And I see a success message:
"""
Password successfully changed!
"""
And I log out through the menu in the top right corner
Then I cannot login anymore with password "exposed"
But I can login successfully with password "secure"

View File

@ -0,0 +1,154 @@
import { mount, createLocalVue } from '@vue/test-utils'
import ChangePassword from './ChangePassword.vue'
import Vue from 'vue'
import Styleguide from '@human-connection/styleguide'
const localVue = createLocalVue()
localVue.use(Styleguide)
describe('ChangePassword.vue', () => {
let mocks
let wrapper
beforeEach(() => {
mocks = {
validate: jest.fn(),
$toast: {
error: jest.fn(),
success: jest.fn()
},
$t: jest.fn(),
$store: {
commit: jest.fn()
},
$apollo: {
mutate: jest
.fn()
.mockRejectedValue({ message: 'Ouch!' })
.mockResolvedValueOnce({ data: { changePassword: 'NEWTOKEN' } })
}
}
})
describe('mount', () => {
let wrapper
const Wrapper = () => {
return mount(ChangePassword, { mocks, localVue })
}
beforeEach(() => {
wrapper = Wrapper()
})
it('renders three input fields', () => {
expect(wrapper.findAll('input')).toHaveLength(3)
})
describe('validations', () => {
it('invalid', () => {
expect(wrapper.vm.disabled).toBe(true)
})
describe('old password and new password', () => {
describe('match', () => {
beforeEach(() => {
wrapper.find('input#oldPassword').setValue('some secret')
wrapper.find('input#newPassword').setValue('some secret')
})
it('invalid', () => {
expect(wrapper.vm.disabled).toBe(true)
})
it.skip('displays a warning', () => {
const calls = mocks.validate.mock.calls
const expected = [
['change-password.validations.old-and-new-password-match']
]
expect(calls).toEqual(expect.arrayContaining(expected))
})
})
})
describe('new password and confirmation', () => {
describe('mismatch', () => {
it.todo('invalid')
it.todo('displays a warning')
})
describe('match', () => {
describe('and old password mismatch', () => {
it.todo('valid')
})
describe('clicked', () => {
it.todo('sets loading')
})
})
})
})
describe('given valid input', () => {
beforeEach(() => {
wrapper.find('input#oldPassword').setValue('supersecret')
wrapper.find('input#newPassword').setValue('superdupersecret')
wrapper.find('input#confirmPassword').setValue('superdupersecret')
})
describe('submit form', () => {
beforeEach(() => {
wrapper.find('form').trigger('submit')
})
it('calls changePassword mutation', () => {
expect(mocks.$apollo.mutate).toHaveBeenCalled()
})
it('passes form data as variables', () => {
expect(mocks.$apollo.mutate.mock.calls[0][0]).toEqual(
expect.objectContaining({
variables: {
oldPassword: 'supersecret',
newPassword: 'superdupersecret',
confirmPassword: 'superdupersecret'
}
})
)
})
describe('mutation resolves', () => {
beforeEach(() => {
mocks.$apollo.mutate = jest.fn().mockResolvedValue()
wrapper = Wrapper()
})
it('calls auth/SET_TOKEN with response', () => {
expect(mocks.$store.commit).toHaveBeenCalledWith(
'auth/SET_TOKEN',
'NEWTOKEN'
)
})
it('displays success message', () => {
expect(mocks.$t).toHaveBeenCalledWith(
'settings.security.change-password.success'
)
expect(mocks.$toast.success).toHaveBeenCalled()
})
})
describe('mutation rejects', () => {
beforeEach(() => {
// second call will reject
wrapper.find('form').trigger('submit')
})
it('displays error message', () => {
expect(mocks.$toast.error).toHaveBeenCalledWith('Ouch!')
})
})
})
})
})
})

View File

@ -0,0 +1,83 @@
<template>
<ds-form
v-model="formData"
:schema="formSchema"
@submit="handleSubmit"
>
<template>
<ds-input
id="oldPassword"
model="oldPassword"
type="password"
label="Your old password"
/>
<ds-input
id="newPassword"
model="newPassword"
type="password"
label="Your new password"
/>
<ds-input
id="confirmPassword"
model="confirmPassword"
type="password"
label="Confirm new password"
/>
<ds-space margin-top="base">
<ds-button
:loading="loading"
primary
>
{{ $t('settings.security.change-password.button') }}
</ds-button>
</ds-space>
</template>
</ds-form>
</template>
<script>
import gql from 'graphql-tag'
export default {
name: 'ChangePassword',
data() {
return {
formData: {
oldPassword: '',
newPassword: '',
confirmPassword: ''
},
formSchema: {
oldPassword: { required: true },
newPassword: { required: true },
confirmPassword: { required: true }
},
loading: false,
disabled: true
}
},
methods: {
async handleSubmit(data) {
this.loading = true
const mutation = gql`
mutation($oldPassword: String!, $newPassword: String!) {
changePassword(oldPassword: $oldPassword, newPassword: $newPassword)
}
`
const variables = this.formData
try {
const { data } = await this.$apollo.mutate({ mutation, variables })
this.$store.commit('auth/SET_TOKEN', data.changePassword)
this.$toast.success(
this.$t('settings.security.change-password.success')
)
} catch (err) {
this.$toast.error(err.message)
} finally {
this.loading = false
}
}
}
}
</script>

View File

@ -31,7 +31,11 @@
"labelBio": "Über dich"
},
"security": {
"name": "Sicherheit"
"name": "Sicherheit",
"change-password": {
"button": "Passwort ändern",
"success": "Passwort erfolgreich geändert!"
}
},
"invites": {
"name": "Einladungen"

View File

@ -31,7 +31,11 @@
"labelBio": "About You"
},
"security": {
"name": "Security"
"name": "Security",
"change-password": {
"button": "Change password",
"success": "Password successfully changed!"
}
},
"invites": {
"name": "Invites"

View File

@ -1,18 +1,16 @@
<template>
<ds-card :header="$t('settings.security.name')">
<hc-empty
icon="tasks"
message="Coming Soon…"
/>
<change-password />
</ds-card>
</template>
<script>
import HcEmpty from '~/components/Empty.vue'
import ChangePassword from '~/components/ChangePassword.vue'
export default {
components: {
HcEmpty
ChangePassword
}
}
</script>