]> git.proxmox.com Git - ceph.git/blob - ceph/src/pybind/mgr/dashboard/frontend/src/app/ceph/dashboard/health/health.component.spec.ts
import ceph pacific 16.2.5
[ceph.git] / ceph / src / pybind / mgr / dashboard / frontend / src / app / ceph / dashboard / health / health.component.spec.ts
1 import { HttpClientTestingModule } from '@angular/common/http/testing';
2 import { NO_ERRORS_SCHEMA } from '@angular/core';
3 import { ComponentFixture, TestBed } from '@angular/core/testing';
4 import { By } from '@angular/platform-browser';
5
6 import _ from 'lodash';
7 import { of } from 'rxjs';
8
9 import { PgCategoryService } from '~/app/ceph/shared/pg-category.service';
10 import { HealthService } from '~/app/shared/api/health.service';
11 import { CssHelper } from '~/app/shared/classes/css-helper';
12 import { Permissions } from '~/app/shared/models/permissions';
13 import { AuthStorageService } from '~/app/shared/services/auth-storage.service';
14 import { FeatureTogglesService } from '~/app/shared/services/feature-toggles.service';
15 import { RefreshIntervalService } from '~/app/shared/services/refresh-interval.service';
16 import { SharedModule } from '~/app/shared/shared.module';
17 import { configureTestBed } from '~/testing/unit-test-helper';
18 import { HealthPieComponent } from '../health-pie/health-pie.component';
19 import { MdsSummaryPipe } from '../mds-summary.pipe';
20 import { MgrSummaryPipe } from '../mgr-summary.pipe';
21 import { MonSummaryPipe } from '../mon-summary.pipe';
22 import { OsdSummaryPipe } from '../osd-summary.pipe';
23 import { HealthComponent } from './health.component';
24
25 describe('HealthComponent', () => {
26 let component: HealthComponent;
27 let fixture: ComponentFixture<HealthComponent>;
28 let getHealthSpy: jasmine.Spy;
29 const healthPayload: Record<string, any> = {
30 health: { status: 'HEALTH_OK' },
31 mon_status: { monmap: { mons: [] }, quorum: [] },
32 osd_map: { osds: [] },
33 mgr_map: { standbys: [] },
34 hosts: 0,
35 rgw: 0,
36 fs_map: { filesystems: [], standbys: [] },
37 iscsi_daemons: 0,
38 client_perf: {},
39 scrub_status: 'Inactive',
40 pools: [],
41 df: { stats: {} },
42 pg_info: { object_stats: { num_objects: 0 } }
43 };
44 const fakeAuthStorageService = {
45 getPermissions: () => {
46 return new Permissions({ log: ['read'] });
47 }
48 };
49 let fakeFeatureTogglesService: jasmine.Spy;
50
51 configureTestBed({
52 imports: [SharedModule, HttpClientTestingModule],
53 declarations: [
54 HealthComponent,
55 HealthPieComponent,
56 MonSummaryPipe,
57 OsdSummaryPipe,
58 MdsSummaryPipe,
59 MgrSummaryPipe
60 ],
61 schemas: [NO_ERRORS_SCHEMA],
62 providers: [
63 { provide: AuthStorageService, useValue: fakeAuthStorageService },
64 PgCategoryService,
65 RefreshIntervalService,
66 CssHelper
67 ]
68 });
69
70 beforeEach(() => {
71 fakeFeatureTogglesService = spyOn(TestBed.inject(FeatureTogglesService), 'get').and.returnValue(
72 of({
73 rbd: true,
74 mirroring: true,
75 iscsi: true,
76 cephfs: true,
77 rgw: true
78 })
79 );
80 fixture = TestBed.createComponent(HealthComponent);
81 component = fixture.componentInstance;
82 getHealthSpy = spyOn(TestBed.inject(HealthService), 'getMinimalHealth');
83 getHealthSpy.and.returnValue(of(healthPayload));
84 });
85
86 it('should create', () => {
87 expect(component).toBeTruthy();
88 });
89
90 it('should render all info groups and all info cards', () => {
91 fixture.detectChanges();
92
93 const infoGroups = fixture.debugElement.nativeElement.querySelectorAll('cd-info-group');
94 expect(infoGroups.length).toBe(3);
95
96 const infoCards = fixture.debugElement.nativeElement.querySelectorAll('cd-info-card');
97 expect(infoCards.length).toBe(17);
98 });
99
100 describe('features disabled', () => {
101 beforeEach(() => {
102 fakeFeatureTogglesService.and.returnValue(
103 of({
104 rbd: false,
105 mirroring: false,
106 iscsi: false,
107 cephfs: false,
108 rgw: false
109 })
110 );
111 fixture = TestBed.createComponent(HealthComponent);
112 component = fixture.componentInstance;
113 });
114
115 it('should not render cards related to disabled features', () => {
116 fixture.detectChanges();
117
118 const infoGroups = fixture.debugElement.nativeElement.querySelectorAll('cd-info-group');
119 expect(infoGroups.length).toBe(3);
120
121 const infoCards = fixture.debugElement.nativeElement.querySelectorAll('cd-info-card');
122 expect(infoCards.length).toBe(14);
123 });
124 });
125
126 it('should render all except "Status" group and cards', () => {
127 const payload = _.cloneDeep(healthPayload);
128 payload.health.status = '';
129 payload.mon_status = null;
130 payload.osd_map = null;
131 payload.mgr_map = null;
132 payload.hosts = null;
133 payload.rgw = null;
134 payload.fs_map = null;
135 payload.iscsi_daemons = null;
136
137 getHealthSpy.and.returnValue(of(payload));
138 fixture.detectChanges();
139
140 const infoGroups = fixture.debugElement.nativeElement.querySelectorAll('cd-info-group');
141 expect(infoGroups.length).toBe(2);
142
143 const infoCards = fixture.debugElement.nativeElement.querySelectorAll('cd-info-card');
144 expect(infoCards.length).toBe(9);
145 });
146
147 it('should render all except "Performance" group and cards', () => {
148 const payload = _.cloneDeep(healthPayload);
149 payload.scrub_status = '';
150 payload.client_perf = null;
151
152 getHealthSpy.and.returnValue(of(payload));
153 fixture.detectChanges();
154
155 const infoGroups = fixture.debugElement.nativeElement.querySelectorAll('cd-info-group');
156 expect(infoGroups.length).toBe(2);
157
158 const infoCards = fixture.debugElement.nativeElement.querySelectorAll('cd-info-card');
159 expect(infoCards.length).toBe(13);
160 });
161
162 it('should render all except "Capacity" group and cards', () => {
163 const payload = _.cloneDeep(healthPayload);
164 payload.pools = null;
165 payload.df = null;
166 payload.pg_info = null;
167
168 getHealthSpy.and.returnValue(of(payload));
169 fixture.detectChanges();
170
171 const infoGroups = fixture.debugElement.nativeElement.querySelectorAll('cd-info-group');
172 expect(infoGroups.length).toBe(2);
173
174 const infoCards = fixture.debugElement.nativeElement.querySelectorAll('cd-info-card');
175 expect(infoCards.length).toBe(12);
176 });
177
178 it('should render all groups and 1 card per group', () => {
179 const payload: Record<string, any> = { hosts: 0, scrub_status: 'Inactive', pools: [] };
180
181 getHealthSpy.and.returnValue(of(payload));
182 fixture.detectChanges();
183
184 const infoGroups = fixture.debugElement.nativeElement.querySelectorAll('cd-info-group');
185 expect(infoGroups.length).toBe(3);
186
187 _.each(infoGroups, (infoGroup) => {
188 expect(infoGroup.querySelectorAll('cd-info-card').length).toBe(1);
189 });
190 });
191
192 it('should render "Cluster Status" card text that is not clickable', () => {
193 fixture.detectChanges();
194
195 const clusterStatusCard = fixture.debugElement.query(
196 By.css('cd-info-card[cardTitle="Cluster Status"]')
197 );
198 const clickableContent = clusterStatusCard.query(By.css('.info-card-content-clickable'));
199 expect(clickableContent).toBeNull();
200 expect(clusterStatusCard.nativeElement.textContent).toEqual(` ${healthPayload.health.status} `);
201 });
202
203 it('should render "Cluster Status" card text that is clickable (popover)', () => {
204 const payload = _.cloneDeep(healthPayload);
205 payload.health['status'] = 'HEALTH_WARN';
206 payload.health['checks'] = [
207 { severity: 'HEALTH_WARN', type: 'WRN', summary: { message: 'fake warning' } }
208 ];
209
210 getHealthSpy.and.returnValue(of(payload));
211 fixture.detectChanges();
212
213 expect(component.permissions.log.read).toBeTruthy();
214
215 const clusterStatusCard = fixture.debugElement.query(
216 By.css('cd-info-card[cardTitle="Cluster Status"]')
217 );
218 const clickableContent = clusterStatusCard.query(By.css('.info-card-content-clickable'));
219 expect(clickableContent.nativeElement.textContent).toEqual(` ${payload.health.status} `);
220 });
221
222 it('event binding "prepareReadWriteRatio" is called', () => {
223 const prepareReadWriteRatio = spyOn(component, 'prepareReadWriteRatio').and.callThrough();
224
225 const payload = _.cloneDeep(healthPayload);
226 payload.client_perf['read_op_per_sec'] = 1;
227 payload.client_perf['write_op_per_sec'] = 3;
228 getHealthSpy.and.returnValue(of(payload));
229 fixture.detectChanges();
230
231 expect(prepareReadWriteRatio).toHaveBeenCalled();
232 expect(prepareReadWriteRatio.calls.mostRecent().args[0].dataset[0].data).toEqual([25, 75]);
233 });
234
235 it('event binding "prepareRawUsage" is called', () => {
236 const prepareRawUsage = spyOn(component, 'prepareRawUsage');
237
238 fixture.detectChanges();
239
240 expect(prepareRawUsage).toHaveBeenCalled();
241 });
242
243 it('event binding "preparePgStatus" is called', () => {
244 const preparePgStatus = spyOn(component, 'preparePgStatus');
245
246 fixture.detectChanges();
247
248 expect(preparePgStatus).toHaveBeenCalled();
249 });
250
251 it('event binding "prepareObjects" is called', () => {
252 const prepareObjects = spyOn(component, 'prepareObjects');
253
254 fixture.detectChanges();
255
256 expect(prepareObjects).toHaveBeenCalled();
257 });
258
259 describe('preparePgStatus', () => {
260 const expectedChart = (data: number[], label: string = null) => ({
261 labels: [
262 `Clean: ${component['dimless'].transform(data[0])}`,
263 `Working: ${component['dimless'].transform(data[1])}`,
264 `Warning: ${component['dimless'].transform(data[2])}`,
265 `Unknown: ${component['dimless'].transform(data[3])}`
266 ],
267 options: {},
268 dataset: [
269 {
270 data: data.map((i) =>
271 component['calcPercentage'](
272 i,
273 data.reduce((j, k) => j + k)
274 )
275 ),
276 label: label
277 }
278 ]
279 });
280
281 it('gets no data', () => {
282 const chart = { dataset: [{}], options: {} };
283 component.preparePgStatus(chart, {
284 pg_info: {}
285 });
286 expect(chart).toEqual(expectedChart([0, 0, 0, 0], '0\nPGs'));
287 });
288
289 it('gets data from all categories', () => {
290 const chart = { dataset: [{}], options: {} };
291 component.preparePgStatus(chart, {
292 pg_info: {
293 statuses: {
294 'clean+active+scrubbing+nonMappedState': 4,
295 'clean+active+scrubbing': 2,
296 'clean+active': 1,
297 'clean+active+scrubbing+down': 3
298 }
299 }
300 });
301 expect(chart).toEqual(expectedChart([1, 2, 3, 4], '10\nPGs'));
302 });
303 });
304
305 describe('isClientReadWriteChartShowable', () => {
306 beforeEach(() => {
307 component.healthData = healthPayload;
308 });
309
310 it('returns false', () => {
311 component.healthData['client_perf'] = {};
312
313 expect(component.isClientReadWriteChartShowable()).toBeFalsy();
314 });
315
316 it('returns false', () => {
317 component.healthData['client_perf'] = { read_op_per_sec: undefined, write_op_per_sec: 0 };
318
319 expect(component.isClientReadWriteChartShowable()).toBeFalsy();
320 });
321
322 it('returns true', () => {
323 component.healthData['client_perf'] = { read_op_per_sec: 1, write_op_per_sec: undefined };
324
325 expect(component.isClientReadWriteChartShowable()).toBeTruthy();
326 });
327
328 it('returns true', () => {
329 component.healthData['client_perf'] = { read_op_per_sec: 2, write_op_per_sec: 3 };
330
331 expect(component.isClientReadWriteChartShowable()).toBeTruthy();
332 });
333 });
334
335 describe('calcPercentage', () => {
336 it('returns correct value', () => {
337 expect(component['calcPercentage'](1, undefined)).toEqual(0);
338 expect(component['calcPercentage'](1, null)).toEqual(0);
339 expect(component['calcPercentage'](1, 0)).toEqual(0);
340 expect(component['calcPercentage'](undefined, 1)).toEqual(0);
341 expect(component['calcPercentage'](null, 1)).toEqual(0);
342 expect(component['calcPercentage'](0, 1)).toEqual(0);
343 expect(component['calcPercentage'](2.346, 10)).toEqual(23);
344 expect(component['calcPercentage'](2.35, 10)).toEqual(24);
345 });
346 });
347 });