]> git.proxmox.com Git - ceph.git/blob - ceph/src/pybind/mgr/dashboard/frontend/src/app/shared/services/notification.service.spec.ts
028dd90ea39684f174fd8f6007a48697741691b0
[ceph.git] / ceph / src / pybind / mgr / dashboard / frontend / src / app / shared / services / notification.service.spec.ts
1 import { HttpClientTestingModule } from '@angular/common/http/testing';
2 import { fakeAsync, TestBed, tick } from '@angular/core/testing';
3
4 import _ from 'lodash';
5 import { ToastrService } from 'ngx-toastr';
6
7 import { configureTestBed } from '~/testing/unit-test-helper';
8 import { RbdService } from '../api/rbd.service';
9 import { NotificationType } from '../enum/notification-type.enum';
10 import { CdNotificationConfig } from '../models/cd-notification';
11 import { FinishedTask } from '../models/finished-task';
12 import { CdDatePipe } from '../pipes/cd-date.pipe';
13 import { NotificationService } from './notification.service';
14 import { TaskMessageService } from './task-message.service';
15
16 describe('NotificationService', () => {
17 let service: NotificationService;
18 const toastFakeService = {
19 error: () => true,
20 info: () => true,
21 success: () => true
22 };
23
24 configureTestBed({
25 providers: [
26 NotificationService,
27 TaskMessageService,
28 { provide: ToastrService, useValue: toastFakeService },
29 { provide: CdDatePipe, useValue: { transform: (d: any) => d } },
30 RbdService
31 ],
32 imports: [HttpClientTestingModule]
33 });
34
35 beforeEach(() => {
36 service = TestBed.inject(NotificationService);
37 service.removeAll();
38 });
39
40 it('should be created', () => {
41 expect(service).toBeTruthy();
42 });
43
44 it('should read empty notification list', () => {
45 localStorage.setItem('cdNotifications', '[]');
46 expect(service['dataSource'].getValue()).toEqual([]);
47 });
48
49 it('should read old notifications', fakeAsync(() => {
50 localStorage.setItem(
51 'cdNotifications',
52 '[{"type":2,"message":"foobar","timestamp":"2018-05-24T09:41:32.726Z"}]'
53 );
54 service = new NotificationService(null, null, null);
55 expect(service['dataSource'].getValue().length).toBe(1);
56 }));
57
58 it('should cancel a notification', fakeAsync(() => {
59 const timeoutId = service.show(NotificationType.error, 'Simple test');
60 service.cancel(timeoutId);
61 tick(5000);
62 expect(service['dataSource'].getValue().length).toBe(0);
63 }));
64
65 describe('Saved notifications', () => {
66 const expectSavedNotificationToHave = (expected: object) => {
67 tick(510);
68 expect(service['dataSource'].getValue().length).toBe(1);
69 const notification = service['dataSource'].getValue()[0];
70 Object.keys(expected).forEach((key) => {
71 expect(notification[key]).toBe(expected[key]);
72 });
73 };
74
75 const addNotifications = (quantity: number) => {
76 for (let index = 0; index < quantity; index++) {
77 service.show(NotificationType.info, `${index}`);
78 tick(510);
79 }
80 };
81
82 beforeEach(() => {
83 spyOn(service, 'show').and.callThrough();
84 service.cancel((<any>service)['justShownTimeoutId']);
85 });
86
87 it('should create a success notification and save it', fakeAsync(() => {
88 service.show(new CdNotificationConfig(NotificationType.success, 'Simple test'));
89 expectSavedNotificationToHave({ type: NotificationType.success });
90 }));
91
92 it('should create an error notification and save it', fakeAsync(() => {
93 service.show(NotificationType.error, 'Simple test');
94 expectSavedNotificationToHave({ type: NotificationType.error });
95 }));
96
97 it('should create an info notification and save it', fakeAsync(() => {
98 service.show(new CdNotificationConfig(NotificationType.info, 'Simple test'));
99 expectSavedNotificationToHave({
100 type: NotificationType.info,
101 title: 'Simple test',
102 message: undefined
103 });
104 }));
105
106 it('should never have more then 10 notifications', fakeAsync(() => {
107 addNotifications(15);
108 expect(service['dataSource'].getValue().length).toBe(10);
109 }));
110
111 it('should show a success task notification, but not save it', fakeAsync(() => {
112 const task = _.assign(new FinishedTask(), {
113 success: true
114 });
115
116 service.notifyTask(task, true);
117 tick(1500);
118
119 expect(service.show).toHaveBeenCalled();
120 const notifications = service['dataSource'].getValue();
121 expect(notifications.length).toBe(0);
122 }));
123
124 it('should be able to stop notifyTask from notifying', fakeAsync(() => {
125 const task = _.assign(new FinishedTask(), {
126 success: true
127 });
128 const timeoutId = service.notifyTask(task, true);
129 service.cancel(timeoutId);
130 tick(100);
131 expect(service['dataSource'].getValue().length).toBe(0);
132 }));
133
134 it('should show a error task notification', fakeAsync(() => {
135 const task = _.assign(
136 new FinishedTask('rbd/create', {
137 pool_name: 'somePool',
138 image_name: 'someImage'
139 }),
140 {
141 success: false,
142 exception: {
143 code: 17
144 }
145 }
146 );
147 service.notifyTask(task);
148
149 tick(1500);
150
151 expect(service.show).toHaveBeenCalled();
152 const notifications = service['dataSource'].getValue();
153 expect(notifications.length).toBe(0);
154 }));
155
156 it('combines different notifications with the same title', fakeAsync(() => {
157 service.show(NotificationType.error, '502 - Bad Gateway', 'Error occurred in path a');
158 tick(60);
159 service.show(NotificationType.error, '502 - Bad Gateway', 'Error occurred in path b');
160 expectSavedNotificationToHave({
161 type: NotificationType.error,
162 title: '502 - Bad Gateway',
163 message: '<ul><li>Error occurred in path a</li><li>Error occurred in path b</li></ul>'
164 });
165 }));
166
167 it('should remove a single notification', fakeAsync(() => {
168 addNotifications(5);
169 let messages = service['dataSource'].getValue().map((notification) => notification.title);
170 expect(messages).toEqual(['4', '3', '2', '1', '0']);
171 service.remove(2);
172 messages = service['dataSource'].getValue().map((notification) => notification.title);
173 expect(messages).toEqual(['4', '3', '1', '0']);
174 }));
175
176 it('should remove all notifications', fakeAsync(() => {
177 addNotifications(5);
178 expect(service['dataSource'].getValue().length).toBe(5);
179 service.removeAll();
180 expect(service['dataSource'].getValue().length).toBe(0);
181 }));
182 });
183
184 describe('notification queue', () => {
185 const n1 = new CdNotificationConfig(NotificationType.success, 'Some success');
186 const n2 = new CdNotificationConfig(NotificationType.info, 'Some info');
187
188 const showArray = (arr: any[]) => arr.forEach((n) => service.show(n));
189
190 beforeEach(() => {
191 spyOn(service, 'save').and.stub();
192 });
193
194 it('filters out duplicated notifications on single call', fakeAsync(() => {
195 showArray([n1, n1, n2, n2]);
196 tick(510);
197 expect(service.save).toHaveBeenCalledTimes(2);
198 }));
199
200 it('filters out duplicated notifications presented in different calls', fakeAsync(() => {
201 showArray([n1, n2]);
202 showArray([n1, n2]);
203 tick(1000);
204 expect(service.save).toHaveBeenCalledTimes(2);
205 }));
206
207 it('will reset the timeout on every call', fakeAsync(() => {
208 showArray([n1, n2]);
209 tick(490);
210 showArray([n1, n2]);
211 tick(450);
212 expect(service.save).toHaveBeenCalledTimes(0);
213 tick(60);
214 expect(service.save).toHaveBeenCalledTimes(2);
215 }));
216
217 it('wont filter out duplicated notifications if timeout was reached before', fakeAsync(() => {
218 showArray([n1, n2]);
219 tick(510);
220 showArray([n1, n2]);
221 tick(510);
222 expect(service.save).toHaveBeenCalledTimes(4);
223 }));
224 });
225
226 describe('showToasty', () => {
227 let toastr: ToastrService;
228 const time = '2022-02-22T00:00:00.000Z';
229
230 beforeEach(() => {
231 const baseTime = new Date(time);
232 spyOn(global, 'Date').and.returnValue(baseTime);
233 spyOn(window, 'setTimeout').and.callFake((fn) => fn());
234
235 toastr = TestBed.inject(ToastrService);
236 // spyOn needs to know the methods before spying and can't read the array for clarification
237 ['error', 'info', 'success'].forEach((method: 'error' | 'info' | 'success') =>
238 spyOn(toastr, method).and.stub()
239 );
240 });
241
242 it('should show with only title defined', () => {
243 service.show(NotificationType.info, 'Some info');
244 expect(toastr.info).toHaveBeenCalledWith(
245 `<small class="date">${time}</small>` +
246 '<i class="float-right custom-icon ceph-icon" title="Ceph"></i>',
247 'Some info',
248 undefined
249 );
250 });
251
252 it('should show with title and message defined', () => {
253 service.show(
254 () =>
255 new CdNotificationConfig(NotificationType.error, 'Some error', 'Some operation failed')
256 );
257 expect(toastr.error).toHaveBeenCalledWith(
258 'Some operation failed<br>' +
259 `<small class="date">${time}</small>` +
260 '<i class="float-right custom-icon ceph-icon" title="Ceph"></i>',
261 'Some error',
262 undefined
263 );
264 });
265
266 it('should show with title, message and application defined', () => {
267 service.show(
268 new CdNotificationConfig(
269 NotificationType.success,
270 'Alert resolved',
271 'Some alert resolved',
272 undefined,
273 'Prometheus'
274 )
275 );
276 expect(toastr.success).toHaveBeenCalledWith(
277 'Some alert resolved<br>' +
278 `<small class="date">${time}</small>` +
279 '<i class="float-right custom-icon prometheus-icon" title="Prometheus"></i>',
280 'Alert resolved',
281 undefined
282 );
283 });
284 });
285 });