1import { dateTime } from '@grafana/data';
2import {
3  alertRulesReducer,
4  initialChannelState,
5  initialState,
6  loadAlertRules,
7  loadedAlertRules,
8  notificationChannelReducer,
9  setSearchQuery,
10  notificationChannelLoaded,
11} from './reducers';
12import { AlertRuleDTO, AlertRulesState, NotificationChannelState, NotifierDTO } from 'app/types';
13import { reducerTester } from '../../../../test/core/redux/reducerTester';
14
15describe('Alert rules', () => {
16  const realDateNow = Date.now.bind(global.Date);
17  const anchorUnix = dateTime('2019-09-04T10:01:01+02:00').valueOf();
18  const dateNowStub = jest.fn(() => anchorUnix);
19  global.Date.now = dateNowStub;
20
21  const newStateDate = dateTime().subtract(1, 'y');
22  const newStateDateFormatted = newStateDate.format('YYYY-MM-DD');
23  const newStateDateAge = newStateDate.fromNow(true);
24  const payload: AlertRuleDTO[] = [
25    {
26      id: 2,
27      dashboardId: 7,
28      dashboardUid: 'ggHbN42mk',
29      dashboardSlug: 'alerting-with-testdata',
30      panelId: 4,
31      name: 'TestData - Always Alerting',
32      state: 'alerting',
33      newStateDate: `${newStateDateFormatted}T10:00:30+02:00`,
34      evalDate: '0001-01-01T00:00:00Z',
35      evalData: { evalMatches: [{ metric: 'A-series', tags: null, value: 215 }] },
36      executionError: '',
37      url: '/d/ggHbN42mk/alerting-with-testdata',
38    },
39    {
40      id: 1,
41      dashboardId: 7,
42      dashboardUid: 'ggHbN42mk',
43      dashboardSlug: 'alerting-with-testdata',
44      panelId: 3,
45      name: 'TestData - Always OK',
46      state: 'ok',
47      newStateDate: `${newStateDateFormatted}T10:01:01+02:00`,
48      evalDate: '0001-01-01T00:00:00Z',
49      evalData: {},
50      executionError: '',
51      url: '/d/ggHbN42mk/alerting-with-testdata',
52    },
53    {
54      id: 3,
55      dashboardId: 7,
56      dashboardUid: 'ggHbN42mk',
57      dashboardSlug: 'alerting-with-testdata',
58      panelId: 3,
59      name: 'TestData - ok',
60      state: 'ok',
61      newStateDate: `${newStateDateFormatted}T10:01:01+02:00`,
62      evalDate: '0001-01-01T00:00:00Z',
63      evalData: {},
64      executionError: 'error',
65      url: '/d/ggHbN42mk/alerting-with-testdata',
66    },
67    {
68      id: 4,
69      dashboardId: 7,
70      dashboardUid: 'ggHbN42mk',
71      dashboardSlug: 'alerting-with-testdata',
72      panelId: 3,
73      name: 'TestData - Paused',
74      state: 'paused',
75      newStateDate: `${newStateDateFormatted}T10:01:01+02:00`,
76      evalDate: '0001-01-01T00:00:00Z',
77      evalData: {},
78      executionError: 'error',
79      url: '/d/ggHbN42mk/alerting-with-testdata',
80    },
81    {
82      id: 5,
83      dashboardId: 7,
84      dashboardUid: 'ggHbN42mk',
85      dashboardSlug: 'alerting-with-testdata',
86      panelId: 3,
87      name: 'TestData - Ok',
88      state: 'ok',
89      newStateDate: `${newStateDateFormatted}T10:01:01+02:00`,
90      evalDate: '0001-01-01T00:00:00Z',
91      evalData: {
92        noData: true,
93      },
94      executionError: 'error',
95      url: '/d/ggHbN42mk/alerting-with-testdata',
96    },
97  ];
98
99  afterAll(() => {
100    global.Date.now = realDateNow;
101  });
102
103  describe('when loadAlertRules is dispatched', () => {
104    it('then state should be correct', () => {
105      reducerTester<AlertRulesState>()
106        .givenReducer(alertRulesReducer, { ...initialState })
107        .whenActionIsDispatched(loadAlertRules())
108        .thenStateShouldEqual({ ...initialState, isLoading: true });
109    });
110  });
111
112  describe('when setSearchQuery is dispatched', () => {
113    it('then state should be correct', () => {
114      reducerTester<AlertRulesState>()
115        .givenReducer(alertRulesReducer, { ...initialState })
116        .whenActionIsDispatched(setSearchQuery('query'))
117        .thenStateShouldEqual({ ...initialState, searchQuery: 'query' });
118    });
119  });
120
121  describe('when loadedAlertRules is dispatched', () => {
122    it('then state should be correct', () => {
123      reducerTester<AlertRulesState>()
124        .givenReducer(alertRulesReducer, { ...initialState, isLoading: true })
125        .whenActionIsDispatched(loadedAlertRules(payload))
126        .thenStateShouldEqual({
127          ...initialState,
128          isLoading: false,
129          items: [
130            {
131              dashboardId: 7,
132              dashboardSlug: 'alerting-with-testdata',
133              dashboardUid: 'ggHbN42mk',
134              evalData: {
135                evalMatches: [
136                  {
137                    metric: 'A-series',
138                    tags: null,
139                    value: 215,
140                  },
141                ],
142              },
143              evalDate: '0001-01-01T00:00:00Z',
144              executionError: '',
145              id: 2,
146              name: 'TestData - Always Alerting',
147              newStateDate: `${newStateDateFormatted}T10:00:30+02:00`,
148              panelId: 4,
149              state: 'alerting',
150              stateAge: newStateDateAge,
151              stateClass: 'alert-state-critical',
152              stateIcon: 'heart-break',
153              stateText: 'ALERTING',
154              url: '/d/ggHbN42mk/alerting-with-testdata',
155            },
156            {
157              dashboardId: 7,
158              dashboardSlug: 'alerting-with-testdata',
159              dashboardUid: 'ggHbN42mk',
160              evalData: {},
161              evalDate: '0001-01-01T00:00:00Z',
162              executionError: '',
163              id: 1,
164              name: 'TestData - Always OK',
165              newStateDate: `${newStateDateFormatted}T10:01:01+02:00`,
166              panelId: 3,
167              state: 'ok',
168              stateAge: newStateDateAge,
169              stateClass: 'alert-state-ok',
170              stateIcon: 'heart',
171              stateText: 'OK',
172              url: '/d/ggHbN42mk/alerting-with-testdata',
173            },
174            {
175              dashboardId: 7,
176              dashboardSlug: 'alerting-with-testdata',
177              dashboardUid: 'ggHbN42mk',
178              evalData: {},
179              evalDate: '0001-01-01T00:00:00Z',
180              executionError: 'error',
181              id: 3,
182              info: 'Execution Error: error',
183              name: 'TestData - ok',
184              newStateDate: `${newStateDateFormatted}T10:01:01+02:00`,
185              panelId: 3,
186              state: 'ok',
187              stateAge: newStateDateAge,
188              stateClass: 'alert-state-ok',
189              stateIcon: 'heart',
190              stateText: 'OK',
191              url: '/d/ggHbN42mk/alerting-with-testdata',
192            },
193            {
194              dashboardId: 7,
195              dashboardSlug: 'alerting-with-testdata',
196              dashboardUid: 'ggHbN42mk',
197              evalData: {},
198              evalDate: '0001-01-01T00:00:00Z',
199              executionError: 'error',
200              id: 4,
201              name: 'TestData - Paused',
202              newStateDate: `${newStateDateFormatted}T10:01:01+02:00`,
203              panelId: 3,
204              state: 'paused',
205              stateAge: newStateDateAge,
206              stateClass: 'alert-state-paused',
207              stateIcon: 'pause',
208              stateText: 'PAUSED',
209              url: '/d/ggHbN42mk/alerting-with-testdata',
210            },
211            {
212              dashboardId: 7,
213              dashboardSlug: 'alerting-with-testdata',
214              dashboardUid: 'ggHbN42mk',
215              evalData: {
216                noData: true,
217              },
218              evalDate: '0001-01-01T00:00:00Z',
219              executionError: 'error',
220              id: 5,
221              info: 'Query returned no data',
222              name: 'TestData - Ok',
223              newStateDate: `${newStateDateFormatted}T10:01:01+02:00`,
224              panelId: 3,
225              state: 'ok',
226              stateAge: newStateDateAge,
227              stateClass: 'alert-state-ok',
228              stateIcon: 'heart',
229              stateText: 'OK',
230              url: '/d/ggHbN42mk/alerting-with-testdata',
231            },
232          ],
233        });
234    });
235  });
236});
237
238describe('Notification channel', () => {
239  const notifiers: NotifierDTO[] = [
240    {
241      type: 'webhook',
242      name: 'webhook',
243      heading: 'Webhook settings',
244      description: 'Sends HTTP POST request to a URL',
245      info: '',
246      options: [
247        {
248          element: 'input',
249          inputType: 'text',
250          label: 'Url',
251          description: '',
252          placeholder: '',
253          propertyName: 'url',
254          showWhen: { field: '', is: '' },
255          required: true,
256          validationRule: '',
257          secure: false,
258        },
259        {
260          element: 'select',
261          inputType: '',
262          label: 'Http Method',
263          description: '',
264          placeholder: '',
265          propertyName: 'httpMethod',
266          selectOptions: [
267            { value: 'POST', label: 'POST' },
268            { value: 'PUT', label: 'PUT' },
269          ],
270          showWhen: { field: '', is: '' },
271          required: false,
272          validationRule: '',
273          secure: false,
274        },
275        {
276          element: 'input',
277          inputType: 'text',
278          label: 'Username',
279          description: '',
280          placeholder: '',
281          propertyName: 'username',
282          showWhen: { field: '', is: '' },
283          required: false,
284          validationRule: '',
285          secure: false,
286        },
287        {
288          element: 'input',
289          inputType: 'password',
290          label: 'Password',
291          description: '',
292          placeholder: '',
293          propertyName: 'password',
294          showWhen: { field: '', is: '' },
295          required: false,
296          validationRule: '',
297          secure: true,
298        },
299      ],
300    },
301  ];
302
303  describe('Load notification channel', () => {
304    it('should migrate non secure settings to secure fields', () => {
305      const payload = {
306        id: 2,
307        uid: '9L3FrrHGk',
308        name: 'Webhook test',
309        type: 'webhook',
310        isDefault: false,
311        sendReminder: false,
312        disableResolveMessage: false,
313        frequency: '',
314        created: '2020-08-28T08:49:24Z',
315        updated: '2020-08-28T08:49:24Z',
316        settings: {
317          autoResolve: true,
318          httpMethod: 'POST',
319          password: 'asdf',
320          severity: 'critical',
321          uploadImage: true,
322          url: 'http://localhost.webhook',
323          username: 'asdf',
324        },
325      };
326
327      const expected = {
328        id: 2,
329        uid: '9L3FrrHGk',
330        name: 'Webhook test',
331        type: 'webhook',
332        isDefault: false,
333        sendReminder: false,
334        disableResolveMessage: false,
335        frequency: '',
336        created: '2020-08-28T08:49:24Z',
337        updated: '2020-08-28T08:49:24Z',
338        secureSettings: {
339          password: 'asdf',
340        },
341        settings: {
342          autoResolve: true,
343          httpMethod: 'POST',
344          password: '',
345          severity: 'critical',
346          uploadImage: true,
347          url: 'http://localhost.webhook',
348          username: 'asdf',
349        },
350      };
351
352      reducerTester<NotificationChannelState>()
353        .givenReducer(notificationChannelReducer, { ...initialChannelState, notifiers: notifiers })
354        .whenActionIsDispatched(notificationChannelLoaded(payload))
355        .thenStateShouldEqual({
356          ...initialChannelState,
357          notifiers: notifiers,
358          notificationChannel: expected,
359        });
360    });
361
362    it('should handle already secure field', () => {
363      const payload = {
364        id: 2,
365        uid: '9L3FrrHGk',
366        name: 'Webhook test',
367        type: 'webhook',
368        isDefault: false,
369        sendReminder: false,
370        disableResolveMessage: false,
371        frequency: '',
372        created: '2020-08-28T08:49:24Z',
373        updated: '2020-08-28T08:49:24Z',
374        secureFields: {
375          password: true,
376        },
377        settings: {
378          autoResolve: true,
379          httpMethod: 'POST',
380          password: '',
381          severity: 'critical',
382          uploadImage: true,
383          url: 'http://localhost.webhook',
384          username: 'asdf',
385        },
386      };
387
388      const expected = {
389        id: 2,
390        uid: '9L3FrrHGk',
391        name: 'Webhook test',
392        type: 'webhook',
393        isDefault: false,
394        sendReminder: false,
395        disableResolveMessage: false,
396        frequency: '',
397        created: '2020-08-28T08:49:24Z',
398        updated: '2020-08-28T08:49:24Z',
399        secureFields: {
400          password: true,
401        },
402        settings: {
403          autoResolve: true,
404          httpMethod: 'POST',
405          password: '',
406          severity: 'critical',
407          uploadImage: true,
408          url: 'http://localhost.webhook',
409          username: 'asdf',
410        },
411      };
412
413      reducerTester<NotificationChannelState>()
414        .givenReducer(notificationChannelReducer, { ...initialChannelState, notifiers: notifiers })
415        .whenActionIsDispatched(notificationChannelLoaded(payload))
416        .thenStateShouldEqual({
417          ...initialChannelState,
418          notifiers: notifiers,
419          notificationChannel: expected,
420        });
421    });
422  });
423});
424