1
|
|
|
// @ts-ignore: mock |
2
|
|
|
import PollCard, {mockCreateCardWithId} from '../src/cards/PollCard'; |
3
|
|
|
// @ts-ignore: mock |
4
|
|
|
import ClosePollFormCard, {mockCreateClosePollFormCard} from '../src/cards/ClosePollFormCard'; |
5
|
|
|
// @ts-ignore: mock |
6
|
|
|
import ScheduleClosePollFormCard, {mockScheduleCreateClosePollFormCard} from '../src/cards/ScheduleClosePollFormCard'; |
7
|
|
|
|
8
|
|
|
import ActionHandler from '../src/handlers/ActionHandler'; |
9
|
|
|
// @ts-ignore: dummy test |
10
|
|
|
import dummyAddOptionForm from './json/add_option_form.json'; |
11
|
|
|
import {mockCreate, mockGoogleAuth, mockUpdate} from './mocks'; |
12
|
|
|
import {createDialogActionResponse, createStatusActionResponse} from '../src/helpers/response'; |
13
|
|
|
import NewPollFormCard from '../src/cards/NewPollFormCard'; |
14
|
|
|
import {chat_v1 as chatV1} from '@googleapis/chat'; |
15
|
|
|
import {ClosableType, PollForm} from '../src/helpers/interfaces'; |
16
|
|
|
import {PROHIBITED_ICON_URL} from '../src/config/default'; |
17
|
|
|
import MessageDialogCard from '../src/cards/MessageDialogCard'; |
18
|
|
|
import {dummyLocalTimezone} from './dummy'; |
19
|
|
|
import {DEFAULT_LOCALE_TIMEZONE} from '../src/helpers/time'; |
20
|
|
|
import PollDialogCard, {mockCreatePollDialogCard} from '../src/cards/PollDialogCard'; |
21
|
|
|
|
22
|
|
|
jest.mock('../src/cards/PollCard'); |
23
|
|
|
jest.mock('../src/cards/PollDialogCard'); |
24
|
|
|
jest.mock('../src/cards/ClosePollFormCard'); |
25
|
|
|
jest.mock('../src/cards/ScheduleClosePollFormCard'); |
26
|
|
|
|
27
|
|
|
jest.mock('@googleapis/chat', () => { |
28
|
|
|
return { |
29
|
|
|
auth: { |
30
|
|
|
GoogleAuth: jest.fn(() => mockGoogleAuth), |
31
|
|
|
}, |
32
|
|
|
chat: jest.fn().mockImplementation(() => { |
33
|
|
|
return { |
34
|
|
|
spaces: { |
35
|
|
|
messages: { |
36
|
|
|
create: mockCreate, |
37
|
|
|
update: mockUpdate, |
38
|
|
|
}, |
39
|
|
|
}, |
40
|
|
|
}; |
41
|
|
|
}), |
42
|
|
|
}; |
43
|
|
|
}); |
44
|
|
|
jest.mock('@google-cloud/tasks', () => { |
45
|
|
|
return { |
46
|
|
|
CloudTasksClient: jest.fn(() => { |
47
|
|
|
return { |
48
|
|
|
createTask: jest.fn().mockResolvedValue([{name: 'testEmail'}]), |
49
|
|
|
queuePath: jest.fn().mockResolvedValue({ |
50
|
|
|
email: 'testEmail', |
51
|
|
|
}), |
52
|
|
|
}; |
53
|
|
|
}), |
54
|
|
|
}; |
55
|
|
|
}); |
56
|
|
|
|
57
|
|
|
it('should add a new option to the poll state and return an "OK" status message', async () => { |
58
|
|
|
// Mock event object |
59
|
|
|
const event = { |
60
|
|
|
user: { |
61
|
|
|
displayName: 'John Doe', |
62
|
|
|
}, |
63
|
|
|
common: { |
64
|
|
|
formInputs: { |
65
|
|
|
value: { |
66
|
|
|
stringInputs: { |
67
|
|
|
value: ['Option 1'], |
68
|
|
|
}, |
69
|
|
|
}, |
70
|
|
|
}, |
71
|
|
|
}, |
72
|
|
|
message: { |
73
|
|
|
name: 'messageName', |
74
|
|
|
}, |
75
|
|
|
}; |
76
|
|
|
|
77
|
|
|
// Mock getEventPollState function |
78
|
|
|
const getEventPollStateMock = jest.fn().mockReturnValue({choices: [], choiceCreator: {}}); |
79
|
|
|
|
80
|
|
|
// Create instance of ActionHandler |
81
|
|
|
const actionHandler = new ActionHandler(event); |
82
|
|
|
|
83
|
|
|
// Mock getEventPollState method |
84
|
|
|
actionHandler.getEventPollState = getEventPollStateMock; |
85
|
|
|
|
86
|
|
|
// Call saveOption method |
87
|
|
|
const result = await actionHandler.saveOption(); |
88
|
|
|
|
89
|
|
|
expect(getEventPollStateMock).toHaveBeenCalled(); |
90
|
|
|
expect(mockUpdate).toHaveBeenCalledWith({name: 'messageName', requestBody: {cardsV2: []}, updateMask: 'cardsV2'}); |
91
|
|
|
expect(result).toEqual(createStatusActionResponse('Option is added')); |
92
|
|
|
|
93
|
|
|
const actionHandler2 = new ActionHandler(event); |
94
|
|
|
|
95
|
|
|
actionHandler2.getEventPollState = getEventPollStateMock; |
96
|
|
|
mockUpdate.mockResolvedValue({status: 400}); |
97
|
|
|
const result2 = await actionHandler2.saveOption(); |
98
|
|
|
expect(result2).toEqual(createStatusActionResponse('Failed to add option.', 'UNKNOWN')); |
99
|
|
|
}); |
100
|
|
|
|
101
|
|
|
describe('process', () => { |
102
|
|
|
it('should return a message with a poll card when the action is "start_poll"', async () => { |
103
|
|
|
// Mock the startPoll function |
104
|
|
|
const startPollMock = jest.fn().mockReturnValue({}); |
105
|
|
|
|
106
|
|
|
// Create an instance of ActionHandler |
107
|
|
|
const actionHandler = new ActionHandler({common: {invokedFunction: 'start_poll'}}); |
108
|
|
|
|
109
|
|
|
// Mock the startPoll function in the ActionHandler instance |
110
|
|
|
actionHandler.startPoll = startPollMock; |
111
|
|
|
|
112
|
|
|
// Call the process method |
113
|
|
|
await actionHandler.process(); |
114
|
|
|
|
115
|
|
|
// Expect the startPoll to be called |
116
|
|
|
expect(startPollMock).toHaveBeenCalled(); |
117
|
|
|
}); |
118
|
|
|
|
119
|
|
|
it('should return a message with an updated poll card when the action is "vote"', async () => { |
120
|
|
|
// Mock the recordVote function |
121
|
|
|
const recordVoteMock = jest.fn().mockReturnValue({}); |
122
|
|
|
|
123
|
|
|
// Create an instance of ActionHandler |
124
|
|
|
const actionHandler = new ActionHandler({common: {invokedFunction: 'vote'}}); |
125
|
|
|
|
126
|
|
|
// Mock the recordVote function in the ActionHandler instance |
127
|
|
|
actionHandler.recordVote = recordVoteMock; |
128
|
|
|
|
129
|
|
|
// Call the process method |
130
|
|
|
await actionHandler.process(); |
131
|
|
|
|
132
|
|
|
// Expect the recordVote function to be called |
133
|
|
|
expect(recordVoteMock).toHaveBeenCalled(); |
134
|
|
|
}); |
135
|
|
|
|
136
|
|
|
it('should return a dialog with an add option form when the action is "add_option_form"', async () => { |
137
|
|
|
// Mock the addOptionForm function |
138
|
|
|
const addOptionFormMock = jest.fn().mockReturnValue({}); |
139
|
|
|
|
140
|
|
|
// Create an instance of ActionHandler |
141
|
|
|
const actionHandler = new ActionHandler({common: {invokedFunction: 'add_option_form'}}); |
142
|
|
|
|
143
|
|
|
// Mock the addOptionForm function in the ActionHandler instance |
144
|
|
|
actionHandler.addOptionForm = addOptionFormMock; |
145
|
|
|
|
146
|
|
|
// Call the process method |
147
|
|
|
await actionHandler.process(); |
148
|
|
|
|
149
|
|
|
// Expect the addOptionForm function to be called |
150
|
|
|
expect(addOptionFormMock).toHaveBeenCalled(); |
151
|
|
|
}); |
152
|
|
|
it('should return a dialog with an add option form when the action is "add_option_form"', async () => { |
153
|
|
|
// Mock the addOptionForm function |
154
|
|
|
const addOptionFormMock = jest.fn().mockReturnValue({}); |
155
|
|
|
|
156
|
|
|
// Create an instance of ActionHandler |
157
|
|
|
const actionHandler = new ActionHandler({common: {invokedFunction: 'add_option_form'}}); |
158
|
|
|
|
159
|
|
|
// Mock the addOptionForm function in the ActionHandler instance |
160
|
|
|
actionHandler.addOptionForm = addOptionFormMock; |
161
|
|
|
|
162
|
|
|
// Call the process method |
163
|
|
|
await actionHandler.process(); |
164
|
|
|
|
165
|
|
|
// Expect the addOptionForm function to be called |
166
|
|
|
expect(addOptionFormMock).toHaveBeenCalled(); |
167
|
|
|
}); |
168
|
|
|
|
169
|
|
|
it('should create a dialog with AddOptionFormCard and return it as an actionResponse', () => { |
170
|
|
|
// Arrange |
171
|
|
|
const card = { |
172
|
|
|
sections: [ |
173
|
|
|
{ |
174
|
|
|
widgets: [ |
175
|
|
|
{ |
176
|
|
|
decoratedText: { |
177
|
|
|
button: { |
178
|
|
|
onClick: { |
179
|
|
|
action: { |
180
|
|
|
parameters: [ |
181
|
|
|
{ |
182
|
|
|
value: '{"topic":"Who is the most handsome AI?", "choices": []}', |
183
|
|
|
}, |
184
|
|
|
], |
185
|
|
|
}, |
186
|
|
|
}, |
187
|
|
|
}, |
188
|
|
|
}, |
189
|
|
|
}, |
190
|
|
|
], |
191
|
|
|
}, |
192
|
|
|
], |
193
|
|
|
}; |
194
|
|
|
const cardWithId: chatV1.Schema$CardWithId = { |
195
|
|
|
cardId: 'cardId', |
196
|
|
|
card, |
197
|
|
|
}; |
198
|
|
|
const event = { |
199
|
|
|
message: { |
200
|
|
|
cardsV2: [cardWithId], |
201
|
|
|
}, |
202
|
|
|
}; |
203
|
|
|
const expectedDialog = { |
204
|
|
|
body: dummyAddOptionForm, |
205
|
|
|
}; |
206
|
|
|
const actionHandler = new ActionHandler(event); |
207
|
|
|
actionHandler.getEventPollState = jest.fn(). |
208
|
|
|
mockReturnValue({'topic': 'Who is the most handsome AI?', 'choices': []}); |
209
|
|
|
// Act |
210
|
|
|
const result = actionHandler.addOptionForm(); |
211
|
|
|
|
212
|
|
|
// Assert |
213
|
|
|
expect(result).toEqual({ |
214
|
|
|
actionResponse: { |
215
|
|
|
type: 'DIALOG', |
216
|
|
|
dialogAction: { |
217
|
|
|
dialog: expectedDialog, |
218
|
|
|
}, |
219
|
|
|
}, |
220
|
|
|
}); |
221
|
|
|
}); |
222
|
|
|
|
223
|
|
|
// Tests that the 'add_option' action returns a message with an updated poll card |
224
|
|
|
it('should return a message with an updated poll card when the action is "add_option"', async () => { |
225
|
|
|
// Mock the saveOption function |
226
|
|
|
const saveOptionMock = jest.fn().mockReturnValue({}); |
227
|
|
|
|
228
|
|
|
// Create an instance of ActionHandler |
229
|
|
|
const actionHandler = new ActionHandler({common: {invokedFunction: 'add_option'}}); |
230
|
|
|
|
231
|
|
|
// Mock the saveOption function in the ActionHandler instance |
232
|
|
|
actionHandler.saveOption = saveOptionMock; |
233
|
|
|
|
234
|
|
|
// Call the process method |
235
|
|
|
await actionHandler.process(); |
236
|
|
|
|
237
|
|
|
// Expect the saveOption function to be called |
238
|
|
|
expect(saveOptionMock).toHaveBeenCalled(); |
239
|
|
|
}); |
240
|
|
|
|
241
|
|
|
it('should return a message with an updated poll card when the action is "close_poll_form"', async () => { |
242
|
|
|
// Mock the closePollForm function |
243
|
|
|
const closePollFormMock = jest.fn().mockReturnValue({}); |
244
|
|
|
|
245
|
|
|
// Create an instance of ActionHandler |
246
|
|
|
const actionHandler = new ActionHandler({common: {invokedFunction: 'close_poll_form'}}); |
247
|
|
|
|
248
|
|
|
// Mock the closePollForm function in the ActionHandler instance |
249
|
|
|
actionHandler.closePollForm = closePollFormMock; |
250
|
|
|
|
251
|
|
|
// Call the process method |
252
|
|
|
await actionHandler.process(); |
253
|
|
|
|
254
|
|
|
// Expect the saveOption function to be called |
255
|
|
|
expect(closePollFormMock).toHaveBeenCalled(); |
256
|
|
|
}); |
257
|
|
|
|
258
|
|
|
it('other actions"', async () => { |
259
|
|
|
const switchVoteMock = jest.fn().mockReturnValue({}); |
260
|
|
|
const voteFormMock = jest.fn().mockReturnValue({}); |
261
|
|
|
|
262
|
|
|
const actionHandler = new ActionHandler({common: {invokedFunction: 'switch_vote'}}); |
263
|
|
|
actionHandler.switchVote = switchVoteMock; |
264
|
|
|
await actionHandler.process(); |
265
|
|
|
|
266
|
|
|
const actionHandler2 = new ActionHandler({common: {invokedFunction: 'vote_form'}}); |
267
|
|
|
actionHandler2.voteForm = voteFormMock; |
268
|
|
|
await actionHandler2.process(); |
269
|
|
|
|
270
|
|
|
|
271
|
|
|
expect(switchVoteMock).toHaveBeenCalled(); |
272
|
|
|
expect(voteFormMock).toHaveBeenCalled(); |
273
|
|
|
}); |
274
|
|
|
|
275
|
|
|
// Tests that the 'unknown' action returns a message with an updated poll card |
276
|
|
|
it('should return a message with an updated poll card when the action is "add_option"', async () => { |
277
|
|
|
// Create an instance of ActionHandler |
278
|
|
|
const actionHandler = new ActionHandler({common: {invokedFunction: 'unknown'}}); |
279
|
|
|
|
280
|
|
|
// Call the process method |
281
|
|
|
const result = await actionHandler.process(); |
282
|
|
|
|
283
|
|
|
// Expect the saveOption function to be called |
284
|
|
|
expect(result).toEqual(createStatusActionResponse('Unknown action!', 'UNKNOWN')); |
285
|
|
|
}); |
286
|
|
|
|
287
|
|
|
it('should rebuild poll form with inputted data when new_poll_on_change invoked', async () => { |
288
|
|
|
const event = { |
289
|
|
|
common: { |
290
|
|
|
invokedFunction: 'new_poll_on_change', |
291
|
|
|
formInputs: { |
292
|
|
|
topic: {stringInputs: {value: ['Yay or Nay']}}, |
293
|
|
|
allow_add_option: {stringInputs: {value: ['0']}}, |
294
|
|
|
type: {stringInputs: {value: ['2']}}, |
295
|
|
|
option0: {stringInputs: {value: ['Yay']}}, |
296
|
|
|
option1: {stringInputs: {value: ['Nae']}}, |
297
|
|
|
}, |
298
|
|
|
}, |
299
|
|
|
user: {displayName: 'User'}, |
300
|
|
|
space: {name: 'Space'}, |
301
|
|
|
}; |
302
|
|
|
const actionHandler = new ActionHandler(event); |
303
|
|
|
const result = await actionHandler.process(); |
304
|
|
|
const expectedConfig: PollForm = {topic: 'Yay or Nay', choices: ['Yay', 'Nae'], optionable: false, type: 2}; |
305
|
|
|
const expectedCard = new NewPollFormCard(expectedConfig, dummyLocalTimezone).create(); |
306
|
|
|
const expectedResponse = createDialogActionResponse(expectedCard); |
307
|
|
|
expect(result).toEqual(expectedResponse); |
308
|
|
|
}); |
309
|
|
|
}); |
310
|
|
|
|
311
|
|
|
describe('startPoll', () => { |
312
|
|
|
// Tests that a valid form is submitted and a poll card is created and displayed in the space |
313
|
|
|
it('should create and display poll card when valid form is submitted', async () => { |
314
|
|
|
const event = { |
315
|
|
|
common: { |
316
|
|
|
invokedFunction: 'start_poll', |
317
|
|
|
formInputs: { |
318
|
|
|
topic: {stringInputs: {value: ['Topic']}}, |
319
|
|
|
allow_add_option: {stringInputs: {value: ['0']}}, |
320
|
|
|
type: {stringInputs: {value: ['0']}}, |
321
|
|
|
option0: {stringInputs: {value: ['Yay']}}, |
322
|
|
|
option1: {stringInputs: {value: ['Nae']}}, |
323
|
|
|
option2: {stringInputs: {value: ['']}}, |
324
|
|
|
option3: {stringInputs: {value: ['']}}, |
325
|
|
|
option4: {stringInputs: {value: ['']}}, |
326
|
|
|
option5: {stringInputs: {value: ['No Way']}}, |
327
|
|
|
is_autoclose: {stringInputs: {value: ['1']}}, |
328
|
|
|
close_schedule_time: {dateTimeInput: {msSinceEpoch: Date.now().toString()}}, |
329
|
|
|
}, |
330
|
|
|
timeZone: {'id': 'America/New_York'}, |
331
|
|
|
}, |
332
|
|
|
user: {displayName: 'User'}, |
333
|
|
|
space: {name: 'Space'}, |
334
|
|
|
}; |
335
|
|
|
|
336
|
|
|
const actionHandler = new ActionHandler(event); |
337
|
|
|
|
338
|
|
|
// will throw error because not all required environment variables are set |
339
|
|
|
await expect(async () => { |
340
|
|
|
await actionHandler.startPoll(); |
341
|
|
|
}).rejects.toThrowError('Missing required environment variables'); |
342
|
|
|
// duplicate test with another way for reference to test the error |
343
|
|
|
await actionHandler.startPoll().catch((error) => { |
344
|
|
|
expect(error).toEqual(new Error('Missing required environment variables')); |
345
|
|
|
}); |
346
|
|
|
|
347
|
|
|
process.env.GCP_PROJECT = 'test-project'; |
348
|
|
|
process.env.QUEUE_NAME = 'test-queue'; |
349
|
|
|
process.env.FUNCTION_REGION = 'us-central1'; |
350
|
|
|
const result = await actionHandler.startPoll(); |
351
|
|
|
|
352
|
|
|
const pollCardMessage = new PollCard({ |
353
|
|
|
topic: 'Topic', |
354
|
|
|
choiceCreator: undefined, |
355
|
|
|
author: event.user, |
356
|
|
|
choices: ['Yae', 'Nae', 'No Way'], |
357
|
|
|
votes: {'0': [], '1': []}, |
358
|
|
|
anon: false, |
359
|
|
|
optionable: false, |
360
|
|
|
}, dummyLocalTimezone).createMessage(); |
361
|
|
|
|
362
|
|
|
const request = { |
363
|
|
|
parent: event.space?.name, |
364
|
|
|
requestBody: pollCardMessage, |
365
|
|
|
}; |
366
|
|
|
|
367
|
|
|
expect(result).toEqual(createStatusActionResponse('Poll started.', 'OK')); |
368
|
|
|
expect(mockCreate).toHaveBeenCalledWith(request); |
369
|
|
|
|
370
|
|
|
// when google API return invalid data, it should return an error message |
371
|
|
|
mockCreate.mockResolvedValue({status: 400, data: {}}); |
372
|
|
|
const actionHandler2 = new ActionHandler(event); |
373
|
|
|
const result2 = await actionHandler2.startPoll(); |
374
|
|
|
expect(result2).toEqual(createStatusActionResponse('Failed to start poll.', 'UNKNOWN')); |
375
|
|
|
}); |
376
|
|
|
|
377
|
|
|
// Tests that an incomplete form is submitted and the form is rerendered |
378
|
|
|
it('should rerender form when incomplete form is submitted', async () => { |
379
|
|
|
// Mock event object |
380
|
|
|
const event = { |
381
|
|
|
common: { |
382
|
|
|
formInputs: { |
383
|
|
|
topic: {stringInputs: {value: ['']}}, |
384
|
|
|
is_anonymous: {stringInputs: {value: ['1']}}, |
385
|
|
|
allow_add_option: {stringInputs: {value: ['1']}}, |
386
|
|
|
type: {stringInputs: {value: ['1']}}, |
387
|
|
|
option0: {stringInputs: {value: ['Option 1']}}, |
388
|
|
|
}, |
389
|
|
|
}, |
390
|
|
|
}; |
391
|
|
|
|
392
|
|
|
const actionHandler = new ActionHandler(event); |
393
|
|
|
|
394
|
|
|
const result = await actionHandler.startPoll(); |
395
|
|
|
|
396
|
|
|
expect(result).toEqual({ |
397
|
|
|
actionResponse: { |
398
|
|
|
type: 'DIALOG', |
399
|
|
|
dialogAction: { |
400
|
|
|
dialog: { |
401
|
|
|
body: new NewPollFormCard({ |
402
|
|
|
topic: '', |
403
|
|
|
choices: ['Option 1'], |
404
|
|
|
anon: true, |
405
|
|
|
type: 1, |
406
|
|
|
}, dummyLocalTimezone).create(), |
407
|
|
|
}, |
408
|
|
|
}, |
409
|
|
|
}, |
410
|
|
|
}); |
411
|
|
|
}); |
412
|
|
|
|
413
|
|
|
// we should validate the input from the form |
414
|
|
|
it('should rerender form when the closed time less than now', async () => { |
415
|
|
|
// Mock event object |
416
|
|
|
const event = { |
417
|
|
|
common: { |
418
|
|
|
invokedFunction: 'start_poll', |
419
|
|
|
formInputs: { |
420
|
|
|
topic: {stringInputs: {value: ['Yay or Nae']}}, |
421
|
|
|
allow_add_option: {stringInputs: {value: ['0']}}, |
422
|
|
|
type: {stringInputs: {value: ['0']}}, |
423
|
|
|
option0: {stringInputs: {value: ['Yay']}}, |
424
|
|
|
option1: {stringInputs: {value: ['Nae']}}, |
425
|
|
|
option2: {stringInputs: {value: ['']}}, |
426
|
|
|
is_autoclose: {stringInputs: {value: ['1']}}, |
427
|
|
|
close_schedule_time: {dateTimeInput: {msSinceEpoch: (Date.now() - 3600000).toString()}}, |
428
|
|
|
}, |
429
|
|
|
timeZone: {'id': 'Asia/Jakarta'}, |
430
|
|
|
}, |
431
|
|
|
user: {displayName: 'User'}, |
432
|
|
|
space: {name: 'Space'}, |
433
|
|
|
}; |
434
|
|
|
|
435
|
|
|
const actionHandler = new ActionHandler(event); |
436
|
|
|
|
437
|
|
|
const result = await actionHandler.startPoll(); |
438
|
|
|
expect(result.actionResponse.dialogAction.dialog.body).toBeDefined(); |
439
|
|
|
}); |
440
|
|
|
}); |
441
|
|
|
describe('recordVote', () => { |
442
|
|
|
it('should throw an error if the index parameter is missing', () => { |
443
|
|
|
const event = { |
444
|
|
|
common: { |
445
|
|
|
parameters: {}, |
446
|
|
|
}, |
447
|
|
|
}; |
448
|
|
|
const actionHandler = new ActionHandler(event); |
449
|
|
|
|
450
|
|
|
expect(() => actionHandler.recordVote()).toThrow('Index Out of Bounds'); |
451
|
|
|
expect(() => actionHandler.getEventPollState()).toThrow('no valid card in the event'); |
452
|
|
|
const event2 = { |
453
|
|
|
common: { |
454
|
|
|
parameters: {}, |
455
|
|
|
}, |
456
|
|
|
message: { |
457
|
|
|
thread: { |
458
|
|
|
'name': 'spaces/AAAAN0lf83o/threads/DJXfo5DXcTA', |
459
|
|
|
}, |
460
|
|
|
cardsV2: [{cardId: 'card', card: {}}], |
461
|
|
|
}, |
462
|
|
|
}; |
463
|
|
|
const actionHandler2 = new ActionHandler(event2); |
464
|
|
|
expect(() => actionHandler2.getEventPollState()).toThrow('no valid state in the event'); |
465
|
|
|
}); |
466
|
|
|
it('should update an existing vote with a new vote', () => { |
467
|
|
|
const event = { |
468
|
|
|
common: { |
469
|
|
|
parameters: { |
470
|
|
|
index: '1', |
471
|
|
|
state: '{"votes": {"0": [{"uid": "userId", "name": "userName"}]}, "anon": false}', |
472
|
|
|
}, |
473
|
|
|
}, |
474
|
|
|
user: { |
475
|
|
|
name: 'userId2', |
476
|
|
|
displayName: 'userName2', |
477
|
|
|
}, |
478
|
|
|
message: { |
479
|
|
|
thread: { |
480
|
|
|
'name': 'spaces/AAAAN0lf83o/threads/DJXfo5DXcTA', |
481
|
|
|
}, |
482
|
|
|
cardsV2: [{cardId: 'card', card: {}}], |
483
|
|
|
}, |
484
|
|
|
}; |
485
|
|
|
const actionHandler = new ActionHandler(event); |
486
|
|
|
const response = actionHandler.recordVote(); |
487
|
|
|
|
488
|
|
|
const expectedResponse = { |
489
|
|
|
thread: { |
490
|
|
|
'name': 'spaces/AAAAN0lf83o/threads/DJXfo5DXcTA', |
491
|
|
|
}, |
492
|
|
|
actionResponse: { |
493
|
|
|
type: 'UPDATE_MESSAGE', |
494
|
|
|
}, |
495
|
|
|
cardsV2: ['card'], |
496
|
|
|
}; |
497
|
|
|
const expectedPollState = { |
498
|
|
|
votes: { |
499
|
|
|
'0': [{uid: 'userId', name: 'userName'}], |
500
|
|
|
'1': [{uid: 'userId2', name: 'userName2'}], |
501
|
|
|
}, anon: false, |
502
|
|
|
}; |
503
|
|
|
expect(PollCard).toHaveBeenCalledWith(expectedPollState, DEFAULT_LOCALE_TIMEZONE); |
504
|
|
|
expect(mockCreateCardWithId).toHaveBeenCalled(); |
505
|
|
|
expect(response).toEqual(expectedResponse); |
506
|
|
|
expect(actionHandler.getEventPollState()).toEqual({ |
507
|
|
|
votes: { |
508
|
|
|
'0': [{uid: 'userId', name: 'userName'}], |
509
|
|
|
}, anon: false, |
510
|
|
|
}); |
511
|
|
|
}); |
512
|
|
|
}); |
513
|
|
|
|
514
|
|
|
describe('closePoll', () => { |
515
|
|
|
it('should close the poll and return a status message with "Poll is closed" and status "OK"', async () => { |
516
|
|
|
const expectedResponse = { |
517
|
|
|
actionResponse: { |
518
|
|
|
type: 'DIALOG', |
519
|
|
|
dialogAction: { |
520
|
|
|
actionStatus: { |
521
|
|
|
statusCode: 'OK', |
522
|
|
|
userFacingMessage: 'Poll is closed', |
523
|
|
|
}, |
524
|
|
|
}, |
525
|
|
|
}, |
526
|
|
|
}; |
527
|
|
|
mockUpdate.mockResolvedValue({'status': 200, 'data': {}}); |
528
|
|
|
|
529
|
|
|
const actionHandler = new ActionHandler({message: {name: 'messageName'}}); |
530
|
|
|
actionHandler.getEventPollState = jest.fn().mockReturnValue({}); |
531
|
|
|
const result = await actionHandler.closePoll(); |
532
|
|
|
|
533
|
|
|
expect(result).toEqual(expectedResponse); |
534
|
|
|
expect(mockUpdate).toHaveBeenCalledWith({name: 'messageName', requestBody: {cardsV2: []}, updateMask: 'cardsV2'}); |
535
|
|
|
}); |
536
|
|
|
|
537
|
|
|
it('should return a status message with "Failed to close poll." when the call to "callMessageApi" fails', |
538
|
|
|
async () => { |
539
|
|
|
// Arrange |
540
|
|
|
const state = { |
541
|
|
|
closedTime: undefined, |
542
|
|
|
}; |
543
|
|
|
const expectedResponse = { |
544
|
|
|
actionResponse: { |
545
|
|
|
type: 'DIALOG', |
546
|
|
|
dialogAction: { |
547
|
|
|
actionStatus: { |
548
|
|
|
statusCode: 'UNKNOWN', |
549
|
|
|
userFacingMessage: 'Failed to close poll.', |
550
|
|
|
}, |
551
|
|
|
}, |
552
|
|
|
}, |
553
|
|
|
}; |
554
|
|
|
|
555
|
|
|
mockUpdate.mockResolvedValue({'status': 400, 'data': {}}); |
556
|
|
|
|
557
|
|
|
const actionHandler = new ActionHandler({message: {name: 'messageName'}}); |
558
|
|
|
actionHandler.getEventPollState = jest.fn().mockReturnValue(state); |
559
|
|
|
|
560
|
|
|
const result = await actionHandler.closePoll(); |
561
|
|
|
|
562
|
|
|
expect(result).toEqual(expectedResponse); |
563
|
|
|
expect(state.closedTime).toBeDefined(); |
564
|
|
|
|
565
|
|
|
mockUpdate.mockResolvedValue(undefined); |
566
|
|
|
await actionHandler.closePoll().catch((error) => { |
567
|
|
|
expect(error).toEqual(new Error('Empty response')); |
568
|
|
|
}); |
569
|
|
|
}); |
570
|
|
|
}); |
571
|
|
|
|
572
|
|
|
describe('closePollForm', () => { |
573
|
|
|
it('should allow the creator of the poll with CLOSEABLE_BY_CREATOR type to close the poll', () => { |
574
|
|
|
const state = { |
575
|
|
|
type: ClosableType.CLOSEABLE_BY_CREATOR, |
576
|
|
|
author: {name: 'creator'}, |
577
|
|
|
}; |
578
|
|
|
const event = { |
579
|
|
|
user: {name: 'creator'}, |
580
|
|
|
}; |
581
|
|
|
const actionHandler = new ActionHandler(event); |
582
|
|
|
actionHandler.getEventPollState = jest.fn().mockReturnValue(state); |
583
|
|
|
actionHandler.closePollForm(); |
584
|
|
|
|
585
|
|
|
expect(ClosePollFormCard).toHaveBeenCalledWith(state, DEFAULT_LOCALE_TIMEZONE); |
586
|
|
|
expect(mockCreateClosePollFormCard).toHaveBeenCalled(); |
587
|
|
|
}); |
588
|
|
|
it('should disallow the creator of the poll with CLOSEABLE_BY_CREATOR type to close the poll', () => { |
589
|
|
|
const state = { |
590
|
|
|
type: ClosableType.CLOSEABLE_BY_CREATOR, |
591
|
|
|
author: {name: 'creator', displayName: 'creator user'}, |
592
|
|
|
}; |
593
|
|
|
const event = { |
594
|
|
|
user: {name: 'other user'}, |
595
|
|
|
}; |
596
|
|
|
const actionHandler = new ActionHandler(event); |
597
|
|
|
actionHandler.getEventPollState = jest.fn().mockReturnValue(state); |
598
|
|
|
|
599
|
|
|
const dialogConfig = { |
600
|
|
|
title: 'Sorry, you can not close this poll', |
601
|
|
|
message: `The poll setting restricts the ability to close the poll to only the creator(${state.author!.displayName}).`, |
602
|
|
|
imageUrl: PROHIBITED_ICON_URL, |
603
|
|
|
}; |
604
|
|
|
const expectedResponse = createDialogActionResponse(new MessageDialogCard(dialogConfig).create()); |
605
|
|
|
const result = actionHandler.closePollForm(); |
606
|
|
|
expect(result).toEqual(expectedResponse); |
607
|
|
|
}); |
608
|
|
|
}); |
609
|
|
|
|
610
|
|
|
describe('scheduleClosePoll', () => { |
611
|
|
|
it('should return a message with an updated poll card when the action is "close_poll_form"', async () => { |
612
|
|
|
// Mock the closePollForm function |
613
|
|
|
const state = { |
614
|
|
|
type: ClosableType.CLOSEABLE_BY_CREATOR, |
615
|
|
|
author: {name: 'creator', displayName: 'creator test user'}, |
616
|
|
|
}; |
617
|
|
|
// Create an instance of ActionHandler |
618
|
|
|
const actionHandler = new ActionHandler({common: {invokedFunction: 'schedule_close_poll_form'}}); |
619
|
|
|
|
620
|
|
|
actionHandler.getEventPollState = jest.fn().mockReturnValue(state); |
621
|
|
|
|
622
|
|
|
// Call the process method |
623
|
|
|
await actionHandler.process(); |
624
|
|
|
|
625
|
|
|
// Expect the saveOption function to be called |
626
|
|
|
expect(ScheduleClosePollFormCard).toHaveBeenCalled(); |
627
|
|
|
expect(mockScheduleCreateClosePollFormCard).toHaveBeenCalled(); |
628
|
|
|
expect(actionHandler.getEventPollState).toHaveBeenCalled(); |
629
|
|
|
}); |
630
|
|
|
|
631
|
|
|
it('should return schedule close form card when the schedule input time is in the past', async () => { |
632
|
|
|
// Create an instance of ActionHandler |
633
|
|
|
const actionHandler = new ActionHandler({ |
634
|
|
|
common: { |
635
|
|
|
invokedFunction: 'schedule_close_poll', |
636
|
|
|
formInputs: { |
637
|
|
|
close_schedule_time: {dateTimeInput: {msSinceEpoch: (Date.now() - 1000000).toString()}}, |
638
|
|
|
}, |
639
|
|
|
}, |
640
|
|
|
message: {'name': 'anu'}, |
641
|
|
|
}); |
642
|
|
|
actionHandler.getEventPollState = jest.fn(); |
643
|
|
|
|
644
|
|
|
process.env.GCP_PROJECT = 'test-project'; |
645
|
|
|
process.env.QUEUE_NAME = 'test-queue'; |
646
|
|
|
process.env.FUNCTION_REGION = 'us-central1'; |
647
|
|
|
// Call the process method |
648
|
|
|
await actionHandler.process(); |
649
|
|
|
|
650
|
|
|
expect(actionHandler.getEventPollState).not.toHaveBeenCalled(); |
651
|
|
|
// since the schedule date is in the past, the form will show again |
652
|
|
|
expect(ScheduleClosePollFormCard).toHaveBeenCalled(); |
653
|
|
|
expect(mockScheduleCreateClosePollFormCard).toHaveBeenCalled(); |
654
|
|
|
}); |
655
|
|
|
}); |
656
|
|
|
it('should update message if close_schedule_time is correct', async () => { |
657
|
|
|
const ms = Date.now() + dummyLocalTimezone.offset + 1000000; |
658
|
|
|
// Create an instance of ActionHandler |
659
|
|
|
const actionHandler = new ActionHandler({ |
660
|
|
|
common: { |
661
|
|
|
invokedFunction: 'schedule_close_poll', |
662
|
|
|
formInputs: { |
663
|
|
|
close_schedule_time: {dateTimeInput: {msSinceEpoch: ms.toString()}}, |
664
|
|
|
auto_mention: {stringInputs: {value: ['1']}}, |
665
|
|
|
}, |
666
|
|
|
timeZone: {'id': dummyLocalTimezone.id, 'offset': dummyLocalTimezone.offset}, |
667
|
|
|
userLocale: dummyLocalTimezone.locale, |
668
|
|
|
}, |
669
|
|
|
message: {'name': 'anu'}, |
670
|
|
|
}); |
671
|
|
|
|
672
|
|
|
const state = { |
673
|
|
|
type: ClosableType.CLOSEABLE_BY_CREATOR, |
674
|
|
|
author: {name: 'creator', displayName: 'creator userzzzz'}, closedTime: undefined, |
675
|
|
|
|
676
|
|
|
}; |
677
|
|
|
actionHandler.getEventPollState = jest.fn().mockReturnValue(state); |
678
|
|
|
mockUpdate.mockReturnValue(state); |
679
|
|
|
process.env.GCP_PROJECT = 'test-project'; |
680
|
|
|
process.env.QUEUE_NAME = 'test-queue'; |
681
|
|
|
process.env.FUNCTION_REGION = 'us-central1'; |
682
|
|
|
|
683
|
|
|
// Call the process method |
684
|
|
|
await actionHandler.scheduleClosePoll(); |
685
|
|
|
|
686
|
|
|
expect(actionHandler.getEventPollState).toHaveBeenCalled(); |
687
|
|
|
expect(mockUpdate).toHaveBeenCalled(); |
688
|
|
|
// since the schedule date is in the past, the form will show again |
689
|
|
|
expect(ScheduleClosePollFormCard).not.toHaveBeenCalled(); |
690
|
|
|
|
691
|
|
|
expect(PollCard).toHaveBeenCalledWith(state, dummyLocalTimezone); |
692
|
|
|
expect(state.closedTime).toEqual(ms - dummyLocalTimezone.offset); |
693
|
|
|
// todo: create task toHaveBeenCalled |
694
|
|
|
}); |
695
|
|
|
|
696
|
|
|
|
697
|
|
|
it('voteForm action', () => { |
698
|
|
|
const state = { |
699
|
|
|
type: ClosableType.CLOSEABLE_BY_CREATOR, |
700
|
|
|
author: {name: 'creator'}, |
701
|
|
|
votes: {}, |
702
|
|
|
}; |
703
|
|
|
const event = { |
704
|
|
|
user: {name: '1123124124124', displayName: 'creator'}, |
705
|
|
|
common: { |
706
|
|
|
parameters: { |
707
|
|
|
index: '1', |
708
|
|
|
}, |
709
|
|
|
timeZone: {'id': dummyLocalTimezone.id, 'offset': dummyLocalTimezone.offset}, |
710
|
|
|
userLocale: dummyLocalTimezone.locale, |
711
|
|
|
}, |
712
|
|
|
message: { |
713
|
|
|
thread: { |
714
|
|
|
'name': 'spaces/AAAAN0lf83o/threads/DJXfo5DXcTA', |
715
|
|
|
}, |
716
|
|
|
cardsV2: [{cardId: 'card', card: {}}], |
717
|
|
|
}, |
718
|
|
|
}; |
719
|
|
|
const actionHandler = new ActionHandler(event); |
720
|
|
|
actionHandler.getEventPollState = jest.fn().mockReturnValue(state); |
721
|
|
|
// Act |
722
|
|
|
actionHandler.voteForm(); |
723
|
|
|
expect(PollCard).toHaveBeenCalledWith(state, dummyLocalTimezone); |
724
|
|
|
expect(PollDialogCard).toHaveBeenCalledWith(state, dummyLocalTimezone, {name: 'creator', uid: '1123124124124'}); |
725
|
|
|
expect(mockCreatePollDialogCard).toHaveBeenCalled(); |
726
|
|
|
}); |
727
|
|
|
|
728
|
|
|
|
729
|
|
|
it('switchVote action', async () => { |
730
|
|
|
const event = { |
731
|
|
|
common: { |
732
|
|
|
parameters: { |
733
|
|
|
}, |
734
|
|
|
}, |
735
|
|
|
}; |
736
|
|
|
const actionHandler = new ActionHandler(event); |
737
|
|
|
|
738
|
|
|
await expect(async () => { |
739
|
|
|
await actionHandler.voteForm(); |
740
|
|
|
}).rejects.toThrowError('Index Out of Bounds'); |
741
|
|
|
}); |
742
|
|
|
|