From 38c1788cbe2dea9beb343d67fd2e890df4a7c965 Mon Sep 17 00:00:00 2001 From: simeng-li Date: Tue, 20 Dec 2022 15:37:54 +0800 Subject: [PATCH] feat(test): add forgot pasword integration test (#2673) --- .../src/__mocks__/connectors-mock.ts | 10 + .../tests/interaction/forgot-password.test.ts | 196 ++++++++++++++++++ 2 files changed, 206 insertions(+) create mode 100644 packages/integration-tests/src/tests/interaction/forgot-password.test.ts diff --git a/packages/integration-tests/src/__mocks__/connectors-mock.ts b/packages/integration-tests/src/__mocks__/connectors-mock.ts index a27714899..0ca4f6695 100644 --- a/packages/integration-tests/src/__mocks__/connectors-mock.ts +++ b/packages/integration-tests/src/__mocks__/connectors-mock.ts @@ -106,6 +106,10 @@ export const mockSmsConnectorConfig = { content: 'This is for registering purposes only. Your passcode is {{code}}.', usageType: 'Register', }, + { + usageType: 'ForgotPassword', + content: 'This is for forgot-password purposes only. Your passcode is {{code}}.', + }, { content: 'This is for testing purposes only. Your passcode is {{code}}.', usageType: 'Test', @@ -131,6 +135,12 @@ export const mockEmailConnectorConfig = { subject: 'Logto Register Template', content: 'This is for registering purposes only. Your passcode is {{code}}.', }, + { + usageType: 'ForgotPassword', + type: 'text/plain', + subject: 'Logto Forgot Password Template', + content: 'This is for forgot-password purposes only. Your passcode is {{code}}.', + }, { usageType: 'Test', type: 'text/plain', diff --git a/packages/integration-tests/src/tests/interaction/forgot-password.test.ts b/packages/integration-tests/src/tests/interaction/forgot-password.test.ts new file mode 100644 index 000000000..9b86d951a --- /dev/null +++ b/packages/integration-tests/src/tests/interaction/forgot-password.test.ts @@ -0,0 +1,196 @@ +import { Event, ConnectorType } from '@logto/schemas'; +import { assert } from '@silverhand/essentials'; + +import { + putInteraction, + sendVerificationPasscode, + deleteUser, + patchInteraction, +} from '#src/api/index.js'; +import { expectRejects, readPasscode } from '#src/helpers.js'; +import { generatePassword } from '#src/utils.js'; + +import { initClient, processSession, logoutClient } from './utils/client.js'; +import { clearConnectorsByTypes, setEmailConnector, setSmsConnector } from './utils/connector.js'; +import { enableAllPasswordSignInMethods } from './utils/sign-in-experience.js'; +import { generateNewUser } from './utils/user.js'; + +describe('reset password', () => { + beforeAll(async () => { + await clearConnectorsByTypes([ConnectorType.Email, ConnectorType.Sms]); + await setEmailConnector(); + await setSmsConnector(); + await enableAllPasswordSignInMethods(); + }); + afterAll(async () => { + await clearConnectorsByTypes([ConnectorType.Email, ConnectorType.Sms]); + }); + it('reset password with email', async () => { + const { user, userProfile } = await generateNewUser({ + primaryEmail: true, + password: true, + }); + + const client = await initClient(); + assert(client.interactionCookie, new Error('Session not found')); + + await expect( + sendVerificationPasscode( + { + event: Event.ForgotPassword, + email: userProfile.primaryEmail, + }, + client.interactionCookie + ) + ).resolves.not.toThrow(); + + const passcodeRecord = await readPasscode(); + + expect(passcodeRecord).toMatchObject({ + address: userProfile.primaryEmail, + type: Event.ForgotPassword, + }); + + const { code } = passcodeRecord; + + await expectRejects( + putInteraction( + { + event: Event.ForgotPassword, + identifier: { + email: userProfile.primaryEmail, + passcode: code, + }, + }, + client.interactionCookie + ), + 'user.new_password_required_in_profile' + ); + + await expectRejects( + patchInteraction( + { + event: Event.ForgotPassword, + profile: { + password: userProfile.password, + }, + }, + client.interactionCookie + ), + 'user.same_password' + ); + + const newPasscodeRecord = generatePassword(); + + await expect( + patchInteraction( + { + event: Event.ForgotPassword, + profile: { + password: newPasscodeRecord, + }, + }, + client.interactionCookie + ) + ).resolves.not.toThrow(); + + const { redirectTo } = await putInteraction( + { + event: Event.SignIn, + identifier: { + email: userProfile.primaryEmail, + password: newPasscodeRecord, + }, + }, + client.interactionCookie + ); + + await processSession(client, redirectTo); + await logoutClient(client); + await deleteUser(user.id); + }); + it('reset password with phone', async () => { + const { user, userProfile } = await generateNewUser({ + primaryPhone: true, + password: true, + }); + + const client = await initClient(); + assert(client.interactionCookie, new Error('Session not found')); + + await expect( + sendVerificationPasscode( + { + event: Event.ForgotPassword, + phone: userProfile.primaryPhone, + }, + client.interactionCookie + ) + ).resolves.not.toThrow(); + + const passcodeRecord = await readPasscode(); + + expect(passcodeRecord).toMatchObject({ + phone: userProfile.primaryPhone, + type: Event.ForgotPassword, + }); + + const { code } = passcodeRecord; + + await expectRejects( + putInteraction( + { + event: Event.ForgotPassword, + identifier: { + phone: userProfile.primaryPhone, + passcode: code, + }, + }, + client.interactionCookie + ), + 'user.new_password_required_in_profile' + ); + + await expectRejects( + patchInteraction( + { + event: Event.ForgotPassword, + profile: { + password: userProfile.password, + }, + }, + client.interactionCookie + ), + 'user.same_password' + ); + + const newPasscodeRecord = generatePassword(); + + await expect( + patchInteraction( + { + event: Event.ForgotPassword, + profile: { + password: newPasscodeRecord, + }, + }, + client.interactionCookie + ) + ).resolves.not.toThrow(); + + const { redirectTo } = await putInteraction( + { + event: Event.SignIn, + identifier: { + phone: userProfile.primaryPhone, + password: newPasscodeRecord, + }, + }, + client.interactionCookie + ); + + await processSession(client, redirectTo); + await logoutClient(client); + await deleteUser(user.id); + }); +});