Skip to content

Commit

Permalink
feat(DTFS2-6711): fix check-your-email tests
Browse files Browse the repository at this point in the history
  • Loading branch information
AlexBramhill committed Nov 22, 2023
1 parent f1ce2c6 commit a9c6bfc
Show file tree
Hide file tree
Showing 3 changed files with 75 additions and 67 deletions.
116 changes: 65 additions & 51 deletions portal/api-tests/login/post-check-your-email.api-test.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,16 @@
const { when, resetAllWhenMocks } = require('jest-when');
const extractSessionCookie = require('../helpers/extractSessionCookie');
const mockLogin = require('../helpers/login');
const app = require('../../server/createApp');
const { post } = require('../create-api').createApi(app);
const { withPartial2faAuthValidationApiTests } = require('../common-tests/partial-2fa-auth-validation-api-tests');
const api = require('../../server/api');

const { FEATURE_FLAGS } = require('../../server/config/feature-flag.config');

jest.mock('csurf', () => () => (req, res, next) => next());
jest.mock('../../server/routes/middleware/csrf', () => ({
...(jest.requireActual('../../server/routes/middleware/csrf')),
...jest.requireActual('../../server/routes/middleware/csrf'),
csrfToken: () => (req, res, next) => next(),
}));

Expand All @@ -11,14 +21,6 @@ jest.mock('../../server/api', () => ({
validateToken: () => true,
validatePartialAuthToken: jest.fn(),
}));
const { when, resetAllWhenMocks } = require('jest-when');
const extractSessionCookie = require('../helpers/extractSessionCookie');
const mockLogin = require('../helpers/login');
const app = require('../../server/createApp');
const { post } = require('../create-api').createApi(app);
const { withPartial2faAuthValidationApiTests } = require('../common-tests/partial-2fa-auth-validation-api-tests');
const { validatePartialAuthToken, sendSignInLink, login } = require('../../server/api');
const { FEATURE_FLAGS } = require('../../server/config/feature-flag.config');

