]> git.proxmox.com Git - ceph.git/blob - ceph/src/pybind/mgr/dashboard/frontend/src/app/ceph/block/rbd-snapshot-list/rbd-snapshot-list.component.spec.ts
update sources to ceph Nautilus 14.2.1
[ceph.git] / ceph / src / pybind / mgr / dashboard / frontend / src / app / ceph / block / rbd-snapshot-list / rbd-snapshot-list.component.spec.ts
1 import { HttpClientTestingModule } from '@angular/common/http/testing';
2 import { ComponentFixture, fakeAsync, TestBed, tick } from '@angular/core/testing';
3 import { By } from '@angular/platform-browser';
4 import { RouterTestingModule } from '@angular/router/testing';
5
6 import { I18n } from '@ngx-translate/i18n-polyfill';
7 import { ToastModule } from 'ng2-toastr';
8 import { BsModalRef, BsModalService } from 'ngx-bootstrap/modal';
9 import { Subject, throwError as observableThrowError } from 'rxjs';
10
11 import {
12 configureTestBed,
13 i18nProviders,
14 PermissionHelper
15 } from '../../../../testing/unit-test-helper';
16 import { ApiModule } from '../../../shared/api/api.module';
17 import { RbdService } from '../../../shared/api/rbd.service';
18 import { ComponentsModule } from '../../../shared/components/components.module';
19 import { DataTableModule } from '../../../shared/datatable/datatable.module';
20 import { TableActionsComponent } from '../../../shared/datatable/table-actions/table-actions.component';
21 import { ExecutingTask } from '../../../shared/models/executing-task';
22 import { Permissions } from '../../../shared/models/permissions';
23 import { PipesModule } from '../../../shared/pipes/pipes.module';
24 import { AuthStorageService } from '../../../shared/services/auth-storage.service';
25 import { NotificationService } from '../../../shared/services/notification.service';
26 import { ServicesModule } from '../../../shared/services/services.module';
27 import { SummaryService } from '../../../shared/services/summary.service';
28 import { TaskListService } from '../../../shared/services/task-list.service';
29 import { RbdSnapshotListComponent } from './rbd-snapshot-list.component';
30 import { RbdSnapshotModel } from './rbd-snapshot.model';
31
32 describe('RbdSnapshotListComponent', () => {
33 let component: RbdSnapshotListComponent;
34 let fixture: ComponentFixture<RbdSnapshotListComponent>;
35 let summaryService: SummaryService;
36
37 const fakeAuthStorageService = {
38 isLoggedIn: () => {
39 return true;
40 },
41 getPermissions: () => {
42 return new Permissions({ 'rbd-image': ['read', 'update', 'create', 'delete'] });
43 }
44 };
45
46 configureTestBed({
47 declarations: [RbdSnapshotListComponent],
48 imports: [
49 DataTableModule,
50 ComponentsModule,
51 ToastModule.forRoot(),
52 ServicesModule,
53 ApiModule,
54 HttpClientTestingModule,
55 RouterTestingModule,
56 PipesModule
57 ],
58 providers: [
59 { provide: AuthStorageService, useValue: fakeAuthStorageService },
60 TaskListService,
61 i18nProviders
62 ]
63 });
64
65 beforeEach(() => {
66 fixture = TestBed.createComponent(RbdSnapshotListComponent);
67 component = fixture.componentInstance;
68 summaryService = TestBed.get(SummaryService);
69 });
70
71 it('should create', () => {
72 fixture.detectChanges();
73 expect(component).toBeTruthy();
74 });
75
76 describe('api delete request', () => {
77 let called;
78 let rbdService: RbdService;
79 let notificationService: NotificationService;
80 let authStorageService: AuthStorageService;
81
82 beforeEach(() => {
83 fixture.detectChanges();
84 const i18n = TestBed.get(I18n);
85 called = false;
86 rbdService = new RbdService(null, null);
87 notificationService = new NotificationService(null, null, null);
88 authStorageService = new AuthStorageService();
89 authStorageService.set('user', '', { 'rbd-image': ['create', 'read', 'update', 'delete'] });
90 component = new RbdSnapshotListComponent(
91 authStorageService,
92 null,
93 null,
94 null,
95 rbdService,
96 null,
97 notificationService,
98 null,
99 null,
100 i18n
101 );
102 spyOn(rbdService, 'deleteSnapshot').and.returnValue(observableThrowError({ status: 500 }));
103 spyOn(notificationService, 'notifyTask').and.stub();
104 component.modalRef = new BsModalRef();
105 component.modalRef.content = {
106 stopLoadingSpinner: () => (called = true)
107 };
108 });
109
110 it('should call stopLoadingSpinner if the request fails', <any>fakeAsync(() => {
111 expect(called).toBe(false);
112 component._asyncTask('deleteSnapshot', 'rbd/snap/delete', 'someName');
113 tick(500);
114 expect(called).toBe(true);
115 }));
116 });
117
118 describe('handling of executing tasks', () => {
119 let snapshots: RbdSnapshotModel[];
120
121 const addSnapshot = (name) => {
122 const model = new RbdSnapshotModel();
123 model.id = 1;
124 model.name = name;
125 snapshots.push(model);
126 };
127
128 const addTask = (task_name: string, snapshot_name: string) => {
129 const task = new ExecutingTask();
130 task.name = task_name;
131 task.metadata = {
132 pool_name: 'rbd',
133 image_name: 'foo',
134 snapshot_name: snapshot_name
135 };
136 summaryService.addRunningTask(task);
137 };
138
139 const expectImageTasks = (snapshot: RbdSnapshotModel, executing: string) => {
140 expect(snapshot.cdExecuting).toEqual(executing);
141 };
142
143 const refresh = (data) => {
144 summaryService['summaryDataSource'].next(data);
145 };
146
147 beforeEach(() => {
148 fixture.detectChanges();
149 snapshots = [];
150 addSnapshot('a');
151 addSnapshot('b');
152 addSnapshot('c');
153 component.snapshots = snapshots;
154 component.poolName = 'rbd';
155 component.rbdName = 'foo';
156 refresh({ executing_tasks: [], finished_tasks: [] });
157 component.ngOnChanges();
158 fixture.detectChanges();
159 });
160
161 it('should gets all snapshots without tasks', () => {
162 expect(component.snapshots.length).toBe(3);
163 expect(component.snapshots.every((image) => !image.cdExecuting)).toBeTruthy();
164 });
165
166 it('should add a new image from a task', () => {
167 addTask('rbd/snap/create', 'd');
168 expect(component.snapshots.length).toBe(4);
169 expectImageTasks(component.snapshots[0], undefined);
170 expectImageTasks(component.snapshots[1], undefined);
171 expectImageTasks(component.snapshots[2], undefined);
172 expectImageTasks(component.snapshots[3], 'Creating');
173 });
174
175 it('should show when an existing image is being modified', () => {
176 addTask('rbd/snap/edit', 'a');
177 addTask('rbd/snap/delete', 'b');
178 addTask('rbd/snap/rollback', 'c');
179 expect(component.snapshots.length).toBe(3);
180 expectImageTasks(component.snapshots[0], 'Updating');
181 expectImageTasks(component.snapshots[1], 'Deleting');
182 expectImageTasks(component.snapshots[2], 'Rolling back');
183 });
184 });
185
186 describe('snapshot modal dialog', () => {
187 beforeEach(() => {
188 component.poolName = 'pool01';
189 component.rbdName = 'image01';
190 spyOn(TestBed.get(BsModalService), 'show').and.callFake((content) => {
191 const ref = new BsModalRef();
192 ref.content = new content();
193 ref.content.onSubmit = new Subject();
194 return ref;
195 });
196 });
197
198 it('should display old snapshot name', () => {
199 component.selection.selected = [{ name: 'oldname' }];
200 component.selection.update();
201 component.openEditSnapshotModal();
202 expect(component.modalRef.content.snapName).toBe('oldname');
203 expect(component.modalRef.content.editing).toBeTruthy();
204 });
205
206 it('should display suggested snapshot name', () => {
207 component.openCreateSnapshotModal();
208 expect(component.modalRef.content.snapName).toMatch(
209 RegExp(`^${component.rbdName}-\\d+T\\d+Z\$`)
210 );
211 });
212 });
213
214 describe('show action buttons and drop down actions depending on permissions', () => {
215 let tableActions: TableActionsComponent;
216 let scenario: { fn; empty; single };
217 let permissionHelper: PermissionHelper;
218
219 const getTableActionComponent = (): TableActionsComponent => {
220 fixture.detectChanges();
221 return fixture.debugElement.query(By.directive(TableActionsComponent)).componentInstance;
222 };
223
224 beforeEach(() => {
225 permissionHelper = new PermissionHelper(component.permission, () =>
226 getTableActionComponent()
227 );
228 scenario = {
229 fn: () => tableActions.getCurrentButton().name,
230 single: 'Rename',
231 empty: 'Create'
232 };
233 });
234
235 describe('with all', () => {
236 beforeEach(() => {
237 tableActions = permissionHelper.setPermissionsAndGetActions(1, 1, 1);
238 });
239
240 it(`shows 'Rename' for single selection else 'Create' as main action`, () =>
241 permissionHelper.testScenarios(scenario));
242
243 it('shows all actions', () => {
244 expect(tableActions.tableActions.length).toBe(8);
245 expect(tableActions.tableActions).toEqual(component.tableActions);
246 });
247 });
248
249 describe('with read, create and update', () => {
250 beforeEach(() => {
251 tableActions = permissionHelper.setPermissionsAndGetActions(1, 1, 0);
252 });
253
254 it(`shows 'Rename' for single selection else 'Create' as main action`, () =>
255 permissionHelper.testScenarios(scenario));
256
257 it(`shows all actions except for 'Delete'`, () => {
258 expect(tableActions.tableActions.length).toBe(7);
259 component.tableActions.pop();
260 expect(tableActions.tableActions).toEqual(component.tableActions);
261 });
262 });
263
264 describe('with read, create and delete', () => {
265 beforeEach(() => {
266 tableActions = permissionHelper.setPermissionsAndGetActions(1, 0, 1);
267 });
268
269 it(`shows 'Clone' for single selection else 'Create' as main action`, () => {
270 scenario.single = 'Clone';
271 permissionHelper.testScenarios(scenario);
272 });
273
274 it(`shows 'Create', 'Clone', 'Copy' and 'Delete' action`, () => {
275 expect(tableActions.tableActions.length).toBe(4);
276 expect(tableActions.tableActions).toEqual([
277 component.tableActions[0],
278 component.tableActions[4],
279 component.tableActions[5],
280 component.tableActions[7]
281 ]);
282 });
283 });
284
285 describe('with read, edit and delete', () => {
286 beforeEach(() => {
287 tableActions = permissionHelper.setPermissionsAndGetActions(0, 1, 1);
288 });
289
290 it(`shows always 'Rename' as main action`, () => {
291 scenario.empty = 'Rename';
292 permissionHelper.testScenarios(scenario);
293 });
294
295 it(`shows 'Rename', 'Protect', 'Unprotect', 'Rollback' and 'Delete' action`, () => {
296 expect(tableActions.tableActions.length).toBe(5);
297 expect(tableActions.tableActions).toEqual([
298 component.tableActions[1],
299 component.tableActions[2],
300 component.tableActions[3],
301 component.tableActions[6],
302 component.tableActions[7]
303 ]);
304 });
305 });
306
307 describe('with read and create', () => {
308 beforeEach(() => {
309 tableActions = permissionHelper.setPermissionsAndGetActions(1, 0, 0);
310 });
311
312 it(`shows 'Clone' for single selection else 'Create' as main action`, () => {
313 scenario.single = 'Clone';
314 permissionHelper.testScenarios(scenario);
315 });
316
317 it(`shows 'Create', 'Clone' and 'Copy' actions`, () => {
318 expect(tableActions.tableActions.length).toBe(3);
319 expect(tableActions.tableActions).toEqual([
320 component.tableActions[0],
321 component.tableActions[4],
322 component.tableActions[5]
323 ]);
324 });
325 });
326
327 describe('with read and edit', () => {
328 beforeEach(() => {
329 tableActions = permissionHelper.setPermissionsAndGetActions(0, 1, 0);
330 });
331
332 it(`shows always 'Rename' as main action`, () => {
333 scenario.empty = 'Rename';
334 permissionHelper.testScenarios(scenario);
335 });
336
337 it(`shows 'Rename', 'Protect', 'Unprotect' and 'Rollback' actions`, () => {
338 expect(tableActions.tableActions.length).toBe(4);
339 expect(tableActions.tableActions).toEqual([
340 component.tableActions[1],
341 component.tableActions[2],
342 component.tableActions[3],
343 component.tableActions[6]
344 ]);
345 });
346 });
347
348 describe('with read and delete', () => {
349 beforeEach(() => {
350 tableActions = permissionHelper.setPermissionsAndGetActions(0, 0, 1);
351 });
352
353 it(`shows always 'Delete' as main action`, () => {
354 scenario.single = 'Delete';
355 scenario.empty = 'Delete';
356 permissionHelper.testScenarios(scenario);
357 });
358
359 it(`shows only 'Delete' action`, () => {
360 expect(tableActions.tableActions.length).toBe(1);
361 expect(tableActions.tableActions).toEqual([component.tableActions[7]]);
362 });
363 });
364
365 describe('with only read', () => {
366 beforeEach(() => {
367 tableActions = permissionHelper.setPermissionsAndGetActions(0, 0, 0);
368 });
369
370 it('shows no main action', () => {
371 permissionHelper.testScenarios({
372 fn: () => tableActions.getCurrentButton(),
373 single: undefined,
374 empty: undefined
375 });
376 });
377
378 it('shows no actions', () => {
379 expect(tableActions.tableActions.length).toBe(0);
380 expect(tableActions.tableActions).toEqual([]);
381 });
382 });
383
384 describe('test unprotected and protected action cases', () => {
385 beforeEach(() => {
386 tableActions = permissionHelper.setPermissionsAndGetActions(0, 1, 0);
387 });
388
389 it(`shows none of them if nothing is selected`, () => {
390 permissionHelper.setSelection([]);
391 fixture.detectChanges();
392 expect(tableActions.dropDownActions).toEqual([
393 component.tableActions[1],
394 component.tableActions[6]
395 ]);
396 });
397
398 it(`shows 'Protect' of them if nothing is selected`, () => {
399 permissionHelper.setSelection([{ is_protected: false }]);
400 fixture.detectChanges();
401 expect(tableActions.dropDownActions).toEqual([
402 component.tableActions[1],
403 component.tableActions[2],
404 component.tableActions[6]
405 ]);
406 });
407
408 it(`shows 'Unprotect' of them if nothing is selected`, () => {
409 permissionHelper.setSelection([{ is_protected: true }]);
410 fixture.detectChanges();
411 expect(tableActions.dropDownActions).toEqual([
412 component.tableActions[1],
413 component.tableActions[3],
414 component.tableActions[6]
415 ]);
416 });
417 });
418 });
419 });