reducers.test.ts 13 KB

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