import { test, expect } from './fixtures'; import { readPasswordResetToken } from './support/db'; const NEW_PASSWORD = 'a-brand-new-password'; test.describe('Password reset', () => { test('the login page offers a way to recover a forgotten password', async ({ page, authModal, passwordReset }) => { await authModal.gotoLogIn(); // Recovery is reached from the login modal rather than a link on a page, // and its title is the modal's rather than a heading. await authModal.forgotPasswordButton.click(); await expect(page).toHaveURL(/\/forgot-password/); await expect(passwordReset.requestDialog).toBeVisible(); }); test('requesting a reset confirms without revealing whether the account exists', async ({ page, passwordReset }) => { await passwordReset.gotoRequest(); await passwordReset.requestLinkFor('definitely-nobody@example.com'); // Identical wording either way; a differing message would make this an // account-enumeration oracle. await expect(page.getByText(/If an account exists/)).toBeVisible(); }); test('a reset link with no token explains itself instead of failing on submit', async ({ page, passwordReset }) => { await passwordReset.gotoReset(); await expect(page.getByText('This link is incomplete')).toBeVisible(); await expect(passwordReset.setNewPasswordButton).toHaveCount(0); }); test('rejects a mismatched confirmation before contacting the server', async ({ page, passwordReset }) => { await passwordReset.gotoReset('whatever'); await passwordReset.setNewPassword(NEW_PASSWORD, 'something-else-entirely'); await expect(page.getByText('The passwords do not match')).toBeVisible(); }); test('reports an invalid token rather than appearing to succeed', async ({ page, passwordReset }) => { await passwordReset.gotoReset('not-a-real-token'); await passwordReset.setNewPassword(NEW_PASSWORD); await expect(page.getByText('invalid or expired token')).toBeVisible(); await expect(page).toHaveURL(/\/reset-password/); }); test('a customer can reset their password and sign in with the new one', async ({ request, customer, accountModal, header, authModal, passwordReset }) => { await accountModal.openAndLogOut(); await expect(header.logInButton).toBeVisible(); // The reset link arrives by email, which the tests can't read. Request the // reset through the real endpoint, then read the issued token the way the // customer's mail client would deliver it. const requested = await request.post('/api/customers/request-password-reset', { data: { email: customer.email } }); expect(requested.ok()).toBeTruthy(); await passwordReset.gotoReset(await readPasswordResetToken(customer.email)); await passwordReset.setNewPassword(NEW_PASSWORD); // The reset signs them in and closes back to the storefront — the link came // from an email, so there is no page behind it to return to. await header.waitForSignedIn(); await accountModal.open(); await expect(accountModal.emailText(customer.email)).toBeVisible(); // And the new password actually works on a fresh sign-in. await accountModal.logOut(); await expect(header.logInButton).toBeVisible(); await authModal.gotoLogIn(); await authModal.logIn(customer.email, NEW_PASSWORD); await header.waitForSignedIn(); }); test('the old password stops working after a reset', async ({ page, request, customer, accountModal, header, authModal }) => { await accountModal.openAndLogOut(); await expect(header.logInButton).toBeVisible(); await request.post('/api/customers/request-password-reset', { data: { email: customer.email } }); const token = await readPasswordResetToken(customer.email); await request.post('/api/customers/reset-password', { data: { token, password: NEW_PASSWORD } }); await authModal.gotoLogIn(); await authModal.logIn(customer.email, customer.password); await expect(page.getByText('invalid email or password')).toBeVisible(); await expect(header.myAccountButton).toHaveCount(0); }); });