From ae0322621f157dc9f125ff44e068aed54f501d39 Mon Sep 17 00:00:00 2001 From: Xiao Yijun Date: Mon, 7 Aug 2023 13:16:09 +0800 Subject: [PATCH] refactor(test): refactor `expectRejects` helper method to support status code checking (#4292) * refactor(test): refactor `expectRejects` helper method to support status code checking * refactor(test): replace `createResponseWithCode` with `expectRejects` --- .../src/helpers/admin-tenant.ts | 4 - .../integration-tests/src/helpers/index.ts | 20 +++-- .../src/helpers/interactions.ts | 5 +- .../src/tests/api/admin-user.roles.test.ts | 10 +-- .../src/tests/api/admin-user.search.test.ts | 41 ++++++---- .../src/tests/api/admin-user.test.ts | 76 +++++++++++-------- .../src/tests/api/connector.test.ts | 33 ++++---- .../src/tests/api/dashboard.test.ts | 22 +++--- .../src/tests/api/hook/hook.test.ts | 46 ++++++----- .../src/tests/api/hook/hook.testing.test.ts | 23 ++++-- .../api/interaction/forgot-password.test.ts | 20 ++++- .../register-with-identifier.test.ts | 20 ++++- .../sign-in-with-passcode-identifier.test.ts | 35 +++++++-- .../sign-in-with-password-identifier.test.ts | 10 ++- .../interaction/social-interaction.test.ts | 40 ++++++++-- .../src/tests/api/log.test.ts | 7 +- .../src/tests/api/me.test.ts | 19 +++-- .../src/tests/api/resource.test.ts | 9 ++- .../src/tests/api/sign-in-experience.test.ts | 9 ++- .../src/tests/api/verification-code.test.ts | 45 ++++++----- 20 files changed, 318 insertions(+), 176 deletions(-) diff --git a/packages/integration-tests/src/helpers/admin-tenant.ts b/packages/integration-tests/src/helpers/admin-tenant.ts index f2da45e49..4786a8b44 100644 --- a/packages/integration-tests/src/helpers/admin-tenant.ts +++ b/packages/integration-tests/src/helpers/admin-tenant.ts @@ -27,10 +27,6 @@ import { generatePassword, generateUsername } from '#src/utils.js'; export const resourceDefault = getManagementApiResourceIndicator(defaultTenantId); export const resourceMe = getManagementApiResourceIndicator(adminTenantId, 'me'); -export const createResponseWithCode = (statusCode: number) => ({ - response: { statusCode }, -}); - const createUserWithRoles = async (roleNames: string[]) => { const username = generateUsername(); const password = generatePassword(); diff --git a/packages/integration-tests/src/helpers/index.ts b/packages/integration-tests/src/helpers/index.ts index 32a1b023c..cd06018f2 100644 --- a/packages/integration-tests/src/helpers/index.ts +++ b/packages/integration-tests/src/helpers/index.ts @@ -52,15 +52,17 @@ export const removeVerificationCode = async (): Promise => { } }; -export const expectRejects = async ( - promise: Promise, - code: string, - messageIncludes?: string -) => { +type ExpectedErrorInfo = { + code: string; + statusCode: number; + messageIncludes?: string; +}; + +export const expectRejects = async (promise: Promise, expected: ExpectedErrorInfo) => { try { await promise; } catch (error: unknown) { - expectRequestError(error, code, messageIncludes); + expectRequestError(error, expected); return; } @@ -68,7 +70,9 @@ export const expectRejects = async ( fail(); }; -export const expectRequestError = (error: unknown, code: string, messageIncludes?: string) => { +export const expectRequestError = (error: unknown, expected: ExpectedErrorInfo) => { + const { code, statusCode, messageIncludes } = expected; + if (!(error instanceof RequestError)) { fail('Error should be an instance of RequestError'); } @@ -82,6 +86,8 @@ export const expectRequestError = (error: unknown, code: string, messageIncludes expect(body.code).toEqual(code); + expect(error.response?.statusCode).toEqual(statusCode); + if (messageIncludes) { expect(body.message.includes(messageIncludes)).toBeTruthy(); } diff --git a/packages/integration-tests/src/helpers/interactions.ts b/packages/integration-tests/src/helpers/interactions.ts index f21b76e3c..20840b593 100644 --- a/packages/integration-tests/src/helpers/interactions.ts +++ b/packages/integration-tests/src/helpers/interactions.ts @@ -78,7 +78,10 @@ export const createNewSocialUserWithUsernameAndPassword = async (connectorId: st connectorData: { state, redirectUri, code, userId: socialUserId }, }); - await expectRejects(client.submitInteraction(), 'user.identity_not_exist'); + await expectRejects(client.submitInteraction(), { + code: 'user.identity_not_exist', + statusCode: 422, + }); await client.successSend(patchInteractionIdentifiers, { username, password }); await client.successSend(putInteractionProfile, { connectorId }); diff --git a/packages/integration-tests/src/tests/api/admin-user.roles.test.ts b/packages/integration-tests/src/tests/api/admin-user.roles.test.ts index 3455b7d48..94be20dfc 100644 --- a/packages/integration-tests/src/tests/api/admin-user.roles.test.ts +++ b/packages/integration-tests/src/tests/api/admin-user.roles.test.ts @@ -2,8 +2,7 @@ import { HTTPError } from 'got'; import { assignRolesToUser, getUserRoles, deleteRoleFromUser } from '#src/api/index.js'; import { createRole } from '#src/api/role.js'; -import { createResponseWithCode } from '#src/helpers/admin-tenant.js'; -import { createUserByAdmin } from '#src/helpers/index.js'; +import { createUserByAdmin, expectRejects } from '#src/helpers/index.js'; describe('admin console user management (roles)', () => { it('should get empty list successfully', async () => { @@ -27,9 +26,10 @@ describe('admin console user management (roles)', () => { const role = await createRole(); await assignRolesToUser(user.id, [role.id]); - await expect(assignRolesToUser(user.id, [role.id])).rejects.toMatchObject( - createResponseWithCode(422) - ); + await expectRejects(assignRolesToUser(user.id, [role.id]), { + code: 'user.role_exists', + statusCode: 422, + }); }); it('should delete role from user successfully', async () => { diff --git a/packages/integration-tests/src/tests/api/admin-user.search.test.ts b/packages/integration-tests/src/tests/api/admin-user.search.test.ts index 701659fda..e4491057a 100644 --- a/packages/integration-tests/src/tests/api/admin-user.search.test.ts +++ b/packages/integration-tests/src/tests/api/admin-user.search.test.ts @@ -178,8 +178,7 @@ describe('admin console user search params', () => { ['search.primaryEmail', 'jerry_swift_jr_2@geek.best'], ['search.primaryEmail', 'jerry_swift_jr_jr@gmail.com'], ]), - 'request.invalid_input', - '`exact`' + { code: 'request.invalid_input', statusCode: 400, messageIncludes: '`exact`' } ); }); @@ -189,8 +188,11 @@ describe('admin console user search params', () => { ['search.primaryEmail', ''], ['search', 'tom'], ]), - 'request.invalid_input', - 'cannot be empty' + { + code: 'request.invalid_input', + statusCode: 400, + messageIncludes: 'cannot be empty', + } ); }); @@ -200,8 +202,11 @@ describe('admin console user search params', () => { ['search.primaryEmail', '%gmail%'], ['mode.primaryEmail', 'similar_to'], ]), - 'request.invalid_input', - 'case-insensitive' + { + code: 'request.invalid_input', + statusCode: 400, + messageIncludes: 'case-insensitive', + } ); }); @@ -212,21 +217,27 @@ describe('admin console user search params', () => { ['search.primaryEmail', '%gmail%'], ['mode.primaryEmail', 'similar to'], ]), - 'request.invalid_input', - 'is not valid' - ), - expectRejects( - getUsers([['search.email', '%gmail%']]), - 'request.invalid_input', - 'is not valid' + { + code: 'request.invalid_input', + statusCode: 400, + messageIncludes: 'is not valid', + } ), + expectRejects(getUsers([['search.email', '%gmail%']]), { + code: 'request.invalid_input', + statusCode: 400, + messageIncludes: 'is not valid', + }), expectRejects( getUsers([ ['search.primaryEmail', '%gmail%'], ['joint', 'and1'], ]), - 'request.invalid_input', - 'is not valid' + { + code: 'request.invalid_input', + statusCode: 400, + messageIncludes: 'is not valid', + } ), ]); }); diff --git a/packages/integration-tests/src/tests/api/admin-user.test.ts b/packages/integration-tests/src/tests/api/admin-user.test.ts index ab444fe2a..527b86e02 100644 --- a/packages/integration-tests/src/tests/api/admin-user.test.ts +++ b/packages/integration-tests/src/tests/api/admin-user.test.ts @@ -17,8 +17,7 @@ import { postUserIdentity, verifyUserPassword, } from '#src/api/index.js'; -import { createResponseWithCode } from '#src/helpers/admin-tenant.js'; -import { createUserByAdmin } from '#src/helpers/index.js'; +import { createUserByAdmin, expectRejects } from '#src/helpers/index.js'; import { createNewSocialUserWithUsernameAndPassword } from '#src/helpers/interactions.js'; import { generateUsername, generateEmail, generatePhone, generatePassword } from '#src/utils.js'; @@ -38,19 +37,25 @@ describe('admin console user management', () => { generatePhone(), ]; await createUserByAdmin(username, password, email, phone); - await expect(createUserByAdmin(username, password)).rejects.toMatchObject( - createResponseWithCode(422) - ); - await expect(createUserByAdmin(undefined, undefined, email)).rejects.toMatchObject( - createResponseWithCode(422) - ); - await expect(createUserByAdmin(undefined, undefined, undefined, phone)).rejects.toMatchObject( - createResponseWithCode(422) - ); + await expectRejects(createUserByAdmin(username, password), { + code: 'user.username_already_in_use', + statusCode: 422, + }); + await expectRejects(createUserByAdmin(undefined, undefined, email), { + code: 'user.email_already_in_use', + statusCode: 422, + }); + await expectRejects(createUserByAdmin(undefined, undefined, undefined, phone), { + code: 'user.phone_already_in_use', + statusCode: 422, + }); }); it('should fail when get user by invalid id', async () => { - await expect(getUser('invalid-user-id')).rejects.toMatchObject(createResponseWithCode(404)); + await expectRejects(getUser('invalid-user-id'), { + code: 'entity.not_found', + statusCode: 404, + }); }); it('should update userinfo successfully', async () => { @@ -74,7 +79,10 @@ describe('admin console user management', () => { it('should respond 422 when no update data provided', async () => { const user = await createUserByAdmin(); - await expect(updateUser(user.id, {})).rejects.toMatchObject(createResponseWithCode(422)); + await expectRejects(updateUser(user.id, {}), { + code: 'entity.invalid_input', + statusCode: 422, + }); }); it('should fail when update userinfo with conflict identifiers', async () => { @@ -82,15 +90,20 @@ describe('admin console user management', () => { await createUserByAdmin(username, undefined, email, phone); const anotherUser = await createUserByAdmin(); - await expect(updateUser(anotherUser.id, { username })).rejects.toMatchObject( - createResponseWithCode(422) - ); - await expect(updateUser(anotherUser.id, { primaryEmail: email })).rejects.toMatchObject( - createResponseWithCode(422) - ); - await expect(updateUser(anotherUser.id, { primaryPhone: phone })).rejects.toMatchObject( - createResponseWithCode(422) - ); + await expectRejects(updateUser(anotherUser.id, { username }), { + code: 'user.username_already_in_use', + statusCode: 422, + }); + + await expectRejects(updateUser(anotherUser.id, { primaryEmail: email }), { + code: 'user.email_already_in_use', + statusCode: 422, + }); + + await expectRejects(updateUser(anotherUser.id, { primaryPhone: phone }), { + code: 'user.phone_already_in_use', + statusCode: 422, + }); }); it('should delete user successfully', async () => { @@ -171,22 +184,23 @@ describe('admin console user management', () => { it('should return 204 if password is correct', async () => { const user = await createUserByAdmin(undefined, 'new_password'); expect(await verifyUserPassword(user.id, 'new_password')).toHaveProperty('statusCode', 204); - void deleteUser(user.id); + await deleteUser(user.id); }); it('should return 422 if password is incorrect', async () => { const user = await createUserByAdmin(undefined, 'new_password'); - await expect(verifyUserPassword(user.id, 'wrong_password')).rejects.toMatchObject( - createResponseWithCode(422) - ); - void deleteUser(user.id); + await expectRejects(verifyUserPassword(user.id, 'wrong_password'), { + code: 'session.invalid_credentials', + statusCode: 422, + }); + await deleteUser(user.id); }); it('should return 400 if password is empty', async () => { const user = await createUserByAdmin(); - await expect(verifyUserPassword(user.id, '')).rejects.toMatchObject( - createResponseWithCode(400) - ); - void deleteUser(user.id); + await expectRejects(verifyUserPassword(user.id, ''), { + code: 'guard.invalid_input', + statusCode: 400, + }); }); }); diff --git a/packages/integration-tests/src/tests/api/connector.test.ts b/packages/integration-tests/src/tests/api/connector.test.ts index e20863320..240945a6b 100644 --- a/packages/integration-tests/src/tests/api/connector.test.ts +++ b/packages/integration-tests/src/tests/api/connector.test.ts @@ -22,7 +22,7 @@ import { listConnectorFactories, getConnectorFactory, } from '#src/api/connector.js'; -import { createResponseWithCode } from '#src/helpers/admin-tenant.js'; +import { expectRejects } from '#src/helpers/index.js'; const connectorIdMap = new Map(); @@ -158,32 +158,39 @@ test('connector set-up flow', async () => { test('create connector with non-exist connectorId', async () => { await cleanUpConnectorTable(); - await expect(postConnector({ connectorId: 'non-exist-id' })).rejects.toMatchObject( - createResponseWithCode(422) - ); + await expectRejects(postConnector({ connectorId: 'non-exist-id' }), { + code: 'connector.not_found_with_connector_id', + statusCode: 422, + }); }); test('create non standard social connector with target', async () => { await cleanUpConnectorTable(); - await expect( - postConnector({ connectorId: mockSocialConnectorId, metadata: { target: 'target' } }) - ).rejects.toMatchObject(createResponseWithCode(400)); + await expectRejects( + postConnector({ connectorId: mockSocialConnectorId, metadata: { target: 'target' } }), + { + code: 'connector.cannot_overwrite_metadata_for_non_standard_connector', + statusCode: 400, + } + ); }); test('create duplicated social connector', async () => { await cleanUpConnectorTable(); await postConnector({ connectorId: mockSocialConnectorId }); - await expect(postConnector({ connectorId: mockSocialConnectorId })).rejects.toMatchObject( - createResponseWithCode(422) - ); + await expectRejects(postConnector({ connectorId: mockSocialConnectorId }), { + code: 'connector.multiple_instances_not_supported', + statusCode: 422, + }); }); test('override metadata for non-standard social connector', async () => { await cleanUpConnectorTable(); const { id } = await postConnector({ connectorId: mockSocialConnectorId }); - await expect(updateConnectorConfig(id, {}, { target: 'target' })).rejects.toMatchObject( - createResponseWithCode(400) - ); + await expectRejects(updateConnectorConfig(id, {}, { target: 'target' }), { + code: 'connector.cannot_overwrite_metadata_for_non_standard_connector', + statusCode: 400, + }); }); test('send SMS/email test message', async () => { diff --git a/packages/integration-tests/src/tests/api/dashboard.test.ts b/packages/integration-tests/src/tests/api/dashboard.test.ts index 0dc660317..d95e9bc41 100644 --- a/packages/integration-tests/src/tests/api/dashboard.test.ts +++ b/packages/integration-tests/src/tests/api/dashboard.test.ts @@ -2,8 +2,7 @@ import { SignInIdentifier } from '@logto/schemas'; import type { StatisticsData } from '#src/api/index.js'; import { api, getTotalUsersCount, getNewUsersData, getActiveUsersData } from '#src/api/index.js'; -import { createResponseWithCode } from '#src/helpers/admin-tenant.js'; -import { createUserByAdmin } from '#src/helpers/index.js'; +import { createUserByAdmin, expectRejects } from '#src/helpers/index.js'; import { registerNewUser, signInWithPassword } from '#src/helpers/interactions.js'; import { enableAllPasswordSignInMethods } from '#src/helpers/sign-in-experience.js'; import { generateUsername, generatePassword } from '#src/utils.js'; @@ -18,13 +17,18 @@ describe('admin console dashboard', () => { }); it('non authorized request should return 401', async () => { - await expect(api.get('dashboard/users/total')).rejects.toMatchObject( - createResponseWithCode(401) - ); - await expect(api.get('dashboard/users/new')).rejects.toMatchObject(createResponseWithCode(401)); - await expect(api.get('dashboard/users/active')).rejects.toMatchObject( - createResponseWithCode(401) - ); + await expectRejects(api.get('dashboard/users/total'), { + code: 'auth.authorization_header_missing', + statusCode: 401, + }); + await expectRejects(api.get('dashboard/users/new'), { + code: 'auth.authorization_header_missing', + statusCode: 401, + }); + await expectRejects(api.get('dashboard/users/active'), { + code: 'auth.authorization_header_missing', + statusCode: 401, + }); }); it('should get total user count successfully', async () => { diff --git a/packages/integration-tests/src/tests/api/hook/hook.test.ts b/packages/integration-tests/src/tests/api/hook/hook.test.ts index 7b865101b..3b2d0f955 100644 --- a/packages/integration-tests/src/tests/api/hook/hook.test.ts +++ b/packages/integration-tests/src/tests/api/hook/hook.test.ts @@ -2,8 +2,8 @@ import type { Hook } from '@logto/schemas'; import { HookEvent } from '@logto/schemas'; import { authedAdminApi } from '#src/api/index.js'; -import { createResponseWithCode } from '#src/helpers/admin-tenant.js'; import { getHookCreationPayload } from '#src/helpers/hook.js'; +import { expectRejects } from '#src/helpers/index.js'; describe('hooks', () => { it('should be able to create, query, update, and delete a hook', async () => { @@ -20,10 +20,10 @@ describe('hooks', () => { .json() ).toMatchObject({ ...created, events: [HookEvent.PostSignIn] }); expect(await authedAdminApi.delete(`hooks/${created.id}`)).toHaveProperty('statusCode', 204); - await expect(authedAdminApi.get(`hooks/${created.id}`)).rejects.toHaveProperty( - 'response.statusCode', - 404 - ); + await expectRejects(authedAdminApi.get(`hooks/${created.id}`), { + code: 'entity.not_exists_with_id', + statusCode: 404, + }); }); it('should be able to create, query, update, and delete a hook by the original API', async () => { @@ -49,10 +49,10 @@ describe('hooks', () => { event: HookEvent.PostSignIn, }); expect(await authedAdminApi.delete(`hooks/${created.id}`)).toHaveProperty('statusCode', 204); - await expect(authedAdminApi.get(`hooks/${created.id}`)).rejects.toHaveProperty( - 'response.statusCode', - 404 - ); + await expectRejects(authedAdminApi.get(`hooks/${created.id}`), { + code: 'entity.not_exists_with_id', + statusCode: 404, + }); }); it('should return hooks with pagination if pagination-related query params are provided', async () => { @@ -76,9 +76,10 @@ describe('hooks', () => { url: 'not_work_url', }, }; - await expect(authedAdminApi.post('hooks', { json: payload })).rejects.toMatchObject( - createResponseWithCode(400) - ); + await expectRejects(authedAdminApi.post('hooks', { json: payload }), { + code: 'guard.invalid_input', + statusCode: 400, + }); }); it('should throw error when no event is provided when creating a hook', async () => { @@ -88,9 +89,10 @@ describe('hooks', () => { url: 'not_work_url', }, }; - await expect(authedAdminApi.post('hooks', { json: payload })).rejects.toMatchObject( - createResponseWithCode(400) - ); + await expectRejects(authedAdminApi.post('hooks', { json: payload }), { + code: 'hook.missing_events', + statusCode: 400, + }); }); it('should throw error if update a hook with a invalid hook id', async () => { @@ -98,14 +100,16 @@ describe('hooks', () => { name: 'new_hook_name', }; - await expect(authedAdminApi.patch('hooks/invalid_id', { json: payload })).rejects.toMatchObject( - createResponseWithCode(404) - ); + await expectRejects(authedAdminApi.patch('hooks/invalid_id', { json: payload }), { + code: 'entity.not_exists', + statusCode: 404, + }); }); it('should throw error if regenerate a hook signing key with a invalid hook id', async () => { - await expect(authedAdminApi.patch('hooks/invalid_id/signing-key')).rejects.toMatchObject( - createResponseWithCode(404) - ); + await expectRejects(authedAdminApi.patch('hooks/invalid_id/signing-key'), { + code: 'entity.not_exists', + statusCode: 404, + }); }); }); diff --git a/packages/integration-tests/src/tests/api/hook/hook.testing.test.ts b/packages/integration-tests/src/tests/api/hook/hook.testing.test.ts index 97a739532..10ebf9bf7 100644 --- a/packages/integration-tests/src/tests/api/hook/hook.testing.test.ts +++ b/packages/integration-tests/src/tests/api/hook/hook.testing.test.ts @@ -1,9 +1,8 @@ import { HookEvent, type Hook } from '@logto/schemas'; import { authedAdminApi } from '#src/api/api.js'; -import { createResponseWithCode } from '#src/helpers/admin-tenant.js'; import { getHookCreationPayload } from '#src/helpers/hook.js'; -import { createMockServer } from '#src/helpers/index.js'; +import { createMockServer, expectRejects } from '#src/helpers/index.js'; const responseSuccessPort = 9999; const responseSuccessEndpoint = `http://localhost:${responseSuccessPort}`; @@ -46,21 +45,29 @@ describe('hook testing', () => { it('should return 404 if the hook to test does not exist', async () => { const invalidHookId = 'invalid_id'; - await expect( + await expectRejects( authedAdminApi.post(`hooks/${invalidHookId}/test`, { json: { events: [HookEvent.PostSignIn], config: { url: responseSuccessEndpoint } }, - }) - ).rejects.toMatchObject(createResponseWithCode(404)); + }), + { + code: 'entity.not_exists_with_id', + statusCode: 404, + } + ); }); it('should return 422 if the hook endpoint is not working', async () => { const payload = getHookCreationPayload(HookEvent.PostRegister); const created = await authedAdminApi.post('hooks', { json: payload }).json(); - await expect( + await expectRejects( authedAdminApi.post(`hooks/${created.id}/test`, { json: { events: [HookEvent.PostSignIn], config: { url: 'not_work_url' } }, - }) - ).rejects.toMatchObject(createResponseWithCode(422)); + }), + { + code: 'hook.send_test_payload_failed', + statusCode: 422, + } + ); // Clean Up await authedAdminApi.delete(`hooks/${created.id}`); diff --git a/packages/integration-tests/src/tests/api/interaction/forgot-password.test.ts b/packages/integration-tests/src/tests/api/interaction/forgot-password.test.ts index 236a88588..813b09f2c 100644 --- a/packages/integration-tests/src/tests/api/interaction/forgot-password.test.ts +++ b/packages/integration-tests/src/tests/api/interaction/forgot-password.test.ts @@ -62,11 +62,17 @@ describe('reset password', () => { verificationCode: code, }); - await expectRejects(client.submitInteraction(), 'user.new_password_required_in_profile'); + await expectRejects(client.submitInteraction(), { + code: 'user.new_password_required_in_profile', + statusCode: 422, + }); await client.successSend(putInteractionProfile, { password: userProfile.password }); - await expectRejects(client.submitInteraction(), 'user.same_password'); + await expectRejects(client.submitInteraction(), { + code: 'user.same_password', + statusCode: 422, + }); const newPasswordRecord = generatePassword(); @@ -115,11 +121,17 @@ describe('reset password', () => { verificationCode: code, }); - await expectRejects(client.submitInteraction(), 'user.new_password_required_in_profile'); + await expectRejects(client.submitInteraction(), { + code: 'user.new_password_required_in_profile', + statusCode: 422, + }); await client.successSend(putInteractionProfile, { password: userProfile.password }); - await expectRejects(client.submitInteraction(), 'user.same_password'); + await expectRejects(client.submitInteraction(), { + code: 'user.same_password', + statusCode: 422, + }); const newPasswordRecord = generatePassword(); diff --git a/packages/integration-tests/src/tests/api/interaction/register-with-identifier.test.ts b/packages/integration-tests/src/tests/api/interaction/register-with-identifier.test.ts index 4a378f0ac..aaeace2e5 100644 --- a/packages/integration-tests/src/tests/api/interaction/register-with-identifier.test.ts +++ b/packages/integration-tests/src/tests/api/interaction/register-with-identifier.test.ts @@ -138,7 +138,10 @@ describe('Register with passwordless identifier', () => { email: primaryEmail, }); - await expectRejects(client.submitInteraction(), 'user.missing_profile'); + await expectRejects(client.submitInteraction(), { + code: 'user.missing_profile', + statusCode: 422, + }); await client.successSend(patchInteractionProfile, { password, @@ -240,7 +243,10 @@ describe('Register with passwordless identifier', () => { phone: primaryPhone, }); - await expectRejects(client.submitInteraction(), 'user.missing_profile'); + await expectRejects(client.submitInteraction(), { + code: 'user.missing_profile', + statusCode: 422, + }); await client.successSend(patchInteractionProfile, { password, @@ -308,7 +314,10 @@ describe('Register with passwordless identifier', () => { email: primaryEmail, }); - await expectRejects(client.submitInteraction(), 'user.email_already_in_use'); + await expectRejects(client.submitInteraction(), { + code: 'user.email_already_in_use', + statusCode: 422, + }); await client.successSend(deleteInteractionProfile); await client.successSend(putInteractionEvent, { event: InteractionEvent.SignIn }); @@ -360,7 +369,10 @@ describe('Register with passwordless identifier', () => { phone: primaryPhone, }); - await expectRejects(client.submitInteraction(), 'user.phone_already_in_use'); + await expectRejects(client.submitInteraction(), { + code: 'user.phone_already_in_use', + statusCode: 422, + }); await client.successSend(deleteInteractionProfile); await client.successSend(putInteractionEvent, { event: InteractionEvent.SignIn }); diff --git a/packages/integration-tests/src/tests/api/interaction/sign-in-with-passcode-identifier.test.ts b/packages/integration-tests/src/tests/api/interaction/sign-in-with-passcode-identifier.test.ts index 2bc7d4838..fd036a32a 100644 --- a/packages/integration-tests/src/tests/api/interaction/sign-in-with-passcode-identifier.test.ts +++ b/packages/integration-tests/src/tests/api/interaction/sign-in-with-passcode-identifier.test.ts @@ -124,7 +124,10 @@ describe('Sign-In flow using verification-code identifiers', () => { verificationCode: code, }); - await expectRejects(client.submitInteraction(), 'user.user_not_exist'); + await expectRejects(client.submitInteraction(), { + code: 'user.user_not_exist', + statusCode: 404, + }); await client.successSend(putInteractionEvent, { event: InteractionEvent.Register }); await client.successSend(putInteractionProfile, { email: newEmail }); @@ -163,7 +166,10 @@ describe('Sign-In flow using verification-code identifiers', () => { verificationCode: code, }); - await expectRejects(client.submitInteraction(), 'user.user_not_exist'); + await expectRejects(client.submitInteraction(), { + code: 'user.user_not_exist', + statusCode: 404, + }); await client.successSend(putInteractionEvent, { event: InteractionEvent.Register }); await client.successSend(putInteractionProfile, { phone: newPhone }); @@ -205,7 +211,10 @@ describe('Sign-In flow using verification-code identifiers', () => { verificationCode: code, }); - await expectRejects(client.submitInteraction(), 'user.missing_profile'); + await expectRejects(client.submitInteraction(), { + code: 'user.missing_profile', + statusCode: 422, + }); // Fulfill user profile await client.successSend(putInteractionProfile, { @@ -264,7 +273,10 @@ describe('Sign-In flow using verification-code identifiers', () => { verificationCode: code, }); - await expectRejects(client.submitInteraction(), 'user.missing_profile'); + await expectRejects(client.submitInteraction(), { + code: 'user.missing_profile', + statusCode: 422, + }); // Fulfill user profile with existing password await client.successSend(putInteractionProfile, { @@ -272,7 +284,10 @@ describe('Sign-In flow using verification-code identifiers', () => { password, }); - await expectRejects(client.submitInteraction(), 'user.password_exists_in_profile'); + await expectRejects(client.submitInteraction(), { + code: 'user.password_exists_in_profile', + statusCode: 400, + }); await client.successSend(putInteractionProfile, { username, @@ -315,7 +330,10 @@ describe('Sign-In flow using verification-code identifiers', () => { verificationCode: code, }); - await expectRejects(client.submitInteraction(), 'user.missing_profile'); + await expectRejects(client.submitInteraction(), { + code: 'user.missing_profile', + statusCode: 422, + }); // Fulfill user profile with existing password await client.successSend(putInteractionProfile, { @@ -323,7 +341,10 @@ describe('Sign-In flow using verification-code identifiers', () => { password, }); - await expectRejects(client.submitInteraction(), 'user.username_already_in_use'); + await expectRejects(client.submitInteraction(), { + code: 'user.username_already_in_use', + statusCode: 422, + }); await client.successSend(putInteractionProfile, { username, diff --git a/packages/integration-tests/src/tests/api/interaction/sign-in-with-password-identifier.test.ts b/packages/integration-tests/src/tests/api/interaction/sign-in-with-password-identifier.test.ts index 84b7f7883..c3ae4ddf7 100644 --- a/packages/integration-tests/src/tests/api/interaction/sign-in-with-password-identifier.test.ts +++ b/packages/integration-tests/src/tests/api/interaction/sign-in-with-password-identifier.test.ts @@ -112,7 +112,10 @@ describe('Sign-In flow using password identifiers', () => { }, }); - await expectRejects(client.submitInteraction(), 'user.missing_profile'); + await expectRejects(client.submitInteraction(), { + code: 'user.missing_profile', + statusCode: 422, + }); await client.successSend(sendVerificationCode, { email: primaryEmail, @@ -171,7 +174,10 @@ describe('Sign-In flow using password identifiers', () => { }, }); - await expectRejects(client.submitInteraction(), 'user.missing_profile'); + await expectRejects(client.submitInteraction(), { + code: 'user.missing_profile', + statusCode: 422, + }); await client.successSend(sendVerificationCode, { phone: primaryPhone, diff --git a/packages/integration-tests/src/tests/api/interaction/social-interaction.test.ts b/packages/integration-tests/src/tests/api/interaction/social-interaction.test.ts index bdc4b9579..8fe614651 100644 --- a/packages/integration-tests/src/tests/api/interaction/social-interaction.test.ts +++ b/packages/integration-tests/src/tests/api/interaction/social-interaction.test.ts @@ -63,7 +63,10 @@ describe('Social Identifier Interactions', () => { connectorData: { state, redirectUri, code, userId: socialUserId }, }); - await expectRejects(client.submitInteraction(), 'user.identity_not_exist'); + await expectRejects(client.submitInteraction(), { + code: 'user.identity_not_exist', + statusCode: 422, + }); await client.successSend(putInteractionEvent, { event: InteractionEvent.Register }); await client.successSend(putInteractionProfile, { connectorId }); @@ -116,7 +119,10 @@ describe('Social Identifier Interactions', () => { connectorData: { state, redirectUri, code, userId: socialUserId, email: socialEmail }, }); - await expectRejects(client.submitInteraction(), 'user.identity_not_exist'); + await expectRejects(client.submitInteraction(), { + code: 'user.identity_not_exist', + statusCode: 422, + }); await client.successSend(putInteractionEvent, { event: InteractionEvent.Register }); await client.successSend(putInteractionProfile, { connectorId }); @@ -149,7 +155,10 @@ describe('Social Identifier Interactions', () => { connectorData: { state, redirectUri, code, userId: socialUserId, phone: socialPhone }, }); - await expectRejects(client.submitInteraction(), 'user.identity_not_exist'); + await expectRejects(client.submitInteraction(), { + code: 'user.identity_not_exist', + statusCode: 422, + }); await client.successSend(putInteractionEvent, { event: InteractionEvent.Register }); await client.successSend(putInteractionProfile, { connectorId }); @@ -192,7 +201,10 @@ describe('Social Identifier Interactions', () => { }, }); - await expectRejects(client.submitInteraction(), 'user.identity_not_exist'); + await expectRejects(client.submitInteraction(), { + code: 'user.identity_not_exist', + statusCode: 422, + }); await client.successSend(patchInteractionIdentifiers, { connectorId, @@ -253,12 +265,18 @@ describe('Social Identifier Interactions', () => { connectorData: { state, redirectUri, code, userId: socialUserId }, }); - await expectRejects(client.submitInteraction(), 'user.identity_not_exist'); + await expectRejects(client.submitInteraction(), { + code: 'user.identity_not_exist', + statusCode: 422, + }); await client.successSend(putInteractionEvent, { event: InteractionEvent.Register }); await client.successSend(putInteractionProfile, { connectorId }); - await expectRejects(client.submitInteraction(), 'user.missing_profile'); + await expectRejects(client.submitInteraction(), { + code: 'user.missing_profile', + statusCode: 422, + }); await client.successSend(patchInteractionProfile, { username: generateUsername() }); @@ -291,7 +309,10 @@ describe('Social Identifier Interactions', () => { connectorData: { state, redirectUri, code, userId: socialUserId, email: generateEmail() }, }); - await expectRejects(client.submitInteraction(), 'user.identity_not_exist'); + await expectRejects(client.submitInteraction(), { + code: 'user.identity_not_exist', + statusCode: 422, + }); await client.successSend(putInteractionEvent, { event: InteractionEvent.Register }); await client.successSend(putInteractionProfile, { connectorId }); @@ -325,7 +346,10 @@ describe('Social Identifier Interactions', () => { connectorData: { state, redirectUri, code, userId: socialUserId, phone: generatePhone() }, }); - await expectRejects(client.submitInteraction(), 'user.identity_not_exist'); + await expectRejects(client.submitInteraction(), { + code: 'user.identity_not_exist', + statusCode: 422, + }); await client.successSend(putInteractionEvent, { event: InteractionEvent.Register }); await client.successSend(putInteractionProfile, { connectorId }); diff --git a/packages/integration-tests/src/tests/api/log.test.ts b/packages/integration-tests/src/tests/api/log.test.ts index 0e1ec704b..a9c06cd2e 100644 --- a/packages/integration-tests/src/tests/api/log.test.ts +++ b/packages/integration-tests/src/tests/api/log.test.ts @@ -1,5 +1,5 @@ import { getLog, getAuditLogs } from '#src/api/index.js'; -import { createResponseWithCode } from '#src/helpers/admin-tenant.js'; +import { expectRejects } from '#src/helpers/index.js'; describe('logs', () => { it('should get logs successfully', async () => { @@ -20,6 +20,9 @@ describe('logs', () => { }); it('should throw on getting non-exist log detail', async () => { - await expect(getLog('non-exist-log-id')).rejects.toMatchObject(createResponseWithCode(404)); + await expectRejects(getLog('non-exist-log-id'), { + code: 'entity.not_exists_with_id', + statusCode: 404, + }); }); }); diff --git a/packages/integration-tests/src/tests/api/me.test.ts b/packages/integration-tests/src/tests/api/me.test.ts index 07c67df4e..6c01f8c1f 100644 --- a/packages/integration-tests/src/tests/api/me.test.ts +++ b/packages/integration-tests/src/tests/api/me.test.ts @@ -2,18 +2,19 @@ import { got } from 'got'; import { logtoConsoleUrl, logtoUrl } from '#src/constants.js'; import { - createResponseWithCode, createUserWithAllRolesAndSignInToClient, deleteUser, resourceDefault, resourceMe, } from '#src/helpers/admin-tenant.js'; +import { expectRejects } from '#src/helpers/index.js'; describe('me', () => { it('should only be available in admin tenant', async () => { - await expect(got.get(new URL('/me/custom-data', logtoConsoleUrl))).rejects.toMatchObject( - createResponseWithCode(401) - ); + await expectRejects(got.get(new URL('/me/custom-data', logtoConsoleUrl)), { + code: 'auth.authorization_header_missing', + statusCode: 401, + }); // Redirect to UI const response = await got.get(new URL('/me/custom-data', logtoUrl)); @@ -24,11 +25,15 @@ describe('me', () => { it('should only recognize the access token with correct resource and scope', async () => { const { id, client } = await createUserWithAllRolesAndSignInToClient(); - await expect( + await expectRejects( got.get(logtoConsoleUrl + '/me/custom-data', { headers: { authorization: `Bearer ${await client.getAccessToken(resourceDefault)}` }, - }) - ).rejects.toMatchObject(createResponseWithCode(401)); + }), + { + code: 'auth.unauthorized', + statusCode: 401, + } + ); await expect( got.get(logtoConsoleUrl + '/me/custom-data', { diff --git a/packages/integration-tests/src/tests/api/resource.test.ts b/packages/integration-tests/src/tests/api/resource.test.ts index 572d6cee1..9ddd87906 100644 --- a/packages/integration-tests/src/tests/api/resource.test.ts +++ b/packages/integration-tests/src/tests/api/resource.test.ts @@ -9,7 +9,7 @@ import { deleteResource, setDefaultResource, } from '#src/api/index.js'; -import { createResponseWithCode } from '#src/helpers/admin-tenant.js'; +import { expectRejects } from '#src/helpers/index.js'; import { generateResourceIndicator, generateResourceName } from '#src/utils.js'; describe('admin console api resources', () => { @@ -47,9 +47,10 @@ describe('admin console api resources', () => { // Create second resource with same indicator should throw const resourceName2 = generateResourceName(); - await expect(createResource(resourceName2, resourceIndicator)).rejects.toMatchObject( - createResponseWithCode(422) - ); + await expectRejects(createResource(resourceName2, resourceIndicator), { + code: 'resource.resource_identifier_in_use', + statusCode: 422, + }); }); it('should get resource list successfully', async () => { diff --git a/packages/integration-tests/src/tests/api/sign-in-experience.test.ts b/packages/integration-tests/src/tests/api/sign-in-experience.test.ts index 06021e38f..a35d2b6c6 100644 --- a/packages/integration-tests/src/tests/api/sign-in-experience.test.ts +++ b/packages/integration-tests/src/tests/api/sign-in-experience.test.ts @@ -1,7 +1,7 @@ import { SignInIdentifier } from '@logto/schemas'; import { getSignInExperience, updateSignInExperience } from '#src/api/index.js'; -import { createResponseWithCode } from '#src/helpers/admin-tenant.js'; +import { expectRejects } from '#src/helpers/index.js'; describe('admin console sign-in experience', () => { it('should get sign-in experience successfully', async () => { @@ -38,8 +38,9 @@ describe('admin console sign-in experience', () => { }, }; - await expect(updateSignInExperience(newSignInExperience)).rejects.toMatchObject( - createResponseWithCode(400) - ); + await expectRejects(updateSignInExperience(newSignInExperience), { + code: 'sign_in_experiences.username_requires_password', + statusCode: 400, + }); }); }); diff --git a/packages/integration-tests/src/tests/api/verification-code.test.ts b/packages/integration-tests/src/tests/api/verification-code.test.ts index b4cc9c48e..9a18d3a46 100644 --- a/packages/integration-tests/src/tests/api/verification-code.test.ts +++ b/packages/integration-tests/src/tests/api/verification-code.test.ts @@ -2,13 +2,12 @@ import { VerificationCodeType } from '@logto/connector-kit'; import { ConnectorType, type RequestVerificationCodePayload } from '@logto/schemas'; import { requestVerificationCode, verifyVerificationCode } from '#src/api/verification-code.js'; -import { createResponseWithCode } from '#src/helpers/admin-tenant.js'; import { clearConnectorsByTypes, setEmailConnector, setSmsConnector, } from '#src/helpers/connector.js'; -import { readVerificationCode, removeVerificationCode } from '#src/helpers/index.js'; +import { expectRejects, readVerificationCode, removeVerificationCode } from '#src/helpers/index.js'; import { enableAllVerificationCodeSignInMethods } from '#src/helpers/sign-in-experience.js'; describe('Generic verification code through management API', () => { @@ -55,9 +54,10 @@ describe('Generic verification code through management API', () => { }); it('should fail to create a verification code on server side when the email and phone are not provided', async () => { - await expect(requestVerificationCode({ username: 'any_string' })).rejects.toMatchObject( - createResponseWithCode(400) - ); + await expectRejects(requestVerificationCode({ username: 'any_string' }), { + code: 'guard.invalid_input', + statusCode: 400, + }); await expect(readVerificationCode()).rejects.toThrow(); }); @@ -65,9 +65,10 @@ describe('Generic verification code through management API', () => { it('should fail to send a verification code on server side when no email connector has been set', async () => { const emailForTestSendCode = 'test_send@email.com'; await clearConnectorsByTypes([ConnectorType.Email]); - await expect(requestVerificationCode({ email: emailForTestSendCode })).rejects.toMatchObject( - createResponseWithCode(400) - ); + await expectRejects(requestVerificationCode({ email: emailForTestSendCode }), { + code: 'connector.not_found', + statusCode: 400, + }); await expect( verifyVerificationCode({ email: emailForTestSendCode, verificationCode: 'any_string' }) @@ -88,9 +89,10 @@ describe('Generic verification code through management API', () => { it('should fail to send a verification code on server side when no SMS connector has not been set', async () => { const phoneForTestSendCode = '1233212321'; await clearConnectorsByTypes([ConnectorType.Sms]); - await expect(requestVerificationCode({ phone: phoneForTestSendCode })).rejects.toMatchObject( - createResponseWithCode(400) - ); + await expectRejects(requestVerificationCode({ phone: phoneForTestSendCode }), { + code: 'connector.not_found', + statusCode: 400, + }); await expect( verifyVerificationCode({ phone: phoneForTestSendCode, verificationCode: 'any_string' }) @@ -131,9 +133,10 @@ describe('Generic verification code through management API', () => { it('should throw when the code is not valid', async () => { await requestVerificationCode({ phone: mockPhone }); await readVerificationCode(); - await expect( - verifyVerificationCode({ phone: mockPhone, verificationCode: '666' }) - ).rejects.toMatchObject(createResponseWithCode(400)); + await expectRejects(verifyVerificationCode({ phone: mockPhone, verificationCode: '666' }), { + code: 'verification_code.code_mismatch', + statusCode: 400, + }); }); it('should throw when the phone number is not matched', async () => { @@ -142,9 +145,10 @@ describe('Generic verification code through management API', () => { await requestVerificationCode({ phone: phoneToGetCode }); const { code, phone } = await readVerificationCode(); expect(phoneToGetCode).toEqual(phone); - await expect( - verifyVerificationCode({ phone: phoneToVerify, verificationCode: code }) - ).rejects.toMatchObject(createResponseWithCode(400)); + await expectRejects(verifyVerificationCode({ phone: phoneToVerify, verificationCode: code }), { + code: 'verification_code.not_found', + statusCode: 400, + }); }); it('should throw when the email is not matched', async () => { @@ -153,8 +157,9 @@ describe('Generic verification code through management API', () => { await requestVerificationCode({ email: emailToGetCode }); const { code, address } = await readVerificationCode(); expect(emailToGetCode).toEqual(address); - await expect( - verifyVerificationCode({ email: emailToVerify, verificationCode: code }) - ).rejects.toMatchObject(createResponseWithCode(400)); + await expectRejects(verifyVerificationCode({ email: emailToVerify, verificationCode: code }), { + code: 'verification_code.not_found', + statusCode: 400, + }); }); });