]> git.proxmox.com Git - ceph.git/blob - ceph/src/pybind/mgr/dashboard/frontend/src/app/ceph/block/rbd-list/rbd-list.component.spec.ts
import ceph pacific 16.2.5
[ceph.git] / ceph / src / pybind / mgr / dashboard / frontend / src / app / ceph / block / rbd-list / rbd-list.component.spec.ts
1 import { HttpClientTestingModule } from '@angular/common/http/testing';
2 import { ComponentFixture, TestBed } from '@angular/core/testing';
3 import { BrowserAnimationsModule } from '@angular/platform-browser/animations';
4 import { RouterTestingModule } from '@angular/router/testing';
5
6 import { NgbNavModule, NgbTooltipModule } from '@ng-bootstrap/ng-bootstrap';
7 import { ToastrModule } from 'ngx-toastr';
8 import { BehaviorSubject, of } from 'rxjs';
9
10 import { RbdService } from '~/app/shared/api/rbd.service';
11 import { TableStatusViewCache } from '~/app/shared/classes/table-status-view-cache';
12 import { TableActionsComponent } from '~/app/shared/datatable/table-actions/table-actions.component';
13 import { ViewCacheStatus } from '~/app/shared/enum/view-cache-status.enum';
14 import { ExecutingTask } from '~/app/shared/models/executing-task';
15 import { SummaryService } from '~/app/shared/services/summary.service';
16 import { TaskListService } from '~/app/shared/services/task-list.service';
17 import { SharedModule } from '~/app/shared/shared.module';
18 import { configureTestBed, expectItemTasks, PermissionHelper } from '~/testing/unit-test-helper';
19 import { RbdConfigurationListComponent } from '../rbd-configuration-list/rbd-configuration-list.component';
20 import { RbdDetailsComponent } from '../rbd-details/rbd-details.component';
21 import { RbdSnapshotListComponent } from '../rbd-snapshot-list/rbd-snapshot-list.component';
22 import { RbdTabsComponent } from '../rbd-tabs/rbd-tabs.component';
23 import { RbdListComponent } from './rbd-list.component';
24 import { RbdModel } from './rbd-model';
25
26 describe('RbdListComponent', () => {
27 let fixture: ComponentFixture<RbdListComponent>;
28 let component: RbdListComponent;
29 let summaryService: SummaryService;
30 let rbdService: RbdService;
31
32 const refresh = (data: any) => {
33 summaryService['summaryDataSource'].next(data);
34 };
35
36 configureTestBed({
37 imports: [
38 BrowserAnimationsModule,
39 SharedModule,
40 NgbNavModule,
41 NgbTooltipModule,
42 ToastrModule.forRoot(),
43 RouterTestingModule,
44 HttpClientTestingModule
45 ],
46 declarations: [
47 RbdListComponent,
48 RbdDetailsComponent,
49 RbdSnapshotListComponent,
50 RbdConfigurationListComponent,
51 RbdTabsComponent
52 ],
53 providers: [TaskListService]
54 });
55
56 beforeEach(() => {
57 fixture = TestBed.createComponent(RbdListComponent);
58 component = fixture.componentInstance;
59 summaryService = TestBed.inject(SummaryService);
60 rbdService = TestBed.inject(RbdService);
61
62 // this is needed because summaryService isn't being reset after each test.
63 summaryService['summaryDataSource'] = new BehaviorSubject(null);
64 summaryService['summaryData$'] = summaryService['summaryDataSource'].asObservable();
65 });
66
67 it('should create', () => {
68 expect(component).toBeTruthy();
69 });
70
71 describe('after ngOnInit', () => {
72 beforeEach(() => {
73 fixture.detectChanges();
74 spyOn(rbdService, 'list').and.callThrough();
75 });
76
77 it('should load images on init', () => {
78 refresh({});
79 expect(rbdService.list).toHaveBeenCalled();
80 });
81
82 it('should not load images on init because no data', () => {
83 refresh(undefined);
84 expect(rbdService.list).not.toHaveBeenCalled();
85 });
86
87 it('should call error function on init when summary service fails', () => {
88 spyOn(component.table, 'reset');
89 summaryService['summaryDataSource'].error(undefined);
90 expect(component.table.reset).toHaveBeenCalled();
91 expect(component.tableStatus).toEqual(
92 new TableStatusViewCache(ViewCacheStatus.ValueException)
93 );
94 });
95 });
96
97 describe('handling of deletion', () => {
98 beforeEach(() => {
99 fixture.detectChanges();
100 });
101
102 it('should check if there are no snapshots', () => {
103 component.selection.add({
104 id: '-1',
105 name: 'rbd1',
106 pool_name: 'rbd'
107 });
108 expect(component.hasSnapshots()).toBeFalsy();
109 });
110
111 it('should check if there are snapshots', () => {
112 component.selection.add({
113 id: '-1',
114 name: 'rbd1',
115 pool_name: 'rbd',
116 snapshots: [{}, {}]
117 });
118 expect(component.hasSnapshots()).toBeTruthy();
119 });
120
121 it('should get delete disable description', () => {
122 component.selection.add({
123 id: '-1',
124 name: 'rbd1',
125 pool_name: 'rbd',
126 snapshots: [
127 {
128 children: [{}]
129 }
130 ]
131 });
132 expect(component.getDeleteDisableDesc(component.selection)).toBe(
133 'This RBD has cloned snapshots. Please delete related RBDs before deleting this RBD.'
134 );
135 });
136
137 it('should list all protected snapshots', () => {
138 component.selection.add({
139 id: '-1',
140 name: 'rbd1',
141 pool_name: 'rbd',
142 snapshots: [
143 {
144 name: 'snap1',
145 is_protected: false
146 },
147 {
148 name: 'snap2',
149 is_protected: true
150 }
151 ]
152 });
153
154 expect(component.listProtectedSnapshots()).toEqual(['snap2']);
155 });
156 });
157
158 describe('handling of executing tasks', () => {
159 let images: RbdModel[];
160
161 const addImage = (name: string) => {
162 const model = new RbdModel();
163 model.id = '-1';
164 model.name = name;
165 model.pool_name = 'rbd';
166 images.push(model);
167 };
168
169 const addTask = (name: string, image_name: string) => {
170 const task = new ExecutingTask();
171 task.name = name;
172 switch (task.name) {
173 case 'rbd/copy':
174 task.metadata = {
175 dest_pool_name: 'rbd',
176 dest_namespace: null,
177 dest_image_name: 'd'
178 };
179 break;
180 case 'rbd/clone':
181 task.metadata = {
182 child_pool_name: 'rbd',
183 child_namespace: null,
184 child_image_name: 'd'
185 };
186 break;
187 case 'rbd/create':
188 task.metadata = {
189 pool_name: 'rbd',
190 namespace: null,
191 image_name: image_name
192 };
193 break;
194 default:
195 task.metadata = {
196 image_spec: `rbd/${image_name}`
197 };
198 break;
199 }
200 summaryService.addRunningTask(task);
201 };
202
203 beforeEach(() => {
204 images = [];
205 addImage('a');
206 addImage('b');
207 addImage('c');
208 component.images = images;
209 refresh({ executing_tasks: [], finished_tasks: [] });
210 spyOn(rbdService, 'list').and.callFake(() =>
211 of([{ pool_name: 'rbd', status: 1, value: images }])
212 );
213 fixture.detectChanges();
214 });
215
216 it('should gets all images without tasks', () => {
217 expect(component.images.length).toBe(3);
218 expect(component.images.every((image: any) => !image.cdExecuting)).toBeTruthy();
219 });
220
221 it('should add a new image from a task', () => {
222 addTask('rbd/create', 'd');
223 expect(component.images.length).toBe(4);
224 expectItemTasks(component.images[0], undefined);
225 expectItemTasks(component.images[1], undefined);
226 expectItemTasks(component.images[2], undefined);
227 expectItemTasks(component.images[3], 'Creating');
228 });
229
230 it('should show when a image is being cloned', () => {
231 addTask('rbd/clone', 'd');
232 expect(component.images.length).toBe(4);
233 expectItemTasks(component.images[0], undefined);
234 expectItemTasks(component.images[1], undefined);
235 expectItemTasks(component.images[2], undefined);
236 expectItemTasks(component.images[3], 'Cloning');
237 });
238
239 it('should show when a image is being copied', () => {
240 addTask('rbd/copy', 'd');
241 expect(component.images.length).toBe(4);
242 expectItemTasks(component.images[0], undefined);
243 expectItemTasks(component.images[1], undefined);
244 expectItemTasks(component.images[2], undefined);
245 expectItemTasks(component.images[3], 'Copying');
246 });
247
248 it('should show when an existing image is being modified', () => {
249 addTask('rbd/edit', 'a');
250 addTask('rbd/delete', 'b');
251 addTask('rbd/flatten', 'c');
252 expect(component.images.length).toBe(3);
253 expectItemTasks(component.images[0], 'Updating');
254 expectItemTasks(component.images[1], 'Deleting');
255 expectItemTasks(component.images[2], 'Flattening');
256 });
257 });
258
259 it('should test all TableActions combinations', () => {
260 const permissionHelper: PermissionHelper = new PermissionHelper(component.permission);
261 const tableActions: TableActionsComponent = permissionHelper.setPermissionsAndGetActions(
262 component.tableActions
263 );
264
265 expect(tableActions).toEqual({
266 'create,update,delete': {
267 actions: ['Create', 'Edit', 'Copy', 'Flatten', 'Delete', 'Move to Trash'],
268 primary: { multiple: 'Create', executing: 'Edit', single: 'Edit', no: 'Create' }
269 },
270 'create,update': {
271 actions: ['Create', 'Edit', 'Copy', 'Flatten'],
272 primary: { multiple: 'Create', executing: 'Edit', single: 'Edit', no: 'Create' }
273 },
274 'create,delete': {
275 actions: ['Create', 'Copy', 'Delete', 'Move to Trash'],
276 primary: { multiple: 'Create', executing: 'Copy', single: 'Copy', no: 'Create' }
277 },
278 create: {
279 actions: ['Create', 'Copy'],
280 primary: { multiple: 'Create', executing: 'Copy', single: 'Copy', no: 'Create' }
281 },
282 'update,delete': {
283 actions: ['Edit', 'Flatten', 'Delete', 'Move to Trash'],
284 primary: { multiple: 'Edit', executing: 'Edit', single: 'Edit', no: 'Edit' }
285 },
286 update: {
287 actions: ['Edit', 'Flatten'],
288 primary: { multiple: 'Edit', executing: 'Edit', single: 'Edit', no: 'Edit' }
289 },
290 delete: {
291 actions: ['Delete', 'Move to Trash'],
292 primary: { multiple: 'Delete', executing: 'Delete', single: 'Delete', no: 'Delete' }
293 },
294 'no-permissions': {
295 actions: [],
296 primary: { multiple: '', executing: '', single: '', no: '' }
297 }
298 });
299 });
300
301 const getActionDisable = (name: string) =>
302 component.tableActions.find((o) => o.name === name).disable;
303
304 const testActions = (selection: any, expected: { [action: string]: string | boolean }) => {
305 expect(getActionDisable('Edit')(selection)).toBe(expected.edit || false);
306 expect(getActionDisable('Delete')(selection)).toBe(expected.delete || false);
307 expect(getActionDisable('Copy')(selection)).toBe(expected.copy || false);
308 expect(getActionDisable('Flatten')(selection)).toBeTruthy();
309 expect(getActionDisable('Move to Trash')(selection)).toBe(expected.moveTrash || false);
310 };
311
312 it('should test TableActions with valid/invalid image name', () => {
313 component.selection.selected = [
314 {
315 name: 'foobar',
316 pool_name: 'rbd',
317 snapshots: []
318 }
319 ];
320 testActions(component.selection, {});
321
322 component.selection.selected = [
323 {
324 name: 'foo/bar',
325 pool_name: 'rbd',
326 snapshots: []
327 }
328 ];
329 const message = `This RBD image has an invalid name and can't be managed by ceph.`;
330 const expected = {
331 edit: message,
332 delete: message,
333 copy: message,
334 moveTrash: message
335 };
336 testActions(component.selection, expected);
337 });
338
339 it('should disable edit, copy, flatten and move action if RBD is in status `Removing`', () => {
340 component.selection.selected = [
341 {
342 name: 'foobar',
343 pool_name: 'rbd',
344 snapshots: [],
345 source: 'REMOVING'
346 }
347 ];
348
349 const message = `Action not possible for an RBD in status 'Removing'`;
350 const expected = {
351 edit: message,
352 copy: message,
353 moveTrash: message
354 };
355 testActions(component.selection, expected);
356 });
357 });