]> git.apps.os.sepia.ceph.com Git - ceph.git/blob
d2fb9b067e0122fcb900d643f940626e384b495c
[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 { NgbModalModule, 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 { ModalService } from '~/app/shared/services/modal.service';
23 import { NotificationService } from '~/app/shared/services/notification.service';
24 import { SummaryService } from '~/app/shared/services/summary.service';
25 import { TaskListService } from '~/app/shared/services/task-list.service';
26 import { configureTestBed, expectItemTasks, PermissionHelper } from '~/testing/unit-test-helper';
27 import { RbdSnapshotFormModalComponent } from '../rbd-snapshot-form/rbd-snapshot-form-modal.component';
28 import { RbdTabsComponent } from '../rbd-tabs/rbd-tabs.component';
29 import { RbdSnapshotActionsModel } from './rbd-snapshot-actions.model';
30 import { RbdSnapshotListComponent } from './rbd-snapshot-list.component';
31 import { RbdSnapshotModel } from './rbd-snapshot.model';
32
33 describe('RbdSnapshotListComponent', () => {
34   let component: RbdSnapshotListComponent;
35   let fixture: ComponentFixture<RbdSnapshotListComponent>;
36   let summaryService: SummaryService;
37
38   const fakeAuthStorageService = {
39     isLoggedIn: () => {
40       return true;
41     },
42     getPermissions: () => {
43       return new Permissions({ 'rbd-image': ['read', 'update', 'create', 'delete'] });
44     }
45   };
46
47   configureTestBed(
48     {
49       declarations: [
50         RbdSnapshotListComponent,
51         RbdTabsComponent,
52         MockComponent(RbdSnapshotFormModalComponent)
53       ],
54       imports: [
55         BrowserAnimationsModule,
56         ComponentsModule,
57         DataTableModule,
58         HttpClientTestingModule,
59         PipesModule,
60         RouterTestingModule,
61         NgbNavModule,
62         ToastrModule.forRoot(),
63         NgbModalModule
64       ],
65       providers: [
66         { provide: AuthStorageService, useValue: fakeAuthStorageService },
67         TaskListService
68       ]
69     },
70     [CriticalConfirmationModalComponent]
71   );
72
73   beforeEach(() => {
74     fixture = TestBed.createComponent(RbdSnapshotListComponent);
75     component = fixture.componentInstance;
76     component.ngOnChanges();
77     summaryService = TestBed.inject(SummaryService);
78   });
79
80   it('should create', () => {
81     fixture.detectChanges();
82     expect(component).toBeTruthy();
83   });
84
85   describe('api delete request', () => {
86     let called: boolean;
87     let rbdService: RbdService;
88     let notificationService: NotificationService;
89     let authStorageService: AuthStorageService;
90
91     beforeEach(() => {
92       fixture.detectChanges();
93       const modalService = TestBed.inject(ModalService);
94       const actionLabelsI18n = TestBed.inject(ActionLabelsI18n);
95       called = false;
96       rbdService = new RbdService(null, null);
97       notificationService = new NotificationService(null, null, null);
98       authStorageService = new AuthStorageService();
99       authStorageService.set('user', { 'rbd-image': ['create', 'read', 'update', 'delete'] });
100       component = new RbdSnapshotListComponent(
101         authStorageService,
102         modalService,
103         null,
104         null,
105         rbdService,
106         null,
107         notificationService,
108         null,
109         null,
110         actionLabelsI18n,
111         null
112       );
113       spyOn(rbdService, 'deleteSnapshot').and.returnValue(observableThrowError({ status: 500 }));
114       spyOn(notificationService, 'notifyTask').and.stub();
115     });
116
117     it('should call stopLoadingSpinner if the request fails', fakeAsync(() => {
118       component.updateSelection(new CdTableSelection([{ name: 'someName' }]));
119       expect(called).toBe(false);
120       component.deleteSnapshotModal();
121       spyOn(component.modalRef.componentInstance, 'stopLoadingSpinner').and.callFake(() => {
122         called = true;
123       });
124       component.modalRef.componentInstance.submitAction();
125       tick(500);
126       expect(called).toBe(true);
127     }));
128   });
129
130   describe('handling of executing tasks', () => {
131     let snapshots: RbdSnapshotModel[];
132
133     const addSnapshot = (name: string) => {
134       const model = new RbdSnapshotModel();
135       model.id = 1;
136       model.name = name;
137       snapshots.push(model);
138     };
139
140     const addTask = (task_name: string, snapshot_name: string) => {
141       const task = new ExecutingTask();
142       task.name = task_name;
143       task.metadata = {
144         image_spec: 'rbd/foo',
145         snapshot_name: snapshot_name
146       };
147       summaryService.addRunningTask(task);
148     };
149
150     const refresh = (data: any) => {
151       summaryService['summaryDataSource'].next(data);
152     };
153
154     beforeEach(() => {
155       fixture.detectChanges();
156       snapshots = [];
157       addSnapshot('a');
158       addSnapshot('b');
159       addSnapshot('c');
160       component.snapshots = snapshots;
161       component.poolName = 'rbd';
162       component.rbdName = 'foo';
163       refresh({ executing_tasks: [], finished_tasks: [] });
164       component.ngOnChanges();
165       fixture.detectChanges();
166     });
167
168     it('should gets all snapshots without tasks', () => {
169       expect(component.snapshots.length).toBe(3);
170       expect(component.snapshots.every((image) => !image.cdExecuting)).toBeTruthy();
171     });
172
173     it('should add a new image from a task', () => {
174       addTask('rbd/snap/create', 'd');
175       expect(component.snapshots.length).toBe(4);
176       expectItemTasks(component.snapshots[0], undefined);
177       expectItemTasks(component.snapshots[1], undefined);
178       expectItemTasks(component.snapshots[2], undefined);
179       expectItemTasks(component.snapshots[3], 'Creating');
180     });
181
182     it('should show when an existing image is being modified', () => {
183       addTask('rbd/snap/edit', 'a');
184       addTask('rbd/snap/delete', 'b');
185       addTask('rbd/snap/rollback', 'c');
186       expect(component.snapshots.length).toBe(3);
187       expectItemTasks(component.snapshots[0], 'Updating');
188       expectItemTasks(component.snapshots[1], 'Deleting');
189       expectItemTasks(component.snapshots[2], 'Rolling back');
190     });
191   });
192
193   describe('snapshot modal dialog', () => {
194     beforeEach(() => {
195       component.poolName = 'pool01';
196       component.rbdName = 'image01';
197       spyOn(TestBed.inject(ModalService), 'show').and.callFake(() => {
198         const ref: any = {};
199         ref.componentInstance = new RbdSnapshotFormModalComponent(
200           null,
201           null,
202           null,
203           null,
204           TestBed.inject(ActionLabelsI18n),
205           null
206         );
207         ref.componentInstance.onSubmit = new Subject();
208         return ref;
209       });
210     });
211
212     it('should display old snapshot name', () => {
213       component.selection.selected = [{ name: 'oldname' }];
214       component.openEditSnapshotModal();
215       expect(component.modalRef.componentInstance.snapName).toBe('oldname');
216       expect(component.modalRef.componentInstance.editing).toBeTruthy();
217     });
218
219     it('should display suggested snapshot name', () => {
220       component.openCreateSnapshotModal();
221       expect(component.modalRef.componentInstance.snapName).toMatch(
222         RegExp(`^${component.rbdName}_[\\d-]+T[\\d.:]+[\\+-][\\d:]+$`)
223       );
224     });
225   });
226
227   it('should test all TableActions combinations', () => {
228     component.ngOnInit();
229     const permissionHelper: PermissionHelper = new PermissionHelper(component.permission);
230     const tableActions: TableActionsComponent = permissionHelper.setPermissionsAndGetActions(
231       component.tableActions
232     );
233
234     expect(tableActions).toEqual({
235       'create,update,delete': {
236         actions: [
237           'Create',
238           'Rename',
239           'Protect',
240           'Unprotect',
241           'Clone',
242           'Copy',
243           'Rollback',
244           'Delete'
245         ],
246         primary: { multiple: 'Create', executing: 'Rename', single: 'Rename', no: 'Create' }
247       },
248       'create,update': {
249         actions: ['Create', 'Rename', 'Protect', 'Unprotect', 'Clone', 'Copy', 'Rollback'],
250         primary: { multiple: 'Create', executing: 'Rename', single: 'Rename', no: 'Create' }
251       },
252       'create,delete': {
253         actions: ['Create', 'Clone', 'Copy', 'Delete'],
254         primary: { multiple: 'Create', executing: 'Clone', single: 'Clone', no: 'Create' }
255       },
256       create: {
257         actions: ['Create', 'Clone', 'Copy'],
258         primary: { multiple: 'Create', executing: 'Clone', single: 'Clone', no: 'Create' }
259       },
260       'update,delete': {
261         actions: ['Rename', 'Protect', 'Unprotect', 'Rollback', 'Delete'],
262         primary: { multiple: 'Rename', executing: 'Rename', single: 'Rename', no: 'Rename' }
263       },
264       update: {
265         actions: ['Rename', 'Protect', 'Unprotect', 'Rollback'],
266         primary: { multiple: 'Rename', executing: 'Rename', single: 'Rename', no: 'Rename' }
267       },
268       delete: {
269         actions: ['Delete'],
270         primary: { multiple: 'Delete', executing: 'Delete', single: 'Delete', no: 'Delete' }
271       },
272       'no-permissions': {
273         actions: [],
274         primary: { multiple: '', executing: '', single: '', no: '' }
275       }
276     });
277   });
278
279   describe('clone button disable state', () => {
280     let actions: RbdSnapshotActionsModel;
281
282     beforeEach(() => {
283       fixture.detectChanges();
284       const rbdService = TestBed.inject(RbdService);
285       const actionLabelsI18n = TestBed.inject(ActionLabelsI18n);
286       actions = new RbdSnapshotActionsModel(actionLabelsI18n, [], rbdService);
287     });
288
289     it('should be disabled with version 1 and protected false', () => {
290       const selection = new CdTableSelection([{ name: 'someName', is_protected: false }]);
291       const disableDesc = actions.getDisableDesc(selection, ['layering']);
292       expect(disableDesc).toBe('Snapshot must be protected in order to clone.');
293     });
294
295     it.each([
296       [1, true],
297       [2, true],
298       [2, false]
299     ])('should be enabled with version %d and protected %s', (version, is_protected) => {
300       actions.cloneFormatVersion = version;
301       const selection = new CdTableSelection([{ name: 'someName', is_protected: is_protected }]);
302       const disableDesc = actions.getDisableDesc(selection, ['layering']);
303       expect(disableDesc).toBe(false);
304     });
305   });
306
307   describe('protect button disable state', () => {
308     let actions: RbdSnapshotActionsModel;
309
310     beforeEach(() => {
311       fixture.detectChanges();
312       const rbdService = TestBed.inject(RbdService);
313       const actionLabelsI18n = TestBed.inject(ActionLabelsI18n);
314       actions = new RbdSnapshotActionsModel(actionLabelsI18n, [], rbdService);
315     });
316
317     it('should be disabled if layering not supported', () => {
318       const selection = new CdTableSelection([{ name: 'someName', is_protected: false }]);
319       const disableDesc = actions.getDisableDesc(selection, ['deep-flatten', 'fast-diff']);
320       expect(disableDesc).toBe('The layering feature needs to be enabled on parent image');
321     });
322   });
323 });