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