From b57bcb1a05875d1dd66d015493b807e06d463721 Mon Sep 17 00:00:00 2001 From: Alan Date: Thu, 14 Sep 2023 16:10:56 -0300 Subject: [PATCH 1/2] Add Robotina's reaction on reply --- src/events/message/messageNew.ts | 130 ++++++++++++++++--------------- 1 file changed, 68 insertions(+), 62 deletions(-) diff --git a/src/events/message/messageNew.ts b/src/events/message/messageNew.ts index 4ff7893..30e2661 100644 --- a/src/events/message/messageNew.ts +++ b/src/events/message/messageNew.ts @@ -1,62 +1,68 @@ -import { Middleware, SlackEventMiddlewareArgs } from '@slack/bolt'; -import { StringIndexed } from '@slack/bolt/dist/types/helpers'; -import { isTaskSubmission } from '../../utils/validateTaskSubmission'; -import replyApi from '../../api/marketplace/reply/replyApi'; -import { ICreateReplyDto } from '../../api/marketplace/reply/ICreateReplyDto'; -import env from '../../config/env.config'; - -export const handleSubmissionReplyNew: Middleware< - SlackEventMiddlewareArgs<'message'>, - StringIndexed -> = async ({ client, message, logger }) => { - if ( - message.subtype === undefined && - message.thread_ts && - message.parent_user_id === env.BOT_ID - ) { - try { - const { messages: messagesFromChannel } = - await client.conversations.history({ - latest: message.thread_ts, - channel: message.channel, - limit: 1, - inclusive: true, - }); - - const isSubmissionThread = isTaskSubmission( - messagesFromChannel![0].text! - ); - - if (!isSubmissionThread) { - return; - } - - const { user: slackUser } = await client.users.info({ - user: message.user, - }); - - const createReplyDto: ICreateReplyDto = { - authorId: slackUser!.id!, - text: message.text!, - threadTS: message.thread_ts!, - timestamp: message.ts, - username: - (slackUser!.profile!.display_name as string) || - (slackUser!.profile!.real_name as string), - }; - - const reply = await replyApi.create(createReplyDto); - - logger.info( - `Reply with ID "${reply.id}" from user "${ - slackUser!.id - }" saved successfully.` - ); - } catch (error) { - logger.error( - `Something when wrong went handling a submission reply. Displaying relevant data: Message ID "${message.ts}", Message author ID "${message.user}", "Thread ID ${message.thread_ts}", Thread author ID "${message.parent_user_id}".` - ); - logger.error(error); - } - } -}; +import { Middleware, SlackEventMiddlewareArgs } from '@slack/bolt'; +import { StringIndexed } from '@slack/bolt/dist/types/helpers'; +import { isTaskSubmission } from '../../utils/validateTaskSubmission'; +import replyApi from '../../api/marketplace/reply/replyApi'; +import { ICreateReplyDto } from '../../api/marketplace/reply/ICreateReplyDto'; +import env from '../../config/env.config'; + +export const handleSubmissionReplyNew: Middleware< + SlackEventMiddlewareArgs<'message'>, + StringIndexed +> = async ({ client, message, logger }) => { + if ( + message.subtype === undefined && + message.thread_ts && + message.parent_user_id === env.BOT_ID + ) { + try { + const { messages: messagesFromChannel } = + await client.conversations.history({ + latest: message.thread_ts, + channel: message.channel, + limit: 1, + inclusive: true, + }); + + const isSubmissionThread = isTaskSubmission( + messagesFromChannel![0].text! + ); + + if (!isSubmissionThread) { + return; + } + + const { user: slackUser } = await client.users.info({ + user: message.user, + }); + + const createReplyDto: ICreateReplyDto = { + authorId: slackUser!.id!, + text: message.text!, + threadTS: message.thread_ts!, + timestamp: message.ts, + username: + (slackUser!.profile!.display_name as string) || + (slackUser!.profile!.real_name as string), + }; + + const reply = await replyApi.create(createReplyDto); + + await client.reactions.add({ + channel: message.channel, + name: 'white_check_mark', + timestamp: message.ts, + }); + + logger.info( + `Reply with ID "${reply.id}" from user "${ + slackUser!.id + }" saved successfully.` + ); + } catch (error) { + logger.error( + `Something when wrong went handling a submission reply. Displaying relevant data: Message ID "${message.ts}", Message author ID "${message.user}", "Thread ID ${message.thread_ts}", Thread author ID "${message.parent_user_id}".` + ); + logger.error(error); + } + } +}; From 680c0772c202c42c4d9e78b7e086e3107fd36b14 Mon Sep 17 00:00:00 2001 From: Alan Date: Thu, 14 Sep 2023 16:13:48 -0300 Subject: [PATCH 2/2] Add Robotina's reaction in test case --- .../message/__test__/newMessage.spec.ts | 298 +++++++++--------- 1 file changed, 155 insertions(+), 143 deletions(-) diff --git a/src/events/message/__test__/newMessage.spec.ts b/src/events/message/__test__/newMessage.spec.ts index 5506960..5541b91 100644 --- a/src/events/message/__test__/newMessage.spec.ts +++ b/src/events/message/__test__/newMessage.spec.ts @@ -1,143 +1,155 @@ -import { expect, jest } from '@jest/globals'; -import { Logger } from '@slack/bolt'; -import { WebClient } from '@slack/web-api'; -import { handleSubmissionReplyNew } from '../messageNew'; -import { validConversationHistoryResponse } from './fixture/validConversationHistoryResponse'; -import { userInfoResponse } from './fixture/userInfoResponse'; -import replyApi from '../../../api/marketplace/reply/replyApi'; -import { invalidConversationHistoryResponse } from './fixture/invalidConversationHistoryResponse'; -import { messageNewEvent } from './fixture/messageNewEvent'; - -jest.mock('../../../api/marketplace/reply/replyApi'); - -jest.mock('@slack/bolt', () => ({ - App: jest.fn(() => ({ event: jest.fn() })), -})); - -jest.mock('@slack/web-api', () => ({ - WebClient: jest.fn(() => ({ - conversations: { - history: jest.fn(), - }, - users: { - info: jest.fn(), - }, - })), -})); - -const webClientMock = new WebClient(); -const loggerMock = { - info: jest.fn(), - error: jest.fn(), -} as unknown as Logger; - -describe('handleSubmissionReply', () => { - beforeEach(() => { - jest.clearAllMocks(); - }); - - it('should save a new message as a submission reply if the thread author is robotina and the thread is from a submission', async () => { - const conversationHistoryMock = jest - .spyOn(webClientMock.conversations, 'history') - // @ts-ignore - .mockResolvedValueOnce(validConversationHistoryResponse); - - const userInfoMock = jest - .spyOn(webClientMock.users, 'info') - .mockResolvedValueOnce(userInfoResponse); - - const replyCreateMock = jest - .spyOn(replyApi, 'create') - .mockResolvedValueOnce({ - id: 1, - authorId: '', - text: '', - threadTS: '', - timestamp: '', - username: '', - }); - - await handleSubmissionReplyNew({ - client: webClientMock, - // @ts-ignore - message: messageNewEvent, - logger: loggerMock, - }); - - expect(conversationHistoryMock).toHaveBeenCalledTimes(1); - expect(conversationHistoryMock).toHaveBeenCalledWith({ - latest: messageNewEvent.thread_ts, - channel: messageNewEvent.channel, - limit: 1, - inclusive: true, - }); - - expect(userInfoMock).toHaveBeenCalledTimes(1); - expect(userInfoMock).toHaveBeenCalledWith({ - user: messageNewEvent.user, - }); - expect(replyCreateMock).toHaveBeenCalledTimes(1); - expect(replyCreateMock).toHaveBeenCalledWith({ - authorId: userInfoResponse.user.id, - text: messageNewEvent.text!, - threadTS: messageNewEvent.thread_ts!, - timestamp: messageNewEvent.ts, - username: expect.any(String), - }); - expect(loggerMock.info).toHaveBeenCalledTimes(1); - expect(loggerMock.error).toHaveBeenCalledTimes(0); - }); - - it('should not save a new message as a submission reply if the thread is not from a submission', async () => { - const conversationHistoryMock = jest - .spyOn(webClientMock.conversations, 'history') - // @ts-ignore - .mockResolvedValueOnce(invalidConversationHistoryResponse); - - const userInfoMock = jest.spyOn(webClientMock.users, 'info'); - const replyCreateMock = jest.spyOn(replyApi, 'create'); - - await handleSubmissionReplyNew({ - client: webClientMock, - // @ts-ignore - message: messageNewEvent, - logger: loggerMock, - }); - - expect(conversationHistoryMock).toHaveBeenCalledTimes(1); - expect(conversationHistoryMock).toHaveBeenCalledWith({ - latest: messageNewEvent.thread_ts, - channel: messageNewEvent.channel, - limit: 1, - inclusive: true, - }); - - expect(userInfoMock).toHaveBeenCalledTimes(0); - expect(replyCreateMock).toHaveBeenCalledTimes(0); - expect(loggerMock.info).toHaveBeenCalledTimes(0); - expect(loggerMock.error).toHaveBeenCalledTimes(0); - }); - - it('should log errors if they happen', async () => { - const conversationHistoryMock = jest - .spyOn(webClientMock.conversations, 'history') - // @ts-ignore - .mockRejectedValueOnce(new Error('Network Error')); - - const userInfoMock = jest.spyOn(webClientMock.users, 'info'); - const replyCreateMock = jest.spyOn(replyApi, 'create'); - - await handleSubmissionReplyNew({ - client: webClientMock, - // @ts-ignore - message: messageNewEvent, - logger: loggerMock, - }); - - expect(conversationHistoryMock).toHaveBeenCalledTimes(1); - expect(userInfoMock).toHaveBeenCalledTimes(0); - expect(replyCreateMock).toHaveBeenCalledTimes(0); - expect(loggerMock.info).toHaveBeenCalledTimes(0); - expect(loggerMock.error).toHaveBeenCalledTimes(2); - }); -}); +import { expect, jest } from '@jest/globals'; +import { Logger } from '@slack/bolt'; +import { WebClient } from '@slack/web-api'; +import { handleSubmissionReplyNew } from '../messageNew'; +import { validConversationHistoryResponse } from './fixture/validConversationHistoryResponse'; +import { userInfoResponse } from './fixture/userInfoResponse'; +import replyApi from '../../../api/marketplace/reply/replyApi'; +import { invalidConversationHistoryResponse } from './fixture/invalidConversationHistoryResponse'; +import { messageNewEvent } from './fixture/messageNewEvent'; + +jest.mock('../../../api/marketplace/reply/replyApi'); + +jest.mock('@slack/bolt', () => ({ + App: jest.fn(() => ({ event: jest.fn() })), +})); + +jest.mock('@slack/web-api', () => ({ + WebClient: jest.fn(() => ({ + conversations: { + history: jest.fn(), + }, + users: { + info: jest.fn(), + }, + reactions: { + add: jest.fn(), + }, + })), +})); + +const webClientMock = new WebClient(); +const loggerMock = { + info: jest.fn(), + error: jest.fn(), +} as unknown as Logger; + +describe('handleSubmissionReply', () => { + beforeEach(() => { + jest.clearAllMocks(); + }); + + it('should save a new message as a submission reply if the thread author is robotina and the thread is from a submission', async () => { + const conversationHistoryMock = jest + .spyOn(webClientMock.conversations, 'history') + // @ts-ignore + .mockResolvedValueOnce(validConversationHistoryResponse); + + const userInfoMock = jest + .spyOn(webClientMock.users, 'info') + .mockResolvedValueOnce(userInfoResponse); + + const replyCreateMock = jest + .spyOn(replyApi, 'create') + .mockResolvedValueOnce({ + id: 1, + authorId: '', + text: '', + threadTS: '', + timestamp: '', + username: '', + }); + + await handleSubmissionReplyNew({ + client: webClientMock, + // @ts-ignore + message: messageNewEvent, + logger: loggerMock, + }); + + expect(conversationHistoryMock).toHaveBeenCalledTimes(1); + expect(conversationHistoryMock).toHaveBeenCalledWith({ + latest: messageNewEvent.thread_ts, + channel: messageNewEvent.channel, + limit: 1, + inclusive: true, + }); + + expect(userInfoMock).toHaveBeenCalledTimes(1); + expect(userInfoMock).toHaveBeenCalledWith({ + user: messageNewEvent.user, + }); + + expect(replyCreateMock).toHaveBeenCalledTimes(1); + expect(replyCreateMock).toHaveBeenCalledWith({ + authorId: userInfoResponse.user.id, + text: messageNewEvent.text!, + threadTS: messageNewEvent.thread_ts!, + timestamp: messageNewEvent.ts, + username: expect.any(String), + }); + + expect(webClientMock.reactions.add).toBeCalledTimes(1); + expect(webClientMock.reactions.add).toBeCalledWith({ + channel: messageNewEvent.channel, + name: 'white_check_mark', + timestamp: messageNewEvent.ts, + }); + + expect(loggerMock.info).toHaveBeenCalledTimes(1); + expect(loggerMock.error).toHaveBeenCalledTimes(0); + }); + + it('should not save a new message as a submission reply if the thread is not from a submission', async () => { + const conversationHistoryMock = jest + .spyOn(webClientMock.conversations, 'history') + // @ts-ignore + .mockResolvedValueOnce(invalidConversationHistoryResponse); + + const userInfoMock = jest.spyOn(webClientMock.users, 'info'); + const replyCreateMock = jest.spyOn(replyApi, 'create'); + + await handleSubmissionReplyNew({ + client: webClientMock, + // @ts-ignore + message: messageNewEvent, + logger: loggerMock, + }); + + expect(conversationHistoryMock).toHaveBeenCalledTimes(1); + expect(conversationHistoryMock).toHaveBeenCalledWith({ + latest: messageNewEvent.thread_ts, + channel: messageNewEvent.channel, + limit: 1, + inclusive: true, + }); + + expect(userInfoMock).toHaveBeenCalledTimes(0); + expect(replyCreateMock).toHaveBeenCalledTimes(0); + expect(loggerMock.info).toHaveBeenCalledTimes(0); + expect(loggerMock.error).toHaveBeenCalledTimes(0); + }); + + it('should log errors if they happen', async () => { + const conversationHistoryMock = jest + .spyOn(webClientMock.conversations, 'history') + // @ts-ignore + .mockRejectedValueOnce(new Error('Network Error')); + + const userInfoMock = jest.spyOn(webClientMock.users, 'info'); + const replyCreateMock = jest.spyOn(replyApi, 'create'); + + await handleSubmissionReplyNew({ + client: webClientMock, + // @ts-ignore + message: messageNewEvent, + logger: loggerMock, + }); + + expect(conversationHistoryMock).toHaveBeenCalledTimes(1); + expect(userInfoMock).toHaveBeenCalledTimes(0); + expect(replyCreateMock).toHaveBeenCalledTimes(0); + expect(loggerMock.info).toHaveBeenCalledTimes(0); + expect(loggerMock.error).toHaveBeenCalledTimes(2); + }); +});