]>
Commit | Line | Data |
---|---|---|
11fdf7f2 TL |
1 | import { HttpClientTestingModule } from '@angular/common/http/testing'; |
2 | import { ComponentFixture, TestBed } from '@angular/core/testing'; | |
3 | import { RouterTestingModule } from '@angular/router/testing'; | |
4 | ||
494da23a | 5 | import * as _ from 'lodash'; |
11fdf7f2 TL |
6 | import { BsModalService } from 'ngx-bootstrap/modal'; |
7 | import { TabsModule } from 'ngx-bootstrap/tabs'; | |
494da23a | 8 | import { ToastrModule } from 'ngx-toastr'; |
11fdf7f2 TL |
9 | import { of } from 'rxjs'; |
10 | ||
eafe8130 TL |
11 | import { |
12 | configureTestBed, | |
13 | expectItemTasks, | |
14 | i18nProviders | |
15 | } from '../../../../testing/unit-test-helper'; | |
494da23a | 16 | import { ConfigurationService } from '../../../shared/api/configuration.service'; |
11fdf7f2 TL |
17 | import { PoolService } from '../../../shared/api/pool.service'; |
18 | import { CriticalConfirmationModalComponent } from '../../../shared/components/critical-confirmation-modal/critical-confirmation-modal.component'; | |
19 | import { ExecutingTask } from '../../../shared/models/executing-task'; | |
92f5a8d4 | 20 | import { AuthStorageService } from '../../../shared/services/auth-storage.service'; |
11fdf7f2 TL |
21 | import { SummaryService } from '../../../shared/services/summary.service'; |
22 | import { TaskWrapperService } from '../../../shared/services/task-wrapper.service'; | |
23 | import { SharedModule } from '../../../shared/shared.module'; | |
24 | import { RbdConfigurationListComponent } from '../../block/rbd-configuration-list/rbd-configuration-list.component'; | |
25 | import { PgCategoryService } from '../../shared/pg-category.service'; | |
26 | import { Pool } from '../pool'; | |
27 | import { PoolDetailsComponent } from '../pool-details/pool-details.component'; | |
28 | import { PoolListComponent } from './pool-list.component'; | |
29 | ||
30 | describe('PoolListComponent', () => { | |
31 | let component: PoolListComponent; | |
32 | let fixture: ComponentFixture<PoolListComponent>; | |
33 | let poolService: PoolService; | |
34 | ||
9f95a23c | 35 | const createPool = (name: string, id: number): Pool => { |
eafe8130 TL |
36 | return _.merge(new Pool(name), { |
37 | pool: id, | |
38 | pg_num: 256, | |
39 | pg_placement_num: 256, | |
40 | pg_num_target: 256, | |
41 | pg_placement_num_target: 256 | |
42 | }); | |
11fdf7f2 TL |
43 | }; |
44 | ||
eafe8130 TL |
45 | const getPoolList = (): Pool[] => { |
46 | return [createPool('a', 0), createPool('b', 1), createPool('c', 2)]; | |
11fdf7f2 TL |
47 | }; |
48 | ||
49 | configureTestBed({ | |
50 | declarations: [PoolListComponent, PoolDetailsComponent, RbdConfigurationListComponent], | |
51 | imports: [ | |
52 | SharedModule, | |
494da23a | 53 | ToastrModule.forRoot(), |
11fdf7f2 TL |
54 | RouterTestingModule, |
55 | TabsModule.forRoot(), | |
56 | HttpClientTestingModule | |
57 | ], | |
58 | providers: [i18nProviders, PgCategoryService] | |
59 | }); | |
60 | ||
61 | beforeEach(() => { | |
62 | fixture = TestBed.createComponent(PoolListComponent); | |
63 | component = fixture.componentInstance; | |
64 | component.permissions.pool.read = true; | |
65 | poolService = TestBed.get(PoolService); | |
eafe8130 | 66 | spyOn(poolService, 'getList').and.callFake(() => of(getPoolList())); |
11fdf7f2 TL |
67 | fixture.detectChanges(); |
68 | }); | |
69 | ||
70 | it('should create', () => { | |
71 | expect(component).toBeTruthy(); | |
72 | }); | |
73 | ||
81eedcae TL |
74 | it('should have columns that are sortable', () => { |
75 | expect(component.columns.every((column) => Boolean(column.prop))).toBeTruthy(); | |
76 | }); | |
77 | ||
494da23a | 78 | describe('monAllowPoolDelete', () => { |
92f5a8d4 | 79 | let configOptRead: boolean; |
494da23a TL |
80 | let configurationService: ConfigurationService; |
81 | ||
82 | beforeEach(() => { | |
92f5a8d4 TL |
83 | configOptRead = true; |
84 | spyOn(TestBed.get(AuthStorageService), 'getPermissions').and.callFake(() => ({ | |
85 | configOpt: { read: configOptRead } | |
86 | })); | |
494da23a TL |
87 | configurationService = TestBed.get(ConfigurationService); |
88 | }); | |
89 | ||
90 | it('should set value correctly if mon_allow_pool_delete flag is set to true', () => { | |
91 | const configOption = { | |
92 | name: 'mon_allow_pool_delete', | |
93 | value: [ | |
94 | { | |
95 | section: 'mon', | |
96 | value: 'true' | |
97 | } | |
98 | ] | |
99 | }; | |
100 | spyOn(configurationService, 'get').and.returnValue(of(configOption)); | |
101 | fixture = TestBed.createComponent(PoolListComponent); | |
102 | component = fixture.componentInstance; | |
103 | expect(component.monAllowPoolDelete).toBe(true); | |
104 | }); | |
105 | ||
106 | it('should set value correctly if mon_allow_pool_delete flag is set to false', () => { | |
107 | const configOption = { | |
108 | name: 'mon_allow_pool_delete', | |
109 | value: [ | |
110 | { | |
111 | section: 'mon', | |
112 | value: 'false' | |
113 | } | |
114 | ] | |
115 | }; | |
116 | spyOn(configurationService, 'get').and.returnValue(of(configOption)); | |
117 | fixture = TestBed.createComponent(PoolListComponent); | |
118 | component = fixture.componentInstance; | |
119 | expect(component.monAllowPoolDelete).toBe(false); | |
120 | }); | |
121 | ||
122 | it('should set value correctly if mon_allow_pool_delete flag is not set', () => { | |
123 | const configOption = { | |
124 | name: 'mon_allow_pool_delete' | |
125 | }; | |
126 | spyOn(configurationService, 'get').and.returnValue(of(configOption)); | |
127 | fixture = TestBed.createComponent(PoolListComponent); | |
128 | component = fixture.componentInstance; | |
129 | expect(component.monAllowPoolDelete).toBe(false); | |
130 | }); | |
92f5a8d4 TL |
131 | |
132 | it('should set value correctly w/o config-opt read privileges', () => { | |
133 | configOptRead = false; | |
134 | fixture = TestBed.createComponent(PoolListComponent); | |
135 | component = fixture.componentInstance; | |
136 | expect(component.monAllowPoolDelete).toBe(false); | |
137 | }); | |
494da23a TL |
138 | }); |
139 | ||
11fdf7f2 TL |
140 | describe('pool deletion', () => { |
141 | let taskWrapper: TaskWrapperService; | |
142 | ||
9f95a23c TL |
143 | const setSelectedPool = (poolName: string) => |
144 | (component.selection.selected = [{ pool_name: poolName }]); | |
11fdf7f2 TL |
145 | |
146 | const callDeletion = () => { | |
147 | component.deletePoolModal(); | |
148 | const deletion: CriticalConfirmationModalComponent = component.modalRef.content; | |
149 | deletion.submitActionObservable(); | |
150 | }; | |
151 | ||
9f95a23c | 152 | const testPoolDeletion = (poolName: string) => { |
11fdf7f2 TL |
153 | setSelectedPool(poolName); |
154 | callDeletion(); | |
155 | expect(poolService.delete).toHaveBeenCalledWith(poolName); | |
156 | expect(taskWrapper.wrapTaskAroundCall).toHaveBeenCalledWith({ | |
157 | task: { | |
158 | name: 'pool/delete', | |
159 | metadata: { | |
160 | pool_name: poolName | |
161 | } | |
162 | }, | |
163 | call: undefined // because of stub | |
164 | }); | |
165 | }; | |
166 | ||
167 | beforeEach(() => { | |
168 | spyOn(TestBed.get(BsModalService), 'show').and.callFake((deletionClass, config) => { | |
169 | return { | |
170 | content: Object.assign(new deletionClass(), config.initialState) | |
171 | }; | |
172 | }); | |
173 | spyOn(poolService, 'delete').and.stub(); | |
174 | taskWrapper = TestBed.get(TaskWrapperService); | |
175 | spyOn(taskWrapper, 'wrapTaskAroundCall').and.callThrough(); | |
176 | }); | |
177 | ||
178 | it('should pool deletion with two different pools', () => { | |
179 | testPoolDeletion('somePoolName'); | |
180 | testPoolDeletion('aDifferentPoolName'); | |
181 | }); | |
182 | }); | |
183 | ||
184 | describe('handling of executing tasks', () => { | |
11fdf7f2 TL |
185 | let summaryService: SummaryService; |
186 | ||
187 | const addTask = (name: string, pool: string) => { | |
188 | const task = new ExecutingTask(); | |
189 | task.name = name; | |
190 | task.metadata = { pool_name: pool }; | |
191 | summaryService.addRunningTask(task); | |
192 | }; | |
193 | ||
194 | beforeEach(() => { | |
195 | summaryService = TestBed.get(SummaryService); | |
196 | summaryService['summaryDataSource'].next({ executing_tasks: [], finished_tasks: [] }); | |
11fdf7f2 TL |
197 | }); |
198 | ||
199 | it('gets all pools without executing pools', () => { | |
200 | expect(component.pools.length).toBe(3); | |
201 | expect(component.pools.every((pool) => !pool.executingTasks)).toBeTruthy(); | |
202 | }); | |
203 | ||
204 | it('gets a pool from a task during creation', () => { | |
205 | addTask('pool/create', 'd'); | |
206 | expect(component.pools.length).toBe(4); | |
eafe8130 | 207 | expectItemTasks(component.pools[3], 'Creating'); |
11fdf7f2 TL |
208 | }); |
209 | ||
210 | it('gets all pools with one executing pools', () => { | |
211 | addTask('pool/create', 'a'); | |
212 | expect(component.pools.length).toBe(3); | |
eafe8130 | 213 | expectItemTasks(component.pools[0], 'Creating'); |
11fdf7f2 TL |
214 | expect(component.pools[1].cdExecuting).toBeFalsy(); |
215 | expect(component.pools[2].cdExecuting).toBeFalsy(); | |
216 | }); | |
217 | ||
218 | it('gets all pools with multiple executing pools', () => { | |
219 | addTask('pool/create', 'a'); | |
220 | addTask('pool/edit', 'a'); | |
221 | addTask('pool/delete', 'a'); | |
222 | addTask('pool/edit', 'b'); | |
223 | addTask('pool/delete', 'b'); | |
224 | addTask('pool/delete', 'c'); | |
225 | expect(component.pools.length).toBe(3); | |
eafe8130 TL |
226 | expectItemTasks(component.pools[0], 'Creating..., Updating..., Deleting'); |
227 | expectItemTasks(component.pools[1], 'Updating..., Deleting'); | |
228 | expectItemTasks(component.pools[2], 'Deleting'); | |
11fdf7f2 TL |
229 | }); |
230 | ||
231 | it('gets all pools with multiple executing tasks (not only pool tasks)', () => { | |
232 | addTask('rbd/create', 'a'); | |
233 | addTask('rbd/edit', 'a'); | |
234 | addTask('pool/delete', 'a'); | |
235 | addTask('pool/edit', 'b'); | |
236 | addTask('rbd/delete', 'b'); | |
237 | addTask('rbd/delete', 'c'); | |
238 | expect(component.pools.length).toBe(3); | |
eafe8130 TL |
239 | expectItemTasks(component.pools[0], 'Deleting'); |
240 | expectItemTasks(component.pools[1], 'Updating'); | |
11fdf7f2 TL |
241 | expect(component.pools[2].cdExecuting).toBeFalsy(); |
242 | }); | |
243 | }); | |
244 | ||
245 | describe('getPgStatusCellClass', () => { | |
9f95a23c | 246 | const testMethod = (value: string, expected: string) => |
11fdf7f2 TL |
247 | expect(component.getPgStatusCellClass('', '', value)).toEqual({ |
248 | 'text-right': true, | |
249 | [expected]: true | |
250 | }); | |
251 | ||
252 | it('pg-clean', () => { | |
253 | testMethod('8 active+clean', 'pg-clean'); | |
254 | }); | |
255 | ||
256 | it('pg-working', () => { | |
257 | testMethod(' 8 active+clean+scrubbing+deep, 255 active+clean ', 'pg-working'); | |
258 | }); | |
259 | ||
260 | it('pg-warning', () => { | |
261 | testMethod('8 active+clean+scrubbing+down', 'pg-warning'); | |
262 | testMethod('8 active+clean+scrubbing+down+nonMappedState', 'pg-warning'); | |
263 | }); | |
264 | ||
265 | it('pg-unknown', () => { | |
266 | testMethod('8 active+clean+scrubbing+nonMappedState', 'pg-unknown'); | |
267 | testMethod('8 ', 'pg-unknown'); | |
268 | testMethod('', 'pg-unknown'); | |
269 | }); | |
270 | }); | |
271 | ||
494da23a TL |
272 | describe('custom row comparators', () => { |
273 | const expectCorrectComparator = (statsAttribute: string) => { | |
9f95a23c | 274 | const mockPool = (v: number) => ({ stats: { [statsAttribute]: { latest: v } } }); |
494da23a TL |
275 | const columnDefinition = _.find( |
276 | component.columns, | |
277 | (column) => column.prop === `stats.${statsAttribute}.rates` | |
278 | ); | |
279 | expect(columnDefinition.comparator(undefined, undefined, mockPool(2), mockPool(1))).toBe(1); | |
280 | expect(columnDefinition.comparator(undefined, undefined, mockPool(1), mockPool(2))).toBe(-1); | |
281 | }; | |
282 | ||
283 | it('compares read bytes correctly', () => { | |
284 | expectCorrectComparator('rd_bytes'); | |
285 | }); | |
286 | ||
287 | it('compares write bytes correctly', () => { | |
288 | expectCorrectComparator('wr_bytes'); | |
289 | }); | |
290 | }); | |
291 | ||
11fdf7f2 | 292 | describe('transformPoolsData', () => { |
eafe8130 | 293 | let pool: Pool; |
11fdf7f2 | 294 | |
9f95a23c | 295 | const getPoolData = (o: object) => [ |
eafe8130 TL |
296 | _.merge( |
297 | _.merge(createPool('a', 0), { | |
81eedcae TL |
298 | cdIsBinary: true, |
299 | pg_status: '', | |
300 | stats: { | |
494da23a TL |
301 | bytes_used: { latest: 0, rate: 0, rates: [] }, |
302 | max_avail: { latest: 0, rate: 0, rates: [] }, | |
303 | rd: { latest: 0, rate: 0, rates: [] }, | |
304 | rd_bytes: { latest: 0, rate: 0, rates: [] }, | |
305 | wr: { latest: 0, rate: 0, rates: [] }, | |
306 | wr_bytes: { latest: 0, rate: 0, rates: [] } | |
81eedcae TL |
307 | }, |
308 | usage: 0 | |
eafe8130 TL |
309 | }), |
310 | o | |
311 | ) | |
312 | ]; | |
313 | ||
314 | beforeEach(() => { | |
315 | pool = createPool('a', 0); | |
316 | }); | |
317 | ||
318 | it('transforms pools data correctly', () => { | |
319 | pool = _.merge(pool, { | |
320 | stats: { | |
321 | bytes_used: { latest: 5, rate: 0, rates: [] }, | |
322 | max_avail: { latest: 15, rate: 0, rates: [] }, | |
801d1391 TL |
323 | rd_bytes: { |
324 | latest: 6, | |
325 | rate: 4, | |
326 | rates: [ | |
327 | [0, 2], | |
328 | [1, 6] | |
329 | ] | |
330 | } | |
eafe8130 TL |
331 | }, |
332 | pg_status: { 'active+clean': 8, down: 2 } | |
333 | }); | |
334 | expect(component.transformPoolsData([pool])).toEqual( | |
335 | getPoolData({ | |
336 | pg_status: '8 active+clean, 2 down', | |
337 | stats: { | |
338 | bytes_used: { latest: 5, rate: 0, rates: [] }, | |
339 | max_avail: { latest: 15, rate: 0, rates: [] }, | |
340 | rd_bytes: { latest: 6, rate: 4, rates: [2, 6] } | |
341 | }, | |
342 | usage: 0.25 | |
343 | }) | |
344 | ); | |
345 | }); | |
346 | ||
347 | it('transforms pools data correctly if stats are missing', () => { | |
348 | expect(component.transformPoolsData([pool])).toEqual(getPoolData({})); | |
11fdf7f2 TL |
349 | }); |
350 | ||
351 | it('transforms empty pools data correctly', () => { | |
eafe8130 TL |
352 | expect(component.transformPoolsData(undefined)).toEqual(undefined); |
353 | expect(component.transformPoolsData([])).toEqual([]); | |
354 | }); | |
355 | ||
356 | it('shows not marked pools in progress if pg_num does not match pg_num_target', () => { | |
357 | const pools = [ | |
358 | _.merge(pool, { | |
359 | pg_num: 32, | |
360 | pg_num_target: 16, | |
361 | pg_placement_num: 32, | |
362 | pg_placement_num_target: 16 | |
363 | }) | |
364 | ]; | |
365 | expect(component.transformPoolsData(pools)).toEqual( | |
366 | getPoolData({ | |
367 | cdExecuting: 'Updating', | |
368 | pg_num: 32, | |
369 | pg_num_target: 16, | |
370 | pg_placement_num: 32, | |
371 | pg_placement_num_target: 16 | |
372 | }) | |
373 | ); | |
374 | }); | |
375 | ||
376 | it('shows marked pools in progress as defined by task', () => { | |
377 | const pools = [ | |
378 | _.merge(pool, { | |
379 | pg_num: 32, | |
380 | pg_num_target: 16, | |
381 | pg_placement_num: 32, | |
382 | pg_placement_num_target: 16, | |
383 | cdExecuting: 'Updating... 50%' | |
384 | }) | |
385 | ]; | |
386 | expect(component.transformPoolsData(pools)).toEqual( | |
387 | getPoolData({ | |
388 | cdExecuting: 'Updating... 50%', | |
389 | pg_num: 32, | |
390 | pg_num_target: 16, | |
391 | pg_placement_num: 32, | |
392 | pg_placement_num_target: 16 | |
393 | }) | |
394 | ); | |
11fdf7f2 TL |
395 | }); |
396 | }); | |
397 | ||
398 | describe('transformPgStatus', () => { | |
399 | it('returns status groups correctly', () => { | |
400 | const pgStatus = { 'active+clean': 8 }; | |
401 | const expected = '8 active+clean'; | |
402 | ||
403 | expect(component.transformPgStatus(pgStatus)).toEqual(expected); | |
404 | }); | |
405 | ||
406 | it('returns separated status groups', () => { | |
407 | const pgStatus = { 'active+clean': 8, down: 2 }; | |
408 | const expected = '8 active+clean, 2 down'; | |
409 | ||
410 | expect(component.transformPgStatus(pgStatus)).toEqual(expected); | |
411 | }); | |
412 | ||
413 | it('returns separated statuses correctly', () => { | |
414 | const pgStatus = { active: 8, down: 2 }; | |
415 | const expected = '8 active, 2 down'; | |
416 | ||
417 | expect(component.transformPgStatus(pgStatus)).toEqual(expected); | |
418 | }); | |
419 | ||
420 | it('returns empty string', () => { | |
9f95a23c | 421 | const pgStatus: any = undefined; |
11fdf7f2 TL |
422 | const expected = ''; |
423 | ||
424 | expect(component.transformPgStatus(pgStatus)).toEqual(expected); | |
425 | }); | |
426 | }); | |
427 | ||
11fdf7f2 | 428 | describe('getSelectionTiers', () => { |
11fdf7f2 | 429 | const setSelectionTiers = (tiers: number[]) => { |
9f95a23c | 430 | component.selection.selected = [{ tiers }]; |
11fdf7f2 TL |
431 | component.getSelectionTiers(); |
432 | }; | |
433 | ||
434 | beforeEach(() => { | |
eafe8130 | 435 | component.pools = getPoolList(); |
11fdf7f2 TL |
436 | }); |
437 | ||
438 | it('should select multiple existing cache tiers', () => { | |
439 | setSelectionTiers([0, 1, 2]); | |
eafe8130 | 440 | expect(component.selectionCacheTiers).toEqual(getPoolList()); |
11fdf7f2 TL |
441 | }); |
442 | ||
443 | it('should select correct existing cache tier', () => { | |
444 | setSelectionTiers([0]); | |
eafe8130 | 445 | expect(component.selectionCacheTiers).toEqual([createPool('a', 0)]); |
11fdf7f2 TL |
446 | }); |
447 | ||
448 | it('should not select cache tier if id is invalid', () => { | |
449 | setSelectionTiers([-1]); | |
450 | expect(component.selectionCacheTiers).toEqual([]); | |
451 | }); | |
452 | ||
453 | it('should not select cache tier if empty', () => { | |
454 | setSelectionTiers([]); | |
455 | expect(component.selectionCacheTiers).toEqual([]); | |
456 | }); | |
457 | ||
458 | it('should be able to selected one pool with multiple tiers, than with a single tier, than with no tiers', () => { | |
459 | setSelectionTiers([0, 1, 2]); | |
eafe8130 | 460 | expect(component.selectionCacheTiers).toEqual(getPoolList()); |
11fdf7f2 | 461 | setSelectionTiers([0]); |
eafe8130 | 462 | expect(component.selectionCacheTiers).toEqual([createPool('a', 0)]); |
11fdf7f2 TL |
463 | setSelectionTiers([]); |
464 | expect(component.selectionCacheTiers).toEqual([]); | |
465 | }); | |
466 | }); | |
494da23a TL |
467 | |
468 | describe('getDisableDesc', () => { | |
469 | it('should return message if mon_allow_pool_delete flag is set to false', () => { | |
470 | component.monAllowPoolDelete = false; | |
471 | expect(component.getDisableDesc()).toBe( | |
472 | 'Pool deletion is disabled by the mon_allow_pool_delete configuration setting.' | |
473 | ); | |
474 | }); | |
475 | ||
476 | it('should return undefined if mon_allow_pool_delete flag is set to true', () => { | |
477 | component.monAllowPoolDelete = true; | |
478 | expect(component.getDisableDesc()).toBeUndefined(); | |
479 | }); | |
480 | }); | |
11fdf7f2 | 481 | }); |