Completed
Push — master ( 4fcad4...efcce7 )
by Muhammad Dyas
19s queued 16s
created

tests/action-handler.test.ts   A

Complexity

Total Complexity 1
Complexity/F 0

Size

Lines of Code 365
Function Count 0

Duplication

Duplicated Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
wmc 1
eloc 258
mnd 1
bc 1
fnc 0
dl 0
loc 365
rs 10
bpm 0
cpm 0
noi 0
c 0
b 0
f 0
1
// @ts-ignore: mock
2
import PollCard, {mockCreateCardWithId} from '../src/cards/PollCard';
3
import ActionHandler from '../src/handlers/ActionHandler';
4
// @ts-ignore: dummy test
5
import dummyAddOptionForm from './json/add_option_form.json';
6
import {mockCreate, mockGoogleAuth, mockUpdate} from './mocks';
7
import {createStatusActionResponse} from '../src/helpers/response';
8
import NewPollFormCard from '../src/cards/NewPollFormCard';
9
import {chat_v1 as chatV1} from 'googleapis';
10
11
jest.mock('../src/cards/PollCard');
12
13
jest.mock('googleapis', () => {
14
  return {
15
    google: {
16
      auth: {
17
        GoogleAuth: jest.fn(() => mockGoogleAuth),
18
      },
19
      chat: jest.fn().mockImplementation(() => {
20
        return {
21
          spaces: {
22
            messages: {
23
              create: mockCreate,
24
              update: mockUpdate,
25
            },
26
          },
27
        };
28
      }),
29
    },
30
  };
31
});
32
33
it('should add a new option to the poll state and return an "OK" status message', async () => {
34
  // Mock event object
35
  const event = {
36
    user: {
37
      displayName: 'John Doe',
38
    },
39
    common: {
40
      formInputs: {
41
        value: {
42
          stringInputs: {
43
            value: ['Option 1'],
44
          },
45
        },
46
      },
47
    },
48
    message: {
49
      name: 'messageName',
50
    },
51
  };
52
53
  // Mock getEventPollState function
54
  const getEventPollStateMock = jest.fn().mockReturnValue({choices: [], choiceCreator: {}});
55
56
  // Create instance of ActionHandler
57
  const actionHandler = new ActionHandler(event);
58
59
  // Mock getEventPollState method
60
  actionHandler.getEventPollState = getEventPollStateMock;
61
62
  // Call saveOption method
63
  const result = await actionHandler.saveOption();
64
65
  expect(getEventPollStateMock).toHaveBeenCalled();
66
  expect(mockUpdate).toHaveBeenCalledWith({name: 'messageName', requestBody: {cardsV2: []}, updateMask: 'cardsV2'});
67
  expect(result).toEqual(createStatusActionResponse('Option is added'));
68
69
  const actionHandler2 = new ActionHandler(event);
70
71
  actionHandler2.getEventPollState = getEventPollStateMock;
72
  mockUpdate.mockResolvedValue('');
73
  const result2 = await actionHandler2.saveOption();
74
  expect(result2).toEqual(createStatusActionResponse('Failed to add option.', 'UNKNOWN'));
75
});
76
77
describe('process', () => {
78
  it('should return a message with a poll card when the action is "start_poll"', async () => {
79
    // Mock the startPoll function
80
    const startPollMock = jest.fn().mockReturnValue({});
81
82
    // Create an instance of ActionHandler
83
    const actionHandler = new ActionHandler({common: {invokedFunction: 'start_poll'}});
84
85
    // Mock the startPoll function in the ActionHandler instance
86
    actionHandler.startPoll = startPollMock;
87
88
    // Call the process method
89
    await actionHandler.process();
90
91
    // Expect the startPoll  to be called
92
    expect(startPollMock).toHaveBeenCalled();
93
  });
94
95
  it('should return a message with an updated poll card when the action is "vote"', async () => {
96
    // Mock the recordVote function
97
    const recordVoteMock = jest.fn().mockReturnValue({});
98
99
    // Create an instance of ActionHandler
100
    const actionHandler = new ActionHandler({common: {invokedFunction: 'vote'}});
101
102
    // Mock the recordVote function in the ActionHandler instance
103
    actionHandler.recordVote = recordVoteMock;
104
105
    // Call the process method
106
    await actionHandler.process();
107
108
    // Expect the recordVote function to be called
109
    expect(recordVoteMock).toHaveBeenCalled();
110
  });
111
112
  it('should return a dialog with an add option form when the action is "add_option_form"', async () => {
113
    // Mock the addOptionForm function
114
    const addOptionFormMock = jest.fn().mockReturnValue({});
115
116
    // Create an instance of ActionHandler
117
    const actionHandler = new ActionHandler({common: {invokedFunction: 'add_option_form'}});
118
119
    // Mock the addOptionForm function in the ActionHandler instance
120
    actionHandler.addOptionForm = addOptionFormMock;
121
122
    // Call the process method
123
    await actionHandler.process();
124
125
    // Expect the addOptionForm function to be called
126
    expect(addOptionFormMock).toHaveBeenCalled();
127
  });
128
  it('should return a dialog with an add option form when the action is "add_option_form"', async () => {
129
    // Mock the addOptionForm function
130
    const addOptionFormMock = jest.fn().mockReturnValue({});
131
132
    // Create an instance of ActionHandler
133
    const actionHandler = new ActionHandler({common: {invokedFunction: 'add_option_form'}});
134
135
    // Mock the addOptionForm function in the ActionHandler instance
136
    actionHandler.addOptionForm = addOptionFormMock;
137
138
    // Call the process method
139
    await actionHandler.process();
140
141
    // Expect the addOptionForm function to be called
142
    expect(addOptionFormMock).toHaveBeenCalled();
143
  });
144
145
  it('should create a dialog with AddOptionFormCard and return it as an actionResponse', () => {
146
    // Arrange
147
    const card = {
148
      sections: [
149
        {
150
          widgets: [
151
            {
152
              decoratedText: {
153
                button: {
154
                  onClick: {
155
                    action: {
156
                      parameters: [
157
                        {
158
                          value: '{"topic":"Who is the most handsome AI?", "choices": []}',
159
                        },
160
                      ],
161
                    },
162
                  },
163
                },
164
              },
165
            },
166
          ],
167
        },
168
      ],
169
    };
170
    const cardWithId: chatV1.Schema$CardWithId = {
171
      cardId: 'cardId',
172
      card,
173
    };
174
    const event = {
175
      message: {
176
        cardsV2: [cardWithId],
177
      },
178
    };
179
    const expectedDialog = {
180
      body: dummyAddOptionForm,
181
    };
182
    const actionHandler = new ActionHandler(event);
183
    actionHandler.getEventPollState = jest.fn().mockReturnValue({});
184
    // Act
185
    const result = actionHandler.addOptionForm();
186
187
    // Assert
188
    expect(result).toEqual({
189
      actionResponse: {
190
        type: 'DIALOG',
191
        dialogAction: {
192
          dialog: expectedDialog,
193
        },
194
      },
195
    });
196
  });
197
198
  // Tests that the 'add_option' action returns a message with an updated poll card
199
  it('should return a message with an updated poll card when the action is "add_option"', async () => {
200
    // Mock the saveOption function
201
    const saveOptionMock = jest.fn().mockReturnValue({});
202
203
    // Create an instance of ActionHandler
204
    const actionHandler = new ActionHandler({common: {invokedFunction: 'add_option'}});
205
206
    // Mock the saveOption function in the ActionHandler instance
207
    actionHandler.saveOption = saveOptionMock;
208
209
    // Call the process method
210
    await actionHandler.process();
211
212
    // Expect the saveOption function to be called
213
    expect(saveOptionMock).toHaveBeenCalled();
214
  });
215
216
  // Tests that the 'unknown' action returns a message with an updated poll card
217
  it('should return a message with an updated poll card when the action is "add_option"', async () => {
218
    // Create an instance of ActionHandler
219
    const actionHandler = new ActionHandler({common: {invokedFunction: 'unknown'}});
220
221
    // Call the process method
222
    const result = await actionHandler.process();
223
224
    // Expect the saveOption function to be called
225
    expect(result).toEqual(createStatusActionResponse('Unknown action!', 'UNKNOWN'));
226
  });
227
});
228
229
describe('startPoll', () => {
230
  // Tests that a valid form is submitted and a poll card is created and displayed in the space
231
  it('should create and display poll card when valid form is submitted', async () => {
232
    const event = {
233
      common: {
234
        invokedFunction: 'start_poll',
235
        formInputs: {
236
          topic: {stringInputs: {value: ['Topic']}},
237
          is_anonymous: {stringInputs: {value: ['1']}},
238
          allow_add_option: {stringInputs: {value: ['1']}},
239
          option0: {stringInputs: {value: ['Option 1']}},
240
          option1: {stringInputs: {value: ['Option 2']}},
241
        },
242
      },
243
      user: {displayName: 'User'},
244
      space: {name: 'Space'},
245
    };
246
    const actionHandler = new ActionHandler(event);
247
248
    const result = await actionHandler.startPoll();
249
    const pollCard = new PollCard({
250
      topic: 'Topic',
251
      choiceCreator: undefined,
252
      author: event.user,
253
      choices: ['Option 1', 'Option 2'],
254
      votes: {'0': [], '1': []},
255
      anon: true,
256
      optionable: true,
257
    }).createCardWithId();
258
    // Valid configuration, make the voting card to display in the space
259
    const message = {
260
      cardsV2: [pollCard],
261
    };
262
    const request = {
263
      parent: event.space?.name,
264
      requestBody: message,
265
    };
266
267
    expect(result).toEqual(createStatusActionResponse('Poll started.', 'OK'));
268
    expect(mockCreate).toHaveBeenCalledWith(request);
269
270
    // when google API return invalid data, it should return an error message
271
    mockCreate.mockResolvedValue('');
272
    const actionHandler2 = new ActionHandler(event);
273
    const result2 = await actionHandler2.startPoll();
274
    expect(result2).toEqual(createStatusActionResponse('Failed to start poll.', 'UNKNOWN'));
275
  });
276
277
  // Tests that an incomplete form is submitted and the form is rerendered
278
  it('should rerender form when incomplete form is submitted', async () => {
279
    // Mock event object
280
    const event = {
281
      common: {
282
        formInputs: {
283
          option0: {stringInputs: {value: ['Option 1']}},
284
        },
285
      },
286
    };
287
288
    const actionHandler = new ActionHandler(event);
289
290
    const result = await actionHandler.startPoll();
291
292
    expect(result).toEqual({
293
      actionResponse: {
294
        type: 'DIALOG',
295
        dialogAction: {
296
          dialog: {
297
            body: new NewPollFormCard({
298
              topic: '',
299
              choices: ['Option 1'],
300
            }).create(),
301
          },
302
        },
303
      },
304
    });
305
  });
306
});
307
describe('recordVote', () => {
308
  it('should throw an error if the index parameter is missing', () => {
309
    const event = {
310
      common: {
311
        parameters: {},
312
      },
313
    };
314
    const actionHandler = new ActionHandler(event);
315
316
    expect(() => actionHandler.recordVote()).toThrow('Index Out of Bounds');
317
    expect(() => actionHandler.getEventPollState()).toThrow('no valid state in the event');
318
  });
319
  it('should update an existing vote with a new vote', () => {
320
    const event = {
321
      common: {
322
        parameters: {
323
          index: '1',
324
          state: '{"votes": {"0": [{"uid": "userId", "name": "userName"}]}, "anon": false}',
325
        },
326
      },
327
      user: {
328
        name: 'userId2',
329
        displayName: 'userName2',
330
      },
331
      message: {
332
        thread: {
333
          'name': 'spaces/AAAAN0lf83o/threads/DJXfo5DXcTA',
334
        },
335
      },
336
    };
337
    const actionHandler = new ActionHandler(event);
338
    const response = actionHandler.recordVote();
339
340
    const expectedResponse = {
341
      thread: {
342
        'name': 'spaces/AAAAN0lf83o/threads/DJXfo5DXcTA',
343
      },
344
      actionResponse: {
345
        type: 'UPDATE_MESSAGE',
346
      },
347
      cardsV2: ['card'],
348
    };
349
    const expectedPollState = {
350
      votes: {
351
        '0': [{uid: 'userId', name: 'userName'}],
352
        '1': [{uid: 'userId2', name: 'userName2'}],
353
      }, anon: false,
354
    };
355
    expect(PollCard).toHaveBeenCalledWith(expectedPollState);
356
    expect(mockCreateCardWithId).toHaveBeenCalled();
357
    expect(response).toEqual(expectedResponse);
358
    expect(actionHandler.getEventPollState()).toEqual({
359
      votes: {
360
        '0': [{uid: 'userId', name: 'userName'}],
361
      }, anon: false,
362
    });
363
  });
364
});
365