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