(FEATURE_FLAGS.MAGIC_LINK ? describe : describe.skip)('POST /login/check-your-email', () => {
withPartial2faAuthValidationApiTests({
Expand All @@ -33,24 +35,26 @@ const { FEATURE_FLAGS } = require('../../server/config/feature-flag.config');
const partialAuthToken = 'partial auth token';
const email = 'email@example.com';
const password = 'a password';

const numberOfSendSignInLinkAttemptsRemaining = 1;
let sessionCookie;
beforeEach(async () => {
resetAllWhenMocks();
jest.resetAllMocks();
login.mockImplementation(mockLogin(partialAuthToken));
api.login.mockImplementation(mockLogin(partialAuthToken));
sessionCookie = await post({ email, password }).to('/login').then(extractSessionCookie);
sendSignInLink.mockReset();
when(validatePartialAuthToken)
.calledWith(partialAuthToken)
.mockResolvedValueOnce();
when(api.validatePartialAuthToken).calledWith(partialAuthToken).mockResolvedValueOnce();
});

describe('when the user does not have a session', () => {
beforeEach(() => {
mockSuccessfulSendSignInLinkResponse();
});

it('does not send a new sign in link', async () => {
api.sendSignInLink.mockClear();
await post().to('/login/check-your-email');

expect(sendSignInLink).not.toHaveBeenCalled();
expect(api.sendSignInLink).not.toHaveBeenCalled();
});

it('redirects the user to /login', async () => {
Expand All @@ -61,41 +65,32 @@ const { FEATURE_FLAGS } = require('../../server/config/feature-flag.config');
});
});

describe('when the user has a session', () => {
describe('the first time it is called in the session', () => {
itRedirectsTheUserToCheckYourEmail();
itSendsANewSignInLink();
describe.each([
{
description: 'when a user is not blocked',
beforeEachSetUp: () => {
mockSuccessfulSendSignInLinkResponse();
},
},
{
description: 'when a user is blocked',
beforeEachSetUp: () => {
mock403SendSignInLinkResponse();
},
},
{
description: 'when sending an email fails',
beforeEachSetUp: () => {
mock500SendSignInLinkResponse();
},
},
])('$description', ({ beforeEachSetUp }) => {
beforeEach(() => {
beforeEachSetUp();
});

describe('the second time it is called in the session', () => {
beforeEach(async () => {
await post({}, { Cookie: sessionCookie }).to('/login/check-your-email');
sendSignInLink.mockReset();
});

itRedirectsTheUserToCheckYourEmail();
itSendsANewSignInLink();
});

describe('the third time it is called in the session', () => {
beforeEach(async () => {
await post({}, { Cookie: sessionCookie }).to('/login/check-your-email');
await post({}, { Cookie: sessionCookie }).to('/login/check-your-email');
sendSignInLink.mockReset();
});

it('does not send a new sign in link', async () => {
await post({}, { Cookie: sessionCookie }).to('/login/check-your-email');

expect(sendSignInLink).not.toHaveBeenCalled();
});

it('returns a 403 error', async () => {
const { status } = await post({}, { Cookie: sessionCookie }).to('/login/check-your-email');

expect(status).toBe(403);
});
});
itRedirectsTheUserToCheckYourEmail();
itSendsANewSignInLink();
});

function itRedirectsTheUserToCheckYourEmail() {
Expand All @@ -109,11 +104,30 @@ const { FEATURE_FLAGS } = require('../../server/config/feature-flag.config');

function itSendsANewSignInLink() {
it('sends a new sign in link', async () => {
api.sendSignInLink.mockClear();
await post({}, { Cookie: sessionCookie }).to('/login/check-your-email');

expect(sendSignInLink).toHaveBeenCalledTimes(1);
expect(sendSignInLink).toHaveBeenCalledWith(partialAuthToken);
expect(api.sendSignInLink).toHaveBeenCalledTimes(1);
expect(api.sendSignInLink).toHaveBeenCalledWith(partialAuthToken);
});
}

function mockSuccessfulSendSignInLinkResponse() {
when(api.sendSignInLink).calledWith(expect.anything()).mockResolvedValue({ data: { numberOfSendSignInLinkAttemptsRemaining } });
}

function mockUnsuccessfulSendSignInLinkResponseWithStatusCode(statusCode) {
const error = new Error(`Request failed with status: ${statusCode}`);
error.response = { status: statusCode };
when(api.sendSignInLink).calledWith(expect.anything()).mockRejectedValue(error);
}

function mock403SendSignInLinkResponse() {
mockUnsuccessfulSendSignInLinkResponseWithStatusCode(403);
}

function mock500SendSignInLinkResponse() {
mockUnsuccessfulSendSignInLinkResponseWithStatusCode(500);
}
});
});
5 changes: 3 additions & 2 deletions portal/server/controllers/login/check-your-email.js
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,8 @@ module.exports.renderCheckYourEmailPage = (req, res) => {
return res.render('login/new-sign-in-link-sent.njk');
case 0:
return res.render('login/we-have-sent-you-another-link.njk', { obscuredSignInLinkTargetEmailAddress: obscureEmail(userEmail) });
case -1:
return res.render('login/temporarily-suspended.njk');
default:
return res.redirect('/login');
}
Expand All @@ -36,8 +38,7 @@ module.exports.sendNewSignInLink = async (req, res) => {
req.session.numberOfSendSignInLinkAttemptsRemaining = numberOfSendSignInLinkAttemptsRemaining;
} catch (error) {
if (error.response?.status === 403) {
delete req.session.numberOfSendSignInLinkAttemptsRemaining;
return res.render('login/temporarily-suspended.njk');
req.session.numberOfSendSignInLinkAttemptsRemaining = -1;
}
console.warn('Failed to send sign in link. The login flow will continue as the user can retry on the next page. The error was: %O', error);
}
Expand Down
21 changes: 7 additions & 14 deletions portal/server/controllers/login/check-your-email.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,10 @@ describe('renderCheckYourEmailPage', () => {
session: { userEmail, numberOfSendSignInLinkAttemptsRemaining: 0 },
expectedRenderArguments: ['login/we-have-sent-you-another-link.njk', { obscuredSignInLinkTargetEmailAddress: redactedEmail }],
},
{
session: { userEmail, numberOfSendSignInLinkAttemptsRemaining: -1 },
expectedRenderArguments: ['login/temporarily-suspended.njk'],
},
])(
'renders the $expectedRenderArguments.0 template if there are $session.numberOfSendSignInLinkAttemptsRemaining attempts remaining to send the sign in link',
({ session, expectedRenderArguments }) => {
Expand All @@ -47,17 +51,6 @@ describe('renderCheckYourEmailPage', () => {

expect(res.render).toHaveBeenCalledWith('_partials/problem-with-service.njk');
});

it.each([
{
description: 'if there are -1 attempts remaining to send the sign in link',
req: { session: { userEmail, numberOfSendSignInLinkAttemptsRemaining: -1 } },
},
])('redirects to the login page $description', ({ req }) => {
renderCheckYourEmailPage(req, res);

expect(res.redirect).toHaveBeenCalledWith('/login');
});
});

describe('sendNewSignInLink', () => {
Expand Down Expand Up @@ -124,15 +117,15 @@ describe('sendNewSignInLink', () => {

await sendNewSignInLink(req, res);

expect(res.render).toHaveBeenCalledWith('login/temporarily-suspended.njk');
expect(res.redirect).toHaveBeenCalledWith('/login/check-your-email');
});

it('deletes the number of send sign in link attempts remaining in the session', async () => {
it('updates the number of send sign in link attempts remaining in the session to -1', async () => {
mock403SendSignInLinkResponse();

await sendNewSignInLink(req, res);

expect(req.session.numberOfSendSignInLinkAttemptsRemaining).toBeUndefined();
expect(req.session.numberOfSendSignInLinkAttemptsRemaining).toBe(-1);
});
});

Expand Down

0 comments on commit a9c6bfc

Please sign in to comment.