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