]> git.apps.os.sepia.ceph.com Git - ceph.git/blob
10b8c09fabd45b13d7164b92125798695aafbf56
[ceph.git] /
1 import { HttpClientTestingModule } from '@angular/common/http/testing';
2 import { ComponentFixture, fakeAsync, TestBed, tick } from '@angular/core/testing';
3 import { BrowserAnimationsModule } from '@angular/platform-browser/animations';
4 import { RouterTestingModule } from '@angular/router/testing';
5
6 import { NgbNavModule } from '@ng-bootstrap/ng-bootstrap';
7 import { MockComponent } from 'ng-mocks';
8 import { ToastrModule } from 'ngx-toastr';
9 import { Subject, throwError as observableThrowError } from 'rxjs';
10
11 import { RbdService } from '~/app/shared/api/rbd.service';
12 import { ComponentsModule } from '~/app/shared/components/components.module';
13 import { CriticalConfirmationModalComponent } from '~/app/shared/components/critical-confirmation-modal/critical-confirmation-modal.component';
14 import { ActionLabelsI18n } from '~/app/shared/constants/app.constants';
15 import { DataTableModule } from '~/app/shared/datatable/datatable.module';
16 import { TableActionsComponent } from '~/app/shared/datatable/table-actions/table-actions.component';
17 import { CdTableSelection } from '~/app/shared/models/cd-table-selection';
18 import { ExecutingTask } from '~/app/shared/models/executing-task';
19 import { Permissions } from '~/app/shared/models/permissions';
20 import { PipesModule } from '~/app/shared/pipes/pipes.module';
21 import { AuthStorageService } from '~/app/shared/services/auth-storage.service';
22 import { NotificationService } from '~/app/shared/services/notification.service';
23 import { SummaryService } from '~/app/shared/services/summary.service';
24 import { TaskListService } from '~/app/shared/services/task-list.service';
25 import { configureTestBed, expectItemTasks, PermissionHelper } from '~/testing/unit-test-helper';
26 import { RbdSnapshotFormModalComponent } from '../rbd-snapshot-form/rbd-snapshot-form-modal.component';
27 import { RbdTabsComponent } from '../rbd-tabs/rbd-tabs.component';
28 import { RbdSnapshotActionsModel } from './rbd-snapshot-actions.model';
29 import { RbdSnapshotListComponent } from './rbd-snapshot-list.component';
30 import { RbdSnapshotModel } from './rbd-snapshot.model';
31 import { ModalCdsService } from '~/app/shared/services/modal-cds.service';
32 import {
33   BaseModal,
34   BaseModalService,
35   ModalModule,
36   ModalService,
37   PlaceholderModule,
38   PlaceholderService
39 } from 'carbon-components-angular';
40 import { NO_ERRORS_SCHEMA } from '@angular/core';
41 import { CoreModule } from '~/app/core/core.module';
42
43 describe('RbdSnapshotListComponent', () => {
44   let component: RbdSnapshotListComponent;
45   let fixture: ComponentFixture<RbdSnapshotListComponent>;
46   let summaryService: SummaryService;
47   let modalService: ModalCdsService;
48
49   const fakeAuthStorageService = {
50     isLoggedIn: () => {
51       return true;
52     },
53     getPermissions: () => {
54       return new Permissions({ 'rbd-image': ['read', 'update', 'create', 'delete'] });
55     }
56   };
57
58   configureTestBed(
59     {
60       declarations: [
61         RbdSnapshotListComponent,
62         RbdTabsComponent,
63         MockComponent(RbdSnapshotFormModalComponent),
64         BaseModal
65       ],
66       imports: [
67         BrowserAnimationsModule,
68         ComponentsModule,
69         DataTableModule,
70         HttpClientTestingModule,
71         PipesModule,
72         RouterTestingModule,
73         NgbNavModule,
74         ToastrModule.forRoot(),
75         ModalModule,
76         PlaceholderModule,
77         CoreModule
78       ],
79       providers: [
80         { provide: AuthStorageService, useValue: fakeAuthStorageService },
81         TaskListService,
82         ModalService,
83         PlaceholderService,
84         BaseModalService
85       ],
86       schemas: [NO_ERRORS_SCHEMA]
87     },
88     [CriticalConfirmationModalComponent]
89   );
90
91   beforeEach(() => {
92     fixture = TestBed.createComponent(RbdSnapshotListComponent);
93     component = fixture.componentInstance;
94
95     // Access the component's native element
96     const element = fixture.nativeElement;
97
98     // Dynamically create and append the cds-placeholder element
99     const cdsPlaceholder = document.createElement('cds-placeholder');
100     element.appendChild(cdsPlaceholder);
101
102     // Trigger change detection to update the view
103     fixture.detectChanges();
104     component.ngOnChanges();
105     summaryService = TestBed.inject(SummaryService);
106   });
107
108   it('should create', () => {
109     fixture.detectChanges();
110     expect(component).toBeTruthy();
111   });
112
113   describe('api delete request', () => {
114     let called: boolean;
115     let rbdService: RbdService;
116     let notificationService: NotificationService;
117     let authStorageService: AuthStorageService;
118
119     beforeEach(() => {
120       fixture.detectChanges();
121       const modalService = TestBed.inject(ModalCdsService);
122       const actionLabelsI18n = TestBed.inject(ActionLabelsI18n);
123       called = false;
124       rbdService = new RbdService(null, null);
125       notificationService = new NotificationService(null, null, null);
126       authStorageService = new AuthStorageService();
127       authStorageService.set('user', { 'rbd-image': ['create', 'read', 'update', 'delete'] });
128       component = new RbdSnapshotListComponent(
129         authStorageService,
130         null,
131         null,
132         rbdService,
133         null,
134         notificationService,
135         null,
136         null,
137         actionLabelsI18n,
138         null,
139         modalService
140       );
141       spyOn(rbdService, 'deleteSnapshot').and.returnValue(observableThrowError({ status: 500 }));
142       spyOn(notificationService, 'notifyTask').and.stub();
143       spyOn(modalService, 'stopLoadingSpinner').and.stub();
144     });
145
146     // @TODO: fix this later. fails with the new cds modal.
147     // disabling this for now.
148     it.skip('should call stopLoadingSpinner if the request fails', fakeAsync(() => {
149       // expect(container.querySelector('cds-placeholder')).not.toBeNull();
150       component.updateSelection(new CdTableSelection([{ name: 'someName' }]));
151       expect(called).toBe(false);
152       component.deleteSnapshotModal();
153       component.modalRef.snapshotForm = { value: { snapName: 'someName' } };
154       component.modalRef.submitAction();
155       tick(500);
156       spyOn(modalService, 'stopLoadingSpinner').and.callFake(() => {
157         called = true;
158       });
159       expect(called).toBe(true);
160     }));
161   });
162
163   describe('handling of executing tasks', () => {
164     let snapshots: RbdSnapshotModel[];
165
166     const addSnapshot = (name: string) => {
167       const model = new RbdSnapshotModel();
168       model.id = 1;
169       model.name = name;
170       snapshots.push(model);
171     };
172
173     const addTask = (task_name: string, snapshot_name: string) => {
174       const task = new ExecutingTask();
175       task.name = task_name;
176       task.metadata = {
177         image_spec: 'rbd/foo',
178         snapshot_name: snapshot_name
179       };
180       summaryService.addRunningTask(task);
181     };
182
183     const refresh = (data: any) => {
184       summaryService['summaryDataSource'].next(data);
185     };
186
187     beforeEach(() => {
188       fixture.detectChanges();
189       snapshots = [];
190       addSnapshot('a');
191       addSnapshot('b');
192       addSnapshot('c');
193       component.snapshots = snapshots;
194       component.poolName = 'rbd';
195       component.rbdName = 'foo';
196       refresh({ executing_tasks: [], finished_tasks: [] });
197       component.ngOnChanges();
198       fixture.detectChanges();
199     });
200
201     it('should gets all snapshots without tasks', () => {
202       expect(component.snapshots.length).toBe(3);
203       expect(component.snapshots.every((image) => !image.cdExecuting)).toBeTruthy();
204     });
205
206     it('should add a new image from a task', () => {
207       addTask('rbd/snap/create', 'd');
208       expect(component.snapshots.length).toBe(4);
209       expectItemTasks(component.snapshots[0], undefined);
210       expectItemTasks(component.snapshots[1], undefined);
211       expectItemTasks(component.snapshots[2], undefined);
212       expectItemTasks(component.snapshots[3], 'Creating');
213     });
214
215     it('should show when an existing image is being modified', () => {
216       addTask('rbd/snap/edit', 'a');
217       addTask('rbd/snap/delete', 'b');
218       addTask('rbd/snap/rollback', 'c');
219       expect(component.snapshots.length).toBe(3);
220       expectItemTasks(component.snapshots[0], 'Updating');
221       expectItemTasks(component.snapshots[1], 'Deleting');
222       expectItemTasks(component.snapshots[2], 'Rolling back');
223     });
224   });
225
226   // cds-modal opening fails in the unit tests. since e2e is already there, disabling this.
227   // @TODO: should be fixed later on
228   describe.skip('snapshot modal dialog', () => {
229     beforeEach(() => {
230       component.poolName = 'pool01';
231       component.rbdName = 'image01';
232       spyOn(TestBed.inject(ModalService), 'show').and.callFake(() => {
233         const ref: any = {};
234         ref.componentInstance = new RbdSnapshotFormModalComponent(
235           null,
236           null,
237           null,
238           null,
239           TestBed.inject(ActionLabelsI18n),
240           null,
241           component.poolName
242         );
243         ref.componentInstance.onSubmit = new Subject();
244         return ref;
245       });
246     });
247
248     it('should display old snapshot name', () => {
249       component.selection.selected = [{ name: 'oldname' }];
250       component.openEditSnapshotModal();
251       expect(component.modalRef.componentInstance.snapName).toBe('oldname');
252       expect(component.modalRef.componentInstance.editing).toBeTruthy();
253     });
254
255     it('should display suggested snapshot name', () => {
256       component.openCreateSnapshotModal();
257       expect(component.modalRef.componentInstance.snapName).toMatch(
258         RegExp(`^${component.rbdName}_[\\d-]+T[\\d.:]+[\\+-][\\d:]+$`)
259       );
260     });
261   });
262
263   it('should test all TableActions combinations', () => {
264     component.ngOnInit();
265     const permissionHelper: PermissionHelper = new PermissionHelper(component.permission);
266     const tableActions: TableActionsComponent = permissionHelper.setPermissionsAndGetActions(
267       component.tableActions
268     );
269
270     expect(tableActions).toEqual({
271       'create,update,delete': {
272         actions: [
273           'Create',
274           'Rename',
275           'Protect',
276           'Unprotect',
277           'Clone',
278           'Copy',
279           'Rollback',
280           'Delete'
281         ],
282         primary: {
283           multiple: 'Create',
284           executing: 'Create',
285           single: 'Create',
286           no: 'Create'
287         }
288       },
289       'create,update': {
290         actions: ['Create', 'Rename', 'Protect', 'Unprotect', 'Clone', 'Copy', 'Rollback'],
291         primary: {
292           multiple: 'Create',
293           executing: 'Create',
294           single: 'Create',
295           no: 'Create'
296         }
297       },
298       'create,delete': {
299         actions: ['Create', 'Clone', 'Copy', 'Delete'],
300         primary: {
301           multiple: 'Create',
302           executing: 'Create',
303           single: 'Create',
304           no: 'Create'
305         }
306       },
307       create: {
308         actions: ['Create', 'Clone', 'Copy'],
309         primary: {
310           multiple: 'Create',
311           executing: 'Create',
312           single: 'Create',
313           no: 'Create'
314         }
315       },
316       'update,delete': {
317         actions: ['Rename', 'Protect', 'Unprotect', 'Rollback', 'Delete'],
318         primary: {
319           multiple: '',
320           executing: '',
321           single: '',
322           no: ''
323         }
324       },
325       update: {
326         actions: ['Rename', 'Protect', 'Unprotect', 'Rollback'],
327         primary: {
328           multiple: '',
329           executing: '',
330           single: '',
331           no: ''
332         }
333       },
334       delete: {
335         actions: ['Delete'],
336         primary: {
337           multiple: 'Delete',
338           executing: 'Delete',
339           single: 'Delete',
340           no: 'Delete'
341         }
342       },
343       'no-permissions': {
344         actions: [],
345         primary: {
346           multiple: '',
347           executing: '',
348           single: '',
349           no: ''
350         }
351       }
352     });
353   });
354
355   describe('clone button disable state', () => {
356     let actions: RbdSnapshotActionsModel;
357
358     beforeEach(() => {
359       fixture.detectChanges();
360       const rbdService = TestBed.inject(RbdService);
361       const actionLabelsI18n = TestBed.inject(ActionLabelsI18n);
362       actions = new RbdSnapshotActionsModel(actionLabelsI18n, [], rbdService);
363     });
364
365     it('should be disabled with version 1 and protected false', () => {
366       const selection = new CdTableSelection([{ name: 'someName', is_protected: false }]);
367       const disableDesc = actions.getCloneDisableDesc(selection);
368       expect(disableDesc).toBe('Snapshot must be protected in order to clone.');
369     });
370
371     it.each([
372       [1, true],
373       [2, true],
374       [2, false]
375     ])('should be enabled with version %d and protected %s', (version, is_protected) => {
376       actions.cloneFormatVersion = version;
377       const selection = new CdTableSelection([{ name: 'someName', is_protected: is_protected }]);
378       const disableDesc = actions.getCloneDisableDesc(selection);
379       expect(disableDesc).toBe(false);
380     });
381   });
382
383   describe('protect button disable state', () => {
384     let actions: RbdSnapshotActionsModel;
385
386     beforeEach(() => {
387       fixture.detectChanges();
388       const rbdService = TestBed.inject(RbdService);
389       const actionLabelsI18n = TestBed.inject(ActionLabelsI18n);
390       actions = new RbdSnapshotActionsModel(actionLabelsI18n, [], rbdService);
391     });
392
393     it('should be disabled if layering not supported', () => {
394       const selection = new CdTableSelection([{ name: 'someName', is_protected: false }]);
395       const disableDesc = actions.getProtectDisableDesc(selection, ['deep-flatten', 'fast-diff']);
396       expect(disableDesc).toBe('The layering feature needs to be enabled on parent image');
397     });
398   });
399 });