]>
Commit | Line | Data |
---|---|---|
11fdf7f2 TL |
1 | import { HttpClientTestingModule } from '@angular/common/http/testing'; |
2 | import { ComponentFixture, fakeAsync, TestBed, tick } from '@angular/core/testing'; | |
3 | import { ReactiveFormsModule } from '@angular/forms'; | |
4 | import { By } from '@angular/platform-browser'; | |
e306af50 | 5 | import { BrowserAnimationsModule } from '@angular/platform-browser/animations'; |
11fdf7f2 TL |
6 | import { RouterTestingModule } from '@angular/router/testing'; |
7 | ||
9f95a23c | 8 | import * as _ from 'lodash'; |
11fdf7f2 TL |
9 | import { BsModalService } from 'ngx-bootstrap/modal'; |
10 | import { TabsModule } from 'ngx-bootstrap/tabs'; | |
9f95a23c | 11 | import { ToastrModule } from 'ngx-toastr'; |
11fdf7f2 TL |
12 | import { EMPTY, of } from 'rxjs'; |
13 | ||
14 | import { | |
15 | configureTestBed, | |
16 | i18nProviders, | |
17 | PermissionHelper | |
18 | } from '../../../../../testing/unit-test-helper'; | |
9f95a23c TL |
19 | import { CoreModule } from '../../../../core/core.module'; |
20 | import { OrchestratorService } from '../../../../shared/api/orchestrator.service'; | |
11fdf7f2 TL |
21 | import { OsdService } from '../../../../shared/api/osd.service'; |
22 | import { ConfirmationModalComponent } from '../../../../shared/components/confirmation-modal/confirmation-modal.component'; | |
23 | import { CriticalConfirmationModalComponent } from '../../../../shared/components/critical-confirmation-modal/critical-confirmation-modal.component'; | |
9f95a23c | 24 | import { FormModalComponent } from '../../../../shared/components/form-modal/form-modal.component'; |
11fdf7f2 TL |
25 | import { TableActionsComponent } from '../../../../shared/datatable/table-actions/table-actions.component'; |
26 | import { CdTableAction } from '../../../../shared/models/cd-table-action'; | |
27 | import { CdTableSelection } from '../../../../shared/models/cd-table-selection'; | |
28 | import { Permissions } from '../../../../shared/models/permissions'; | |
29 | import { AuthStorageService } from '../../../../shared/services/auth-storage.service'; | |
9f95a23c | 30 | import { CephModule } from '../../../ceph.module'; |
11fdf7f2 | 31 | import { PerformanceCounterModule } from '../../../performance-counter/performance-counter.module'; |
11fdf7f2 TL |
32 | import { OsdReweightModalComponent } from '../osd-reweight-modal/osd-reweight-modal.component'; |
33 | import { OsdListComponent } from './osd-list.component'; | |
34 | ||
35 | describe('OsdListComponent', () => { | |
36 | let component: OsdListComponent; | |
37 | let fixture: ComponentFixture<OsdListComponent>; | |
38 | let modalServiceShowSpy: jasmine.Spy; | |
81eedcae | 39 | let osdService: OsdService; |
11fdf7f2 TL |
40 | |
41 | const fakeAuthStorageService = { | |
42 | getPermissions: () => { | |
9f95a23c TL |
43 | return new Permissions({ |
44 | 'config-opt': ['read', 'update', 'create', 'delete'], | |
45 | osd: ['read', 'update', 'create', 'delete'] | |
46 | }); | |
11fdf7f2 TL |
47 | } |
48 | }; | |
49 | ||
9f95a23c TL |
50 | const getTableAction = (name: string) => |
51 | component.tableActions.find((action) => action.name === name); | |
11fdf7f2 TL |
52 | |
53 | const setFakeSelection = () => { | |
54 | // Default data and selection | |
9f95a23c TL |
55 | const selection = [{ id: 1, tree: { device_class: 'ssd' } }]; |
56 | const data = [{ id: 1, tree: { device_class: 'ssd' } }]; | |
11fdf7f2 TL |
57 | |
58 | // Table data and selection | |
59 | component.selection = new CdTableSelection(); | |
60 | component.selection.selected = selection; | |
11fdf7f2 TL |
61 | component.osds = data; |
62 | component.permissions = fakeAuthStorageService.getPermissions(); | |
63 | }; | |
64 | ||
9f95a23c | 65 | const openActionModal = (actionName: string) => { |
11fdf7f2 TL |
66 | setFakeSelection(); |
67 | getTableAction(actionName).click(); | |
68 | }; | |
69 | ||
70 | /** | |
71 | * The following modals are called after the information about their | |
72 | * safety to destroy/remove/mark them lost has been retrieved, hence | |
73 | * we will have to fake its request to be able to open those modals. | |
74 | */ | |
75 | const mockSafeToDestroy = () => { | |
76 | spyOn(TestBed.get(OsdService), 'safeToDestroy').and.callFake(() => | |
9f95a23c TL |
77 | of({ is_safe_to_destroy: true }) |
78 | ); | |
79 | }; | |
80 | ||
81 | const mockSafeToDelete = () => { | |
82 | spyOn(TestBed.get(OsdService), 'safeToDelete').and.callFake(() => | |
83 | of({ is_safe_to_delete: true }) | |
11fdf7f2 TL |
84 | ); |
85 | }; | |
86 | ||
9f95a23c TL |
87 | const mockOrchestratorStatus = () => { |
88 | spyOn(TestBed.get(OrchestratorService), 'status').and.callFake(() => of({ available: true })); | |
89 | }; | |
90 | ||
11fdf7f2 TL |
91 | configureTestBed({ |
92 | imports: [ | |
e306af50 | 93 | BrowserAnimationsModule, |
11fdf7f2 TL |
94 | HttpClientTestingModule, |
95 | PerformanceCounterModule, | |
96 | TabsModule.forRoot(), | |
9f95a23c TL |
97 | ToastrModule.forRoot(), |
98 | CephModule, | |
11fdf7f2 | 99 | ReactiveFormsModule, |
9f95a23c TL |
100 | RouterTestingModule, |
101 | CoreModule, | |
11fdf7f2 TL |
102 | RouterTestingModule |
103 | ], | |
9f95a23c | 104 | declarations: [], |
11fdf7f2 TL |
105 | providers: [ |
106 | { provide: AuthStorageService, useValue: fakeAuthStorageService }, | |
107 | TableActionsComponent, | |
108 | BsModalService, | |
109 | i18nProviders | |
110 | ] | |
111 | }); | |
112 | ||
113 | beforeEach(() => { | |
114 | fixture = TestBed.createComponent(OsdListComponent); | |
11fdf7f2 | 115 | component = fixture.componentInstance; |
81eedcae | 116 | osdService = TestBed.get(OsdService); |
11fdf7f2 TL |
117 | modalServiceShowSpy = spyOn(TestBed.get(BsModalService), 'show').and.stub(); |
118 | }); | |
119 | ||
120 | it('should create', () => { | |
121 | fixture.detectChanges(); | |
122 | expect(component).toBeTruthy(); | |
123 | }); | |
124 | ||
81eedcae | 125 | it('should have columns that are sortable', () => { |
9f95a23c TL |
126 | fixture.detectChanges(); |
127 | expect( | |
128 | component.columns | |
e306af50 | 129 | .filter((column) => !(column.prop === undefined)) |
9f95a23c TL |
130 | .every((column) => Boolean(column.prop)) |
131 | ).toBeTruthy(); | |
81eedcae TL |
132 | }); |
133 | ||
134 | describe('getOsdList', () => { | |
9f95a23c TL |
135 | let osds: any[]; |
136 | ||
137 | const createOsd = (n: number) => | |
138 | <Record<string, any>>{ | |
139 | in: 'in', | |
140 | up: 'up', | |
141 | tree: { | |
142 | device_class: 'ssd' | |
143 | }, | |
144 | stats_history: { | |
801d1391 TL |
145 | op_out_bytes: [ |
146 | [n, n], | |
147 | [n * 2, n * 2] | |
148 | ], | |
149 | op_in_bytes: [ | |
150 | [n * 3, n * 3], | |
151 | [n * 4, n * 4] | |
152 | ] | |
9f95a23c TL |
153 | }, |
154 | stats: { | |
155 | stat_bytes_used: n * n, | |
156 | stat_bytes: n * n * n | |
157 | }, | |
158 | state: [] | |
159 | }; | |
81eedcae TL |
160 | |
161 | const expectAttributeOnEveryOsd = (attr: string) => | |
162 | expect(component.osds.every((osd) => Boolean(_.get(osd, attr)))).toBeTruthy(); | |
163 | ||
164 | beforeEach(() => { | |
165 | spyOn(osdService, 'getList').and.callFake(() => of(osds)); | |
166 | osds = [createOsd(1), createOsd(2), createOsd(3)]; | |
167 | component.getOsdList(); | |
168 | }); | |
169 | ||
170 | it('should replace "this.osds" with new data', () => { | |
171 | expect(component.osds.length).toBe(3); | |
172 | expect(osdService.getList).toHaveBeenCalledTimes(1); | |
173 | ||
174 | osds = [createOsd(4)]; | |
175 | component.getOsdList(); | |
176 | expect(component.osds.length).toBe(1); | |
177 | expect(osdService.getList).toHaveBeenCalledTimes(2); | |
178 | }); | |
179 | ||
180 | it('should have custom attribute "collectedStates"', () => { | |
181 | expectAttributeOnEveryOsd('collectedStates'); | |
182 | expect(component.osds[0].collectedStates).toEqual(['in', 'up']); | |
183 | }); | |
184 | ||
9f95a23c TL |
185 | it('should have "destroyed" state in "collectedStates"', () => { |
186 | osds[0].state.push('destroyed'); | |
187 | osds[0].up = 0; | |
188 | component.getOsdList(); | |
189 | ||
190 | expectAttributeOnEveryOsd('collectedStates'); | |
191 | expect(component.osds[0].collectedStates).toEqual(['in', 'destroyed']); | |
192 | }); | |
193 | ||
81eedcae TL |
194 | it('should have custom attribute "stats_history.out_bytes"', () => { |
195 | expectAttributeOnEveryOsd('stats_history.out_bytes'); | |
196 | expect(component.osds[0].stats_history.out_bytes).toEqual([1, 2]); | |
197 | }); | |
198 | ||
199 | it('should have custom attribute "stats_history.in_bytes"', () => { | |
200 | expectAttributeOnEveryOsd('stats_history.in_bytes'); | |
201 | expect(component.osds[0].stats_history.in_bytes).toEqual([3, 4]); | |
202 | }); | |
203 | ||
204 | it('should have custom attribute "stats.usage"', () => { | |
205 | expectAttributeOnEveryOsd('stats.usage'); | |
206 | expect(component.osds[0].stats.usage).toBe(1); | |
207 | expect(component.osds[1].stats.usage).toBe(0.5); | |
208 | expect(component.osds[2].stats.usage).toBe(3 / 9); | |
209 | }); | |
210 | ||
211 | it('should have custom attribute "cdIsBinary" to be true', () => { | |
212 | expectAttributeOnEveryOsd('cdIsBinary'); | |
213 | expect(component.osds[0].cdIsBinary).toBe(true); | |
214 | }); | |
215 | }); | |
216 | ||
9f95a23c TL |
217 | describe('show osd actions as defined', () => { |
218 | const getOsdActions = () => { | |
11fdf7f2 | 219 | fixture.detectChanges(); |
9f95a23c TL |
220 | return fixture.debugElement.query(By.css('#cluster-wide-actions')).componentInstance |
221 | .dropDownActions; | |
11fdf7f2 TL |
222 | }; |
223 | ||
9f95a23c TL |
224 | it('shows osd actions after osd-actions', () => { |
225 | fixture.detectChanges(); | |
226 | expect(fixture.debugElement.query(By.css('#cluster-wide-actions'))).toBe( | |
227 | fixture.debugElement.queryAll(By.directive(TableActionsComponent))[1] | |
11fdf7f2 | 228 | ); |
11fdf7f2 TL |
229 | }); |
230 | ||
9f95a23c TL |
231 | it('shows both osd actions', () => { |
232 | const osdActions = getOsdActions(); | |
233 | expect(osdActions).toEqual(component.clusterWideActions); | |
234 | expect(osdActions.length).toBe(3); | |
235 | }); | |
11fdf7f2 | 236 | |
9f95a23c TL |
237 | it('shows only "Flags" action', () => { |
238 | component.permissions.configOpt.read = false; | |
239 | const osdActions = getOsdActions(); | |
240 | expect(osdActions[0].name).toBe('Flags'); | |
241 | expect(osdActions.length).toBe(1); | |
242 | }); | |
243 | ||
244 | it('shows only "Recovery Priority" action', () => { | |
245 | component.permissions.osd.read = false; | |
246 | const osdActions = getOsdActions(); | |
247 | expect(osdActions[0].name).toBe('Recovery Priority'); | |
248 | expect(osdActions[1].name).toBe('PG scrub'); | |
249 | expect(osdActions.length).toBe(2); | |
250 | }); | |
251 | ||
252 | it('shows no osd actions', () => { | |
253 | component.permissions.configOpt.read = false; | |
254 | component.permissions.osd.read = false; | |
255 | const osdActions = getOsdActions(); | |
256 | expect(osdActions).toEqual([]); | |
257 | }); | |
258 | }); | |
259 | ||
260 | it('should test all TableActions combinations', () => { | |
261 | const permissionHelper: PermissionHelper = new PermissionHelper(component.permissions.osd); | |
262 | const tableActions: TableActionsComponent = permissionHelper.setPermissionsAndGetActions( | |
263 | component.tableActions | |
264 | ); | |
265 | ||
266 | expect(tableActions).toEqual({ | |
267 | 'create,update,delete': { | |
268 | actions: [ | |
269 | 'Create', | |
270 | 'Edit', | |
271 | 'Scrub', | |
272 | 'Deep Scrub', | |
273 | 'Reweight', | |
274 | 'Mark Out', | |
275 | 'Mark In', | |
276 | 'Mark Down', | |
277 | 'Mark Lost', | |
278 | 'Purge', | |
279 | 'Destroy', | |
280 | 'Delete' | |
281 | ], | |
282 | primary: { multiple: 'Scrub', executing: 'Edit', single: 'Edit', no: 'Create' } | |
283 | }, | |
284 | 'create,update': { | |
285 | actions: [ | |
286 | 'Create', | |
287 | 'Edit', | |
288 | 'Scrub', | |
289 | 'Deep Scrub', | |
290 | 'Reweight', | |
291 | 'Mark Out', | |
292 | 'Mark In', | |
293 | 'Mark Down' | |
294 | ], | |
295 | primary: { multiple: 'Scrub', executing: 'Edit', single: 'Edit', no: 'Create' } | |
296 | }, | |
297 | 'create,delete': { | |
298 | actions: ['Create', 'Mark Lost', 'Purge', 'Destroy', 'Delete'], | |
299 | primary: { | |
300 | multiple: 'Create', | |
301 | executing: 'Mark Lost', | |
302 | single: 'Mark Lost', | |
303 | no: 'Create' | |
304 | } | |
305 | }, | |
306 | create: { | |
307 | actions: ['Create'], | |
308 | primary: { multiple: 'Create', executing: 'Create', single: 'Create', no: 'Create' } | |
309 | }, | |
310 | 'update,delete': { | |
311 | actions: [ | |
312 | 'Edit', | |
313 | 'Scrub', | |
314 | 'Deep Scrub', | |
315 | 'Reweight', | |
316 | 'Mark Out', | |
317 | 'Mark In', | |
318 | 'Mark Down', | |
319 | 'Mark Lost', | |
320 | 'Purge', | |
321 | 'Destroy', | |
322 | 'Delete' | |
323 | ], | |
324 | primary: { multiple: 'Scrub', executing: 'Edit', single: 'Edit', no: 'Edit' } | |
325 | }, | |
326 | update: { | |
327 | actions: ['Edit', 'Scrub', 'Deep Scrub', 'Reweight', 'Mark Out', 'Mark In', 'Mark Down'], | |
328 | primary: { multiple: 'Scrub', executing: 'Edit', single: 'Edit', no: 'Edit' } | |
329 | }, | |
330 | delete: { | |
331 | actions: ['Mark Lost', 'Purge', 'Destroy', 'Delete'], | |
332 | primary: { | |
333 | multiple: 'Mark Lost', | |
334 | executing: 'Mark Lost', | |
335 | single: 'Mark Lost', | |
336 | no: 'Mark Lost' | |
337 | } | |
338 | }, | |
339 | 'no-permissions': { | |
340 | actions: [], | |
341 | primary: { multiple: '', executing: '', single: '', no: '' } | |
342 | } | |
11fdf7f2 TL |
343 | }); |
344 | }); | |
345 | ||
346 | describe('test table actions in submenu', () => { | |
9f95a23c TL |
347 | beforeEach(() => { |
348 | fixture.detectChanges(); | |
349 | }); | |
350 | ||
11fdf7f2 TL |
351 | beforeEach(fakeAsync(() => { |
352 | // The menu needs a click to render the dropdown! | |
353 | const dropDownToggle = fixture.debugElement.query(By.css('.dropdown-toggle')); | |
354 | dropDownToggle.triggerEventHandler('click', null); | |
355 | tick(); | |
356 | fixture.detectChanges(); | |
357 | })); | |
358 | ||
9f95a23c | 359 | it('has all menu entries disabled except create', () => { |
11fdf7f2 TL |
360 | const tableActionElement = fixture.debugElement.query(By.directive(TableActionsComponent)); |
361 | const toClassName = TestBed.get(TableActionsComponent).toClassName; | |
362 | const getActionClasses = (action: CdTableAction) => | |
9f95a23c | 363 | tableActionElement.query(By.css(`.${toClassName(action.name)} .dropdown-item`)).classes; |
11fdf7f2 TL |
364 | |
365 | component.tableActions.forEach((action) => { | |
9f95a23c TL |
366 | if (action.name === 'Create') { |
367 | return; | |
368 | } | |
11fdf7f2 TL |
369 | expect(getActionClasses(action).disabled).toBe(true); |
370 | }); | |
371 | }); | |
372 | }); | |
373 | ||
374 | describe('tests if all modals are opened correctly', () => { | |
375 | /** | |
376 | * Helper function to check if a function opens a modal | |
377 | * | |
378 | * @param modalClass - The expected class of the modal | |
379 | */ | |
9f95a23c | 380 | const expectOpensModal = (actionName: string, modalClass: any): void => { |
11fdf7f2 TL |
381 | openActionModal(actionName); |
382 | ||
383 | // @TODO: check why tsc is complaining when passing 'expectationFailOutput' param. | |
384 | expect(modalServiceShowSpy.calls.any()).toBeTruthy(); | |
385 | expect(modalServiceShowSpy.calls.first().args[0]).toBe(modalClass); | |
386 | ||
387 | modalServiceShowSpy.calls.reset(); | |
388 | }; | |
389 | ||
390 | it('opens the reweight modal', () => { | |
391 | expectOpensModal('Reweight', OsdReweightModalComponent); | |
392 | }); | |
393 | ||
9f95a23c TL |
394 | it('opens the form modal', () => { |
395 | expectOpensModal('Edit', FormModalComponent); | |
396 | }); | |
397 | ||
11fdf7f2 TL |
398 | it('opens all confirmation modals', () => { |
399 | const modalClass = ConfirmationModalComponent; | |
400 | expectOpensModal('Mark Out', modalClass); | |
401 | expectOpensModal('Mark In', modalClass); | |
402 | expectOpensModal('Mark Down', modalClass); | |
403 | }); | |
404 | ||
405 | it('opens all critical confirmation modals', () => { | |
406 | const modalClass = CriticalConfirmationModalComponent; | |
407 | mockSafeToDestroy(); | |
408 | expectOpensModal('Mark Lost', modalClass); | |
409 | expectOpensModal('Purge', modalClass); | |
410 | expectOpensModal('Destroy', modalClass); | |
9f95a23c TL |
411 | mockOrchestratorStatus(); |
412 | mockSafeToDelete(); | |
413 | expectOpensModal('Delete', modalClass); | |
11fdf7f2 TL |
414 | }); |
415 | }); | |
416 | ||
417 | describe('tests if the correct methods are called on confirmation', () => { | |
418 | const expectOsdServiceMethodCalled = ( | |
419 | actionName: string, | |
9f95a23c TL |
420 | osdServiceMethodName: |
421 | | 'markOut' | |
422 | | 'markIn' | |
423 | | 'markDown' | |
424 | | 'markLost' | |
425 | | 'purge' | |
426 | | 'destroy' | |
427 | | 'delete' | |
11fdf7f2 | 428 | ): void => { |
81eedcae | 429 | const osdServiceSpy = spyOn(osdService, osdServiceMethodName).and.callFake(() => EMPTY); |
11fdf7f2 TL |
430 | openActionModal(actionName); |
431 | const initialState = modalServiceShowSpy.calls.first().args[1].initialState; | |
432 | const submit = initialState.onSubmit || initialState.submitAction; | |
433 | submit.call(component); | |
434 | ||
435 | expect(osdServiceSpy.calls.count()).toBe(1); | |
436 | expect(osdServiceSpy.calls.first().args[0]).toBe(1); | |
437 | ||
438 | // Reset spies to be able to recreate them | |
439 | osdServiceSpy.calls.reset(); | |
440 | modalServiceShowSpy.calls.reset(); | |
441 | }; | |
442 | ||
443 | it('calls the corresponding service methods in confirmation modals', () => { | |
444 | expectOsdServiceMethodCalled('Mark Out', 'markOut'); | |
445 | expectOsdServiceMethodCalled('Mark In', 'markIn'); | |
446 | expectOsdServiceMethodCalled('Mark Down', 'markDown'); | |
447 | }); | |
448 | ||
449 | it('calls the corresponding service methods in critical confirmation modals', () => { | |
450 | mockSafeToDestroy(); | |
451 | expectOsdServiceMethodCalled('Mark Lost', 'markLost'); | |
452 | expectOsdServiceMethodCalled('Purge', 'purge'); | |
453 | expectOsdServiceMethodCalled('Destroy', 'destroy'); | |
9f95a23c TL |
454 | mockOrchestratorStatus(); |
455 | mockSafeToDelete(); | |
456 | expectOsdServiceMethodCalled('Delete', 'delete'); | |
11fdf7f2 TL |
457 | }); |
458 | }); | |
459 | }); |