From 37c3f5952d043557d5161e4342bdc7c8ee2012e5 Mon Sep 17 00:00:00 2001 From: Xiao Yijun Date: Wed, 24 May 2023 15:01:32 +0800 Subject: [PATCH] refactor(test): split hook integration tests into several files (#3891) --- .../integration-tests/src/helpers/hook.ts | 17 + .../src/helpers/interactions.ts | 3 +- .../src/tests/api/hook/hook.stats.test.ts | 91 +++++ .../src/tests/api/hook/hook.test.ts | 98 +++++ .../src/tests/api/hook/hook.trigger.test.ts | 185 +++++++++ .../src/tests/api/hooks.test.ts | 381 ------------------ 6 files changed, 393 insertions(+), 382 deletions(-) create mode 100644 packages/integration-tests/src/helpers/hook.ts create mode 100644 packages/integration-tests/src/tests/api/hook/hook.stats.test.ts create mode 100644 packages/integration-tests/src/tests/api/hook/hook.test.ts create mode 100644 packages/integration-tests/src/tests/api/hook/hook.trigger.test.ts delete mode 100644 packages/integration-tests/src/tests/api/hooks.test.ts diff --git a/packages/integration-tests/src/helpers/hook.ts b/packages/integration-tests/src/helpers/hook.ts new file mode 100644 index 000000000..b2dea044b --- /dev/null +++ b/packages/integration-tests/src/helpers/hook.ts @@ -0,0 +1,17 @@ +import { type Hook, type HookConfig, type HookEvent } from '@logto/schemas'; + +type HookCreationPayload = Pick & { + config: HookConfig; +}; + +export const getHookCreationPayload = ( + event: HookEvent, + url = 'not_work_url' +): HookCreationPayload => ({ + name: 'hook_name', + events: [event], + config: { + url, + headers: { foo: 'bar' }, + }, +}); diff --git a/packages/integration-tests/src/helpers/interactions.ts b/packages/integration-tests/src/helpers/interactions.ts index 225923549..13a4c7776 100644 --- a/packages/integration-tests/src/helpers/interactions.ts +++ b/packages/integration-tests/src/helpers/interactions.ts @@ -30,8 +30,9 @@ export const registerNewUser = async (username: string, password: string) => { }); const { redirectTo } = await client.submitInteraction(); - await processSession(client, redirectTo); + const userId = await processSession(client, redirectTo); await logoutClient(client); + return userId; }; export const signInWithPassword = async ( diff --git a/packages/integration-tests/src/tests/api/hook/hook.stats.test.ts b/packages/integration-tests/src/tests/api/hook/hook.stats.test.ts new file mode 100644 index 000000000..39bebae95 --- /dev/null +++ b/packages/integration-tests/src/tests/api/hook/hook.stats.test.ts @@ -0,0 +1,91 @@ +import { + type Hook, + HookEvent, + type HookResponse, + type Log, + LogResult, + SignInIdentifier, +} from '@logto/schemas'; + +import { deleteUser } from '#src/api/admin-user.js'; +import { authedAdminApi } from '#src/api/api.js'; +import { getHookCreationPayload } from '#src/helpers/hook.js'; +import { createMockServer } from '#src/helpers/index.js'; +import { registerNewUser } from '#src/helpers/interactions.js'; +import { enableAllPasswordSignInMethods } from '#src/helpers/sign-in-experience.js'; +import { generateNewUserProfile } from '#src/helpers/user.js'; + +describe('hook logs', () => { + const { listen, close } = createMockServer(9999); + + beforeAll(async () => { + await enableAllPasswordSignInMethods({ + identifiers: [SignInIdentifier.Username], + password: true, + verify: false, + }); + await listen(); + }); + + afterAll(async () => { + await close(); + }); + + it('should get recent hook logs correctly', async () => { + const createdHook = await authedAdminApi + .post('hooks', { + json: getHookCreationPayload(HookEvent.PostRegister, 'http://localhost:9999'), + }) + .json(); + + const { username, password } = generateNewUserProfile({ username: true, password: true }); + const userId = await registerNewUser(username, password); + + const logs = await authedAdminApi + .get(`hooks/${createdHook.id}/recent-logs?page_size=100`) + .json(); + expect( + logs.some( + ({ payload: { hookId, result } }) => + hookId === createdHook.id && result === LogResult.Success + ) + ).toBeTruthy(); + + await authedAdminApi.delete(`hooks/${createdHook.id}`); + + await deleteUser(userId); + }); + + it('should get hook execution stats correctly', async () => { + const createdHook = await authedAdminApi + .post('hooks', { + json: getHookCreationPayload(HookEvent.PostRegister, 'http://localhost:9999'), + }) + .json(); + + const hooksWithExecutionStats = await authedAdminApi + .get('hooks?includeExecutionStats=true') + .json(); + + for (const hook of hooksWithExecutionStats) { + expect(hook.executionStats).toBeTruthy(); + } + + const { username, password } = generateNewUserProfile({ username: true, password: true }); + const userId = await registerNewUser(username, password); + + const hookWithExecutionStats = await authedAdminApi + .get(`hooks/${createdHook.id}?includeExecutionStats=true`) + .json(); + + const { executionStats } = hookWithExecutionStats; + + expect(executionStats).toBeTruthy(); + expect(executionStats.requestCount).toBe(1); + expect(executionStats.successCount).toBe(1); + + await authedAdminApi.delete(`hooks/${createdHook.id}`); + + await deleteUser(userId); + }); +}); diff --git a/packages/integration-tests/src/tests/api/hook/hook.test.ts b/packages/integration-tests/src/tests/api/hook/hook.test.ts new file mode 100644 index 000000000..20d75b25b --- /dev/null +++ b/packages/integration-tests/src/tests/api/hook/hook.test.ts @@ -0,0 +1,98 @@ +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'; + +describe('hooks', () => { + it('should be able to create, query, update, and delete a hook', async () => { + const payload = getHookCreationPayload(HookEvent.PostRegister); + const created = await authedAdminApi.post('hooks', { json: payload }).json(); + + expect(created).toMatchObject(payload); + + expect(await authedAdminApi.get('hooks').json()).toContainEqual(created); + expect(await authedAdminApi.get(`hooks/${created.id}`).json()).toEqual(created); + expect( + await authedAdminApi + .patch(`hooks/${created.id}`, { json: { events: [HookEvent.PostSignIn] } }) + .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 + ); + }); + + it('should be able to create, query, update, and delete a hook by the original API', async () => { + const payload = { + event: HookEvent.PostRegister, + config: { + url: 'not_work_url', + retries: 2, + }, + }; + const created = await authedAdminApi.post('hooks', { json: payload }).json(); + + expect(created).toMatchObject(payload); + + expect(await authedAdminApi.get('hooks').json()).toContainEqual(created); + expect(await authedAdminApi.get(`hooks/${created.id}`).json()).toEqual(created); + expect( + await authedAdminApi + .patch(`hooks/${created.id}`, { json: { event: HookEvent.PostSignIn } }) + .json() + ).toMatchObject({ + ...created, + 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 + ); + }); + + it('should throw error when creating a hook with an empty hook name', async () => { + const payload = { + name: '', + events: [HookEvent.PostRegister], + config: { + url: 'not_work_url', + }, + }; + await expect(authedAdminApi.post('hooks', { json: payload })).rejects.toMatchObject( + createResponseWithCode(400) + ); + }); + + it('should throw error when no event is provided when creating a hook', async () => { + const payload = { + name: 'hook_name', + config: { + url: 'not_work_url', + }, + }; + await expect(authedAdminApi.post('hooks', { json: payload })).rejects.toMatchObject( + createResponseWithCode(400) + ); + }); + + it('should throw error if update a hook with a invalid hook id', async () => { + const payload = { + name: 'new_hook_name', + }; + + await expect(authedAdminApi.patch('hooks/invalid_id', { json: payload })).rejects.toMatchObject( + createResponseWithCode(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) + ); + }); +}); diff --git a/packages/integration-tests/src/tests/api/hook/hook.trigger.test.ts b/packages/integration-tests/src/tests/api/hook/hook.trigger.test.ts new file mode 100644 index 000000000..02acf7ef8 --- /dev/null +++ b/packages/integration-tests/src/tests/api/hook/hook.trigger.test.ts @@ -0,0 +1,185 @@ +import { createHmac } from 'node:crypto'; +import { type RequestListener } from 'node:http'; + +import { + type Hook, + HookEvent, + type LogKey, + LogResult, + SignInIdentifier, + type Log, +} from '@logto/schemas'; + +import { deleteUser } from '#src/api/admin-user.js'; +import { authedAdminApi } from '#src/api/api.js'; +import { getLogs } from '#src/api/logs.js'; +import { getHookCreationPayload } from '#src/helpers/hook.js'; +import { createMockServer } from '#src/helpers/index.js'; +import { registerNewUser, signInWithPassword } from '#src/helpers/interactions.js'; +import { enableAllPasswordSignInMethods } from '#src/helpers/sign-in-experience.js'; +import { generateNewUser, generateNewUserProfile } from '#src/helpers/user.js'; + +type HookSecureData = { + signature: string; + payload: string; +}; + +// Note: return hook payload and signature for webhook security testing +const hookServerRequestListener: RequestListener = (request, response) => { + // eslint-disable-next-line @silverhand/fp/no-mutation + response.statusCode = 204; + + const data: Buffer[] = []; + request.on('data', (chunk: Buffer) => { + // eslint-disable-next-line @silverhand/fp/no-mutating-methods + data.push(chunk); + }); + + request.on('end', () => { + response.writeHead(200, { 'Content-Type': 'application/json' }); + const payload = Buffer.concat(data).toString(); + response.end( + JSON.stringify({ + signature: request.headers['logto-signature-sha-256'] as string, + payload, + } satisfies HookSecureData) + ); + }); +}; + +describe('trigger hooks', () => { + const { listen, close } = createMockServer(9999, hookServerRequestListener); + + beforeAll(async () => { + await enableAllPasswordSignInMethods({ + identifiers: [SignInIdentifier.Username], + password: true, + verify: false, + }); + await listen(); + }); + + afterAll(async () => { + await close(); + }); + + it('should trigger sign-in hook and record error when interaction finished', async () => { + const createdHook = await authedAdminApi + .post('hooks', { json: getHookCreationPayload(HookEvent.PostSignIn) }) + .json(); + const logKey: LogKey = 'TriggerHook.PostSignIn'; + + const { + userProfile: { username, password }, + user, + } = await generateNewUser({ username: true, password: true }); + + await signInWithPassword({ username, password }); + + // Check hook trigger log + const logs = await getLogs(new URLSearchParams({ logKey, page_size: '100' })); + expect( + logs.some( + ({ payload: { hookId, result, error } }) => + hookId === createdHook.id && + result === LogResult.Error && + error === 'RequestError: Invalid URL' + ) + ).toBeTruthy(); + + // Clean up + await authedAdminApi.delete(`hooks/${createdHook.id}`); + await deleteUser(user.id); + }); + + it('should trigger multiple register hooks and record properly when interaction finished', async () => { + const [hook1, hook2, hook3] = await Promise.all([ + authedAdminApi + .post('hooks', { json: getHookCreationPayload(HookEvent.PostRegister) }) + .json(), + authedAdminApi + .post('hooks', { + json: getHookCreationPayload(HookEvent.PostRegister, 'http://localhost:9999'), + }) + .json(), + // Using the old API to create a hook + authedAdminApi + .post('hooks', { + json: { + event: HookEvent.PostRegister, + config: { url: 'http://localhost:9999', retries: 2 }, + }, + }) + .json(), + ]); + const logKey: LogKey = 'TriggerHook.PostRegister'; + + const { username, password } = generateNewUserProfile({ username: true, password: true }); + const userId = await registerNewUser(username, password); + + // Check hook trigger log + const logs = await getLogs(new URLSearchParams({ logKey, page_size: '100' })); + expect( + logs.some( + ({ payload: { hookId, result, error } }) => + hookId === hook1.id && result === LogResult.Error && error === 'RequestError: Invalid URL' + ) + ).toBeTruthy(); + expect( + logs.some( + ({ payload: { hookId, result } }) => hookId === hook2.id && result === LogResult.Success + ) + ).toBeTruthy(); + expect( + logs.some( + ({ payload: { hookId, result } }) => hookId === hook3.id && result === LogResult.Success + ) + ).toBeTruthy(); + + // Clean up + await Promise.all([ + authedAdminApi.delete(`hooks/${hook1.id}`), + authedAdminApi.delete(`hooks/${hook2.id}`), + authedAdminApi.delete(`hooks/${hook3.id}`), + ]); + await deleteUser(userId); + }); + + it('should secure webhook payload data successfully', async () => { + const createdHook = await authedAdminApi + .post('hooks', { + json: getHookCreationPayload(HookEvent.PostRegister, 'http://localhost:9999'), + }) + .json(); + + const { username, password } = generateNewUserProfile({ username: true, password: true }); + const userId = await registerNewUser(username, password); + + const logs = await authedAdminApi + .get(`hooks/${createdHook.id}/recent-logs?page_size=100`) + .json(); + + const log = logs.find(({ payload: { hookId } }) => hookId === createdHook.id); + expect(log).toBeTruthy(); + + const response = log?.payload.response; + expect(response).toBeTruthy(); + + const { + body: { signature, payload }, + } = response as { body: HookSecureData }; + + expect(signature).toBeTruthy(); + expect(payload).toBeTruthy(); + + const calculateSignature = createHmac('sha256', createdHook.signingKey) + .update(payload) + .digest('hex'); + + expect(calculateSignature).toEqual(signature); + + await authedAdminApi.delete(`hooks/${createdHook.id}`); + + await deleteUser(userId); + }); +}); diff --git a/packages/integration-tests/src/tests/api/hooks.test.ts b/packages/integration-tests/src/tests/api/hooks.test.ts deleted file mode 100644 index c1704ac0e..000000000 --- a/packages/integration-tests/src/tests/api/hooks.test.ts +++ /dev/null @@ -1,381 +0,0 @@ -import { createHmac } from 'node:crypto'; -import { type RequestListener } from 'node:http'; - -import type { Hook, HookConfig, HookResponse, Log, LogKey } from '@logto/schemas'; -import { HookEvent, SignInIdentifier, LogResult, InteractionEvent } from '@logto/schemas'; - -import { authedAdminApi, deleteUser, getLogs, putInteraction } from '#src/api/index.js'; -import { createResponseWithCode } from '#src/helpers/admin-tenant.js'; -import { initClient, processSession } from '#src/helpers/client.js'; -import { createMockServer } from '#src/helpers/index.js'; -import { enableAllPasswordSignInMethods } from '#src/helpers/sign-in-experience.js'; -import { generateNewUser, generateNewUserProfile } from '#src/helpers/user.js'; -import { waitFor } from '#src/utils.js'; - -type CreateHookPayload = Pick & { - config: HookConfig; -}; - -const createPayload = (event: HookEvent, url = 'not_work_url'): CreateHookPayload => ({ - name: 'hook_name', - events: [event], - config: { - url, - headers: { foo: 'bar' }, - }, -}); - -type HookSecureData = { - signature: string; - payload: string; -}; - -// Note: return hook payload and signature for webhook security testing -const hookServerRequestListener: RequestListener = (request, response) => { - // eslint-disable-next-line @silverhand/fp/no-mutation - response.statusCode = 204; - - const data: Buffer[] = []; - request.on('data', (chunk: Buffer) => { - // eslint-disable-next-line @silverhand/fp/no-mutating-methods - data.push(chunk); - }); - - request.on('end', () => { - response.writeHead(200, { 'Content-Type': 'application/json' }); - const payload = Buffer.concat(data).toString(); - response.end( - JSON.stringify({ - signature: request.headers['logto-signature-sha-256'] as string, - payload, - } satisfies HookSecureData) - ); - }); -}; -describe('hooks', () => { - const { listen, close } = createMockServer(9999, hookServerRequestListener); - - beforeAll(async () => { - await enableAllPasswordSignInMethods({ - identifiers: [SignInIdentifier.Username], - password: true, - verify: false, - }); - await listen(); - }); - - afterAll(async () => { - await close(); - }); - - it('should be able to create, query, update, and delete a hook', async () => { - const payload = createPayload(HookEvent.PostRegister); - const created = await authedAdminApi.post('hooks', { json: payload }).json(); - - expect(created).toMatchObject(payload); - - expect(await authedAdminApi.get('hooks').json()).toContainEqual(created); - expect(await authedAdminApi.get(`hooks/${created.id}`).json()).toEqual(created); - expect( - await authedAdminApi - .patch(`hooks/${created.id}`, { json: { events: [HookEvent.PostSignIn] } }) - .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 - ); - }); - - it('should be able to create, query, update, and delete a hook by the original API', async () => { - const payload = { - event: HookEvent.PostRegister, - config: { - url: 'not_work_url', - retries: 2, - }, - }; - const created = await authedAdminApi.post('hooks', { json: payload }).json(); - - expect(created).toMatchObject(payload); - - expect(await authedAdminApi.get('hooks').json()).toContainEqual(created); - expect(await authedAdminApi.get(`hooks/${created.id}`).json()).toEqual(created); - expect( - await authedAdminApi - .patch(`hooks/${created.id}`, { json: { event: HookEvent.PostSignIn } }) - .json() - ).toMatchObject({ - ...created, - 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 - ); - }); - - it('should throw error when creating a hook with an empty hook name', async () => { - const payload = { - name: '', - events: [HookEvent.PostRegister], - config: { - url: 'not_work_url', - }, - }; - await expect(authedAdminApi.post('hooks', { json: payload })).rejects.toMatchObject( - createResponseWithCode(400) - ); - }); - - it('should throw error when no event is provided when creating a hook', async () => { - const payload = { - name: 'hook_name', - config: { - url: 'not_work_url', - }, - }; - await expect(authedAdminApi.post('hooks', { json: payload })).rejects.toMatchObject( - createResponseWithCode(400) - ); - }); - - it('should throw error if update a hook with a invalid hook id', async () => { - const payload = { - name: 'new_hook_name', - }; - - await expect(authedAdminApi.patch('hooks/invalid_id', { json: payload })).rejects.toMatchObject( - createResponseWithCode(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) - ); - }); - - it('should trigger sign-in hook and record error when interaction finished', async () => { - const createdHook = await authedAdminApi - .post('hooks', { json: createPayload(HookEvent.PostSignIn) }) - .json(); - const logKey: LogKey = 'TriggerHook.PostSignIn'; - - // Init session and submit - const { - userProfile: { username, password }, - user, - } = await generateNewUser({ username: true, password: true }); - const client = await initClient(); - await client.successSend(putInteraction, { - event: InteractionEvent.SignIn, - identifier: { - username, - password, - }, - }); - await client.submitInteraction(); - await waitFor(500); // Wait for hooks execution - - // Check hook trigger log - const logs = await getLogs(new URLSearchParams({ logKey, page_size: '100' })); - expect( - logs.some( - ({ payload: { hookId, result, error } }) => - hookId === createdHook.id && - result === LogResult.Error && - error === 'RequestError: Invalid URL' - ) - ).toBeTruthy(); - - // Clean up - await authedAdminApi.delete(`hooks/${createdHook.id}`); - await deleteUser(user.id); - }); - - it('should trigger multiple register hooks and record properly when interaction finished', async () => { - const [hook1, hook2, hook3] = await Promise.all([ - authedAdminApi.post('hooks', { json: createPayload(HookEvent.PostRegister) }).json(), - authedAdminApi - .post('hooks', { json: createPayload(HookEvent.PostRegister, 'http://localhost:9999') }) - .json(), - // Using the old API to create a hook - authedAdminApi - .post('hooks', { - json: { - event: HookEvent.PostRegister, - config: { url: 'http://localhost:9999', retries: 2 }, - }, - }) - .json(), - ]); - const logKey: LogKey = 'TriggerHook.PostRegister'; - - // Init session and submit - const { username, password } = generateNewUserProfile({ username: true, password: true }); - const client = await initClient(); - await client.send(putInteraction, { - event: InteractionEvent.Register, - profile: { - username, - password, - }, - }); - const { redirectTo } = await client.submitInteraction(); - const id = await processSession(client, redirectTo); - await waitFor(500); // Wait for hooks execution - - // Check hook trigger log - const logs = await getLogs(new URLSearchParams({ logKey, page_size: '100' })); - expect( - logs.some( - ({ payload: { hookId, result, error } }) => - hookId === hook1.id && result === LogResult.Error && error === 'RequestError: Invalid URL' - ) - ).toBeTruthy(); - expect( - logs.some( - ({ payload: { hookId, result } }) => hookId === hook2.id && result === LogResult.Success - ) - ).toBeTruthy(); - expect( - logs.some( - ({ payload: { hookId, result } }) => hookId === hook3.id && result === LogResult.Success - ) - ).toBeTruthy(); - - // Clean up - await Promise.all([ - authedAdminApi.delete(`hooks/${hook1.id}`), - authedAdminApi.delete(`hooks/${hook2.id}`), - authedAdminApi.delete(`hooks/${hook3.id}`), - ]); - await deleteUser(id); - }); - - it('should get recent hook logs correctly', async () => { - const createdHook = await authedAdminApi - .post('hooks', { json: createPayload(HookEvent.PostRegister, 'http://localhost:9999') }) - .json(); - - // Init session and submit - const { username, password } = generateNewUserProfile({ username: true, password: true }); - const client = await initClient(); - await client.send(putInteraction, { - event: InteractionEvent.Register, - profile: { - username, - password, - }, - }); - const { redirectTo } = await client.submitInteraction(); - const id = await processSession(client, redirectTo); - await waitFor(500); // Wait for hooks execution - - const logs = await authedAdminApi - .get(`hooks/${createdHook.id}/recent-logs?page_size=100`) - .json(); - expect( - logs.some( - ({ payload: { hookId, result } }) => - hookId === createdHook.id && result === LogResult.Success - ) - ).toBeTruthy(); - - await authedAdminApi.delete(`hooks/${createdHook.id}`); - - await deleteUser(id); - }); - - it('should secure webhook payload data successfully', async () => { - const createdHook = await authedAdminApi - .post('hooks', { json: createPayload(HookEvent.PostRegister, 'http://localhost:9999') }) - .json(); - - // Init session and submit - const { username, password } = generateNewUserProfile({ username: true, password: true }); - const client = await initClient(); - await client.send(putInteraction, { - event: InteractionEvent.Register, - profile: { - username, - password, - }, - }); - const { redirectTo } = await client.submitInteraction(); - const id = await processSession(client, redirectTo); - await waitFor(500); // Wait for hooks execution - - const logs = await authedAdminApi - .get(`hooks/${createdHook.id}/recent-logs?page_size=100`) - .json(); - - const log = logs.find(({ payload: { hookId } }) => hookId === createdHook.id); - expect(log).toBeTruthy(); - - const response = log?.payload.response; - expect(response).toBeTruthy(); - - const { - body: { signature, payload }, - } = response as { body: HookSecureData }; - - expect(signature).toBeTruthy(); - expect(payload).toBeTruthy(); - - const calculateSignature = createHmac('sha256', createdHook.signingKey) - .update(payload) - .digest('hex'); - - expect(calculateSignature).toEqual(signature); - - await authedAdminApi.delete(`hooks/${createdHook.id}`); - - await deleteUser(id); - }); - - it('should get hook execution stats correctly', async () => { - const createdHook = await authedAdminApi - .post('hooks', { json: createPayload(HookEvent.PostRegister, 'http://localhost:9999') }) - .json(); - - const hooksWithExecutionStats = await authedAdminApi - .get('hooks?includeExecutionStats=true') - .json(); - - for (const hook of hooksWithExecutionStats) { - expect(hook.executionStats).toBeTruthy(); - } - - // Init session and submit - const { username, password } = generateNewUserProfile({ username: true, password: true }); - const client = await initClient(); - await client.send(putInteraction, { - event: InteractionEvent.Register, - profile: { - username, - password, - }, - }); - const { redirectTo } = await client.submitInteraction(); - const id = await processSession(client, redirectTo); - await waitFor(500); // Wait for hooks execution - - const hookWithExecutionStats = await authedAdminApi - .get(`hooks/${createdHook.id}?includeExecutionStats=true`) - .json(); - - const { executionStats } = hookWithExecutionStats; - - expect(executionStats).toBeTruthy(); - expect(executionStats.requestCount).toBe(1); - expect(executionStats.successCount).toBe(1); - - await authedAdminApi.delete(`hooks/${createdHook.id}`); - - await deleteUser(id); - }); -});