Skip to content

Commit

Permalink
test(e2e): implement recovery tests for SPA
Browse files Browse the repository at this point in the history
  • Loading branch information
aeneasr committed Oct 19, 2021
1 parent 35ea8db commit 3dea57f
Show file tree
Hide file tree
Showing 7 changed files with 124 additions and 100 deletions.
3 changes: 2 additions & 1 deletion test/e2e/cypress/helpers/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -60,7 +60,8 @@ export const website = 'https://www.ory.sh/'
export const gen = {
email,
password,
identity: () => ({email: email(), password: password()})
identity: () => ({email: email(), password: password()}),
identityWithWebsite: () => ({email: email(), password: password(),fields:{'traits.website': 'https://www.ory.sh'}})
}

// Format is
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -69,8 +69,8 @@ context('Account Recovery Errors', () => {

it('is unable to recover the email address if the code is expired', () => {
cy.shortLinkLifespan()
const identity = gen.identity()
cy.registerApi({...identity, fields})
const identity = gen.identityWithWebsite()
cy.registerApi(identity)
cy.recoverApi({email: identity.email})
cy.recoverEmailButExpired({expect: {email: identity.email}})

Expand All @@ -83,8 +83,8 @@ context('Account Recovery Errors', () => {
})

it('is unable to recover the account if the code is incorrect', () => {
const identity = gen.identity()
cy.registerApi({...identity, fields})
const identity = gen.identityWithWebsite()
cy.registerApi(identity)
cy.recoverApi({email: identity.email})

cy.getMail().then((mail) => {
Expand All @@ -100,16 +100,20 @@ context('Account Recovery Errors', () => {
})

it('is unable to recover the account using the token twice', () => {
const identity = gen.identity()
cy.registerApi({...identity, fields})
const identity = gen.identityWithWebsite()
cy.registerApi(identity)
cy.recoverApi({email: identity.email})

cy.getMail().then((mail) => {
const link = parseHtml(mail.body).querySelector('a')

cy.visit(link.href) // add random stuff to the confirm challenge
cy.getSession()
cy.logout()
// Workaround for cypress cy.visit limitation.
cy.request(link.href).should(response => {
// add random stuff to the confirm challenge
expect(response.status).to.eq(200)
})

cy.clearAllCookies()

cy.visit(link.href)
cy.get('[data-testid="ui/message/4060004"]').should(
Expand Down
Original file line number Diff line number Diff line change
@@ -1,60 +1,61 @@
import { APP_URL, assertRecoveryAddress, gen } from '../../../../helpers'

context('Recovery Profile', () => {
describe('Recovery', () => {
before(() => {
cy.useConfigProfile('recovery')
})

describe('successful flow', () => {
let identity

import {assertRecoveryAddress, gen} from '../../../../helpers'
import {routes as react} from "../../../../helpers/react";
import {routes as express} from "../../../../helpers/express";

context('Account Recovery Success', () => {
[
{
recovery: react.recovery,
base: react.base,
app: 'react', profile: 'spa'
},
{
recovery: express.recovery,
base: express.base,
app: 'express', profile: 'recovery'
}
].forEach(({recovery, profile, base, app}) => {
describe(`for app ${app}`, () => {
before(() => {
cy.deleteMail()
cy.useConfigProfile(profile)
})

let identity

beforeEach(() => {
identity = gen.identity()
cy.register(identity)
cy.visit(APP_URL + '/recovery')
cy.deleteMail()
cy.longRecoveryLifespan()
cy.longLinkLifespan()
cy.disableVerification()
cy.enableRecovery()

identity = gen.identityWithWebsite()
cy.registerApi(identity)
})

it('should contain the recovery address in the session', () => {
cy.login(identity)
cy.visit(recovery)
cy.login({...identity, cookieUrl: base})
cy.getSession().should(assertRecoveryAddress(identity))
})

it('should perform a recovery flow', () => {
cy.get('input[name="email"]').type(identity.email)
cy.get('button[value="link"]').click()

cy.location('pathname').should('eq', '/recovery')
cy.get('.messages .message').should(
'have.text',
'An email containing a recovery link has been sent to the email address you provided.'
)
cy.get('input[name="email"]').should('have.value', identity.email)

cy.get('button[type="submit"][name="method"][value="link"]').should(
'exist'
)
cy.recoverApi({email: identity.email})

cy.recoverEmail({ expect: identity })
cy.recoverEmail({expect: identity})

cy.getSession()
cy.location('pathname').should('eq', '/settings')

const newPassword = gen.password()
cy.get('input[name="password"]').clear().type(newPassword)
cy.get('button[value="password"]').click()
cy.get('.container').should(
'contain.text',
'Your changes have been saved!'
)
cy.expectSettingsSaved()
cy.get('input[name="password"]').should('be.empty')

cy.logout()
cy.login({ email: identity.email, password: newPassword })
cy.login({email: identity.email, password: newPassword, cookieUrl: base})
})
})
})
Expand Down

This file was deleted.

Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
import {assertRecoveryAddress, gen} from '../../../../helpers'
import {routes as react} from "../../../../helpers/react";
import {routes as express} from "../../../../helpers/express";

context('Account Recovery Success', () => {
[
{
settings: react.settings,
base: react.base,
app: 'react', profile: 'spa'
},
{
settings: express.settings,
base: express.base,
app: 'express', profile: 'recovery'
}
].forEach(({settings, profile, base, app}) => {
describe(`for app ${app}`, () => {
before(() => {
cy.deleteMail()
cy.useConfigProfile(profile)
})

let identity

beforeEach(() => {
cy.deleteMail()
cy.longRecoveryLifespan()
cy.longLinkLifespan()
cy.disableVerification()
cy.enableRecovery()

identity = gen.identityWithWebsite()
cy.registerApi(identity)
cy.login({...identity, cookieUrl: base})
})

it('should update the recovery address when updating the email', () => {
cy.visit(settings)
const email = gen.email()
cy.get('input[name="traits.email"]').clear().type(email)
cy.get('button[value="profile"]').click()
cy.expectSettingsSaved()
cy.get('input[name="traits.email"]').should('contain.value', email)

cy.getSession().should(assertRecoveryAddress({email}))
})

xit('should not show an immediate error when a recovery address already exists', () => {
// account enumeration prevention, needs to be implemented.
})
})
})
})
26 changes: 14 additions & 12 deletions test/e2e/cypress/support/commands.ts
Original file line number Diff line number Diff line change
Expand Up @@ -270,7 +270,7 @@ Cypress.Commands.add(
const form = body.ui
return cy.request({
method: form.method,
body: mergeFields(form, {email, method: 'link'}),
body: mergeFields(form, {email, method: 'link'}),
url: form.action
})
})
Expand Down Expand Up @@ -552,17 +552,16 @@ Cypress.Commands.add(
expect(link).to.not.be.null
expect(link.href).to.contain(APP_URL)

if (redirectTo) {
cy.request({url: link.href, followRedirect: false}).should(
(response) => {
expect(response.status).to.eq(302)
cy.request({url: link.href, followRedirect: false}).should(
(response) => {
expect(response.status).to.eq(303)
if (redirectTo) {
expect(response.redirectedToUrl).to.eq(redirectTo)
} else {
expect(response.redirectedToUrl).to.not.contain('verification')
}
)
} else {
cy.visit(link.href)
cy.location('pathname').should('not.contain', 'verify')
}
}
)
})
)

Expand Down Expand Up @@ -591,7 +590,7 @@ Cypress.Commands.add(
})
)

Cypress.Commands.add('recoverEmail', ({expect: {email}}) =>
Cypress.Commands.add('recoverEmail', ({expect: {email}, shouldVisit = true}) =>
cy.getMail().then((message) => {
expect(message.subject.trim()).to.equal('Recover access to your account')
expect(message.fromAddress.trim()).to.equal('no-reply@ory.kratos.sh')
Expand All @@ -602,7 +601,10 @@ Cypress.Commands.add('recoverEmail', ({expect: {email}}) =>
expect(link).to.not.be.null
expect(link.href).to.contain(APP_URL)

cy.visit(link.href)
if (shouldVisit) {
cy.visit(link.href)
}
return link.href
})
)

Expand Down
7 changes: 6 additions & 1 deletion test/e2e/cypress/support/index.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -336,7 +336,12 @@ declare global {
*
* @param opts
*/
recoverEmail(opts: {expect: {email: string}}): Chainable<void>
recoverEmail(opts: {expect: {email: string},shouldVisit?:boolean}): Chainable<string>

/**
* Configures a hook which only allows verified email addresses to sign in.
*/
enableLoginForVerifiedAddressOnly(): Chainable<void>
}
}
}

0 comments on commit 3dea57f

Please sign in to comment.