]>
Commit | Line | Data |
---|---|---|
1e59de90 TL |
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 | import { RouterTestingModule } from '@angular/router/testing'; | |
6 | ||
7 | import _ from 'lodash'; | |
8 | import { ToastrModule } from 'ngx-toastr'; | |
9 | import { BehaviorSubject, of } from 'rxjs'; | |
10 | ||
11 | import { HealthService } from '~/app/shared/api/health.service'; | |
12 | import { PrometheusService } from '~/app/shared/api/prometheus.service'; | |
13 | import { CssHelper } from '~/app/shared/classes/css-helper'; | |
14 | import { AlertmanagerAlert } from '~/app/shared/models/prometheus-alerts'; | |
15 | import { FeatureTogglesService } from '~/app/shared/services/feature-toggles.service'; | |
16 | import { PrometheusAlertService } from '~/app/shared/services/prometheus-alert.service'; | |
17 | import { SummaryService } from '~/app/shared/services/summary.service'; | |
18 | import { SharedModule } from '~/app/shared/shared.module'; | |
19 | import { configureTestBed } from '~/testing/unit-test-helper'; | |
20 | import { PgCategoryService } from '../../shared/pg-category.service'; | |
21 | import { CardRowComponent } from '../card-row/card-row.component'; | |
22 | import { CardComponent } from '../card/card.component'; | |
23 | import { DashboardPieComponent } from '../dashboard-pie/dashboard-pie.component'; | |
24 | import { PgSummaryPipe } from '../pg-summary.pipe'; | |
25 | import { DashboardV3Component } from './dashboard-v3.component'; | |
26 | import { OrchestratorService } from '~/app/shared/api/orchestrator.service'; | |
27 | ||
28 | export class SummaryServiceMock { | |
29 | summaryDataSource = new BehaviorSubject({ | |
30 | version: | |
31 | 'ceph version 17.0.0-12222-gcd0cd7cb ' + | |
32 | '(b8193bb4cda16ccc5b028c3e1df62bc72350a15d) quincy (dev)' | |
33 | }); | |
34 | summaryData$ = this.summaryDataSource.asObservable(); | |
35 | ||
36 | subscribe(call: any) { | |
37 | return this.summaryData$.subscribe(call); | |
38 | } | |
39 | } | |
40 | ||
41 | describe('Dashbord Component', () => { | |
42 | let component: DashboardV3Component; | |
43 | let fixture: ComponentFixture<DashboardV3Component>; | |
44 | let healthService: HealthService; | |
45 | let orchestratorService: OrchestratorService; | |
46 | let getHealthSpy: jasmine.Spy; | |
47 | let getAlertsSpy: jasmine.Spy; | |
48 | let fakeFeatureTogglesService: jasmine.Spy; | |
49 | ||
50 | const healthPayload: Record<string, any> = { | |
51 | health: { status: 'HEALTH_OK' }, | |
52 | mon_status: { monmap: { mons: [] }, quorum: [] }, | |
53 | osd_map: { osds: [] }, | |
54 | mgr_map: { standbys: [] }, | |
55 | hosts: 0, | |
56 | rgw: 0, | |
57 | fs_map: { filesystems: [], standbys: [] }, | |
58 | iscsi_daemons: 1, | |
59 | client_perf: {}, | |
60 | scrub_status: 'Inactive', | |
61 | pools: [], | |
62 | df: { stats: {} }, | |
63 | pg_info: { object_stats: { num_objects: 1 } } | |
64 | }; | |
65 | ||
66 | const alertsPayload: AlertmanagerAlert[] = [ | |
67 | { | |
68 | labels: { | |
69 | alertname: 'CephMgrPrometheusModuleInactive', | |
70 | instance: 'ceph2:9283', | |
71 | job: 'ceph', | |
72 | severity: 'critical' | |
73 | }, | |
74 | annotations: { | |
75 | description: 'The mgr/prometheus module at ceph2:9283 is unreachable.', | |
76 | summary: 'The mgr/prometheus module is not available' | |
77 | }, | |
78 | startsAt: '2022-09-28T08:23:41.152Z', | |
79 | endsAt: '2022-09-28T15:28:01.152Z', | |
80 | generatorURL: 'http://prometheus:9090/testUrl', | |
81 | status: { | |
82 | state: 'active', | |
83 | silencedBy: null, | |
84 | inhibitedBy: null | |
85 | }, | |
86 | receivers: ['ceph2'], | |
87 | fingerprint: 'fingerprint' | |
88 | }, | |
89 | { | |
90 | labels: { | |
91 | alertname: 'CephOSDDownHigh', | |
92 | instance: 'ceph:9283', | |
93 | job: 'ceph', | |
94 | severity: 'critical' | |
95 | }, | |
96 | annotations: { | |
97 | description: '66.67% or 2 of 3 OSDs are down (>= 10%).', | |
98 | summary: 'More than 10% of OSDs are down' | |
99 | }, | |
100 | startsAt: '2022-09-28T14:17:22.665Z', | |
101 | endsAt: '2022-09-28T15:28:32.665Z', | |
102 | generatorURL: 'http://prometheus:9090/testUrl', | |
103 | status: { | |
104 | state: 'active', | |
105 | silencedBy: null, | |
106 | inhibitedBy: null | |
107 | }, | |
108 | receivers: ['default'], | |
109 | fingerprint: 'fingerprint' | |
110 | }, | |
111 | { | |
112 | labels: { | |
113 | alertname: 'CephHealthWarning', | |
114 | instance: 'ceph:9283', | |
115 | job: 'ceph', | |
116 | severity: 'warning' | |
117 | }, | |
118 | annotations: { | |
119 | description: 'The cluster state has been HEALTH_WARN for more than 15 minutes.', | |
120 | summary: 'Ceph is in the WARNING state' | |
121 | }, | |
122 | startsAt: '2022-09-28T08:41:38.454Z', | |
123 | endsAt: '2022-09-28T15:28:38.454Z', | |
124 | generatorURL: 'http://prometheus:9090/testUrl', | |
125 | status: { | |
126 | state: 'active', | |
127 | silencedBy: null, | |
128 | inhibitedBy: null | |
129 | }, | |
130 | receivers: ['ceph'], | |
131 | fingerprint: 'fingerprint' | |
132 | } | |
133 | ]; | |
134 | ||
135 | const configValueData: any = 'e90a0d58-658e-4148-8f61-e896c86f0696'; | |
136 | ||
137 | const orchName: any = 'Cephadm'; | |
138 | ||
139 | configureTestBed({ | |
140 | imports: [RouterTestingModule, HttpClientTestingModule, ToastrModule.forRoot(), SharedModule], | |
141 | declarations: [ | |
142 | DashboardV3Component, | |
143 | CardComponent, | |
144 | DashboardPieComponent, | |
145 | CardRowComponent, | |
146 | PgSummaryPipe | |
147 | ], | |
148 | schemas: [NO_ERRORS_SCHEMA], | |
149 | providers: [ | |
150 | { provide: SummaryService, useClass: SummaryServiceMock }, | |
151 | { | |
152 | provide: PrometheusAlertService, | |
153 | useValue: { | |
154 | activeCriticalAlerts: 2, | |
155 | activeWarningAlerts: 1 | |
156 | } | |
157 | }, | |
158 | CssHelper, | |
159 | PgCategoryService | |
160 | ] | |
161 | }); | |
162 | ||
163 | beforeEach(() => { | |
164 | fakeFeatureTogglesService = spyOn(TestBed.inject(FeatureTogglesService), 'get').and.returnValue( | |
165 | of({ | |
166 | rbd: true, | |
167 | mirroring: true, | |
168 | iscsi: true, | |
169 | cephfs: true, | |
170 | rgw: true | |
171 | }) | |
172 | ); | |
173 | fixture = TestBed.createComponent(DashboardV3Component); | |
174 | component = fixture.componentInstance; | |
175 | healthService = TestBed.inject(HealthService); | |
176 | orchestratorService = TestBed.inject(OrchestratorService); | |
177 | getHealthSpy = spyOn(TestBed.inject(HealthService), 'getMinimalHealth'); | |
178 | getHealthSpy.and.returnValue(of(healthPayload)); | |
179 | spyOn(TestBed.inject(PrometheusService), 'ifAlertmanagerConfigured').and.callFake((fn) => fn()); | |
180 | getAlertsSpy = spyOn(TestBed.inject(PrometheusService), 'getAlerts'); | |
181 | getAlertsSpy.and.returnValue(of(alertsPayload)); | |
182 | }); | |
183 | ||
184 | it('should create', () => { | |
185 | expect(component).toBeTruthy(); | |
186 | }); | |
187 | ||
188 | it('should render all cards', () => { | |
189 | fixture.detectChanges(); | |
190 | const dashboardCards = fixture.debugElement.nativeElement.querySelectorAll('cd-card'); | |
191 | expect(dashboardCards.length).toBe(5); | |
192 | }); | |
193 | ||
194 | it('should get corresponding data into detailsCardData', () => { | |
195 | spyOn(healthService, 'getClusterFsid').and.returnValue(of(configValueData)); | |
196 | spyOn(orchestratorService, 'getName').and.returnValue(of(orchName)); | |
197 | component.ngOnInit(); | |
198 | expect(component.detailsCardData.fsid).toBe('e90a0d58-658e-4148-8f61-e896c86f0696'); | |
199 | expect(component.detailsCardData.orchestrator).toBe('Cephadm'); | |
200 | expect(component.detailsCardData.cephVersion).toBe('17.0.0-12222-gcd0cd7cb quincy (dev)'); | |
201 | }); | |
202 | ||
203 | it('should check if the respective icon is shown for each status', () => { | |
204 | const payload = _.cloneDeep(healthPayload); | |
205 | ||
206 | // HEALTH_WARN | |
207 | payload.health['status'] = 'HEALTH_WARN'; | |
208 | payload.health['checks'] = [ | |
209 | { severity: 'HEALTH_WARN', type: 'WRN', summary: { message: 'fake warning' } } | |
210 | ]; | |
211 | ||
212 | getHealthSpy.and.returnValue(of(payload)); | |
213 | fixture.detectChanges(); | |
214 | const clusterStatusCard = fixture.debugElement.query(By.css('cd-card[cardTitle="Status"] i')); | |
215 | expect(clusterStatusCard.nativeElement.title).toEqual(`${payload.health.status}`); | |
216 | ||
217 | // HEALTH_ERR | |
218 | payload.health['status'] = 'HEALTH_ERR'; | |
219 | payload.health['checks'] = [ | |
220 | { severity: 'HEALTH_ERR', type: 'ERR', summary: { message: 'fake error' } } | |
221 | ]; | |
222 | ||
223 | getHealthSpy.and.returnValue(of(payload)); | |
224 | fixture.detectChanges(); | |
225 | expect(clusterStatusCard.nativeElement.title).toEqual(`${payload.health.status}`); | |
226 | ||
227 | // HEALTH_OK | |
228 | payload.health['status'] = 'HEALTH_OK'; | |
229 | payload.health['checks'] = [ | |
230 | { severity: 'HEALTH_OK', type: 'OK', summary: { message: 'fake success' } } | |
231 | ]; | |
232 | ||
233 | getHealthSpy.and.returnValue(of(payload)); | |
234 | fixture.detectChanges(); | |
235 | expect(clusterStatusCard.nativeElement.title).toEqual(`${payload.health.status}`); | |
236 | }); | |
237 | ||
238 | it('should show the actual alert count on each alerts pill', () => { | |
239 | fixture.detectChanges(); | |
240 | ||
241 | const warningAlerts = fixture.debugElement.query(By.css('button[id=warningAlerts] span')); | |
242 | ||
243 | const dangerAlerts = fixture.debugElement.query(By.css('button[id=dangerAlerts] span')); | |
244 | ||
245 | expect(warningAlerts.nativeElement.textContent).toBe('1'); | |
246 | expect(dangerAlerts.nativeElement.textContent).toBe('2'); | |
247 | }); | |
248 | ||
249 | it('should show the critical alerts window and its content', () => { | |
250 | const payload = _.cloneDeep(alertsPayload[0]); | |
251 | component.toggleAlertsWindow('danger'); | |
252 | fixture.detectChanges(); | |
253 | ||
254 | const cardTitle = fixture.debugElement.query(By.css('.tc_alerts h6.card-title')); | |
255 | ||
256 | expect(cardTitle.nativeElement.textContent).toBe(payload.labels.alertname); | |
257 | expect(component.alertType).not.toBe('warning'); | |
258 | }); | |
259 | ||
260 | it('should show the warning alerts window and its content', () => { | |
261 | const payload = _.cloneDeep(alertsPayload[2]); | |
262 | component.toggleAlertsWindow('warning'); | |
263 | fixture.detectChanges(); | |
264 | ||
265 | const cardTitle = fixture.debugElement.query(By.css('.tc_alerts h6.card-title')); | |
266 | ||
267 | expect(cardTitle.nativeElement.textContent).toBe(payload.labels.alertname); | |
268 | expect(component.alertType).not.toBe('critical'); | |
269 | }); | |
270 | ||
271 | it('should only show the pills when the alerts are not empty', () => { | |
272 | spyOn(TestBed.inject(PrometheusAlertService), 'activeCriticalAlerts').and.returnValue(0); | |
273 | spyOn(TestBed.inject(PrometheusAlertService), 'activeWarningAlerts').and.returnValue(0); | |
274 | fixture.detectChanges(); | |
275 | ||
276 | const warningAlerts = fixture.debugElement.query(By.css('button[id=warningAlerts]')); | |
277 | ||
278 | const dangerAlerts = fixture.debugElement.query(By.css('button[id=dangerAlerts]')); | |
279 | ||
280 | expect(warningAlerts).toBe(null); | |
281 | expect(dangerAlerts).toBe(null); | |
282 | }); | |
283 | ||
284 | it('should render "Status" card text that is not clickable', () => { | |
285 | fixture.detectChanges(); | |
286 | ||
287 | const clusterStatusCard = fixture.debugElement.query(By.css('cd-card[cardTitle="Status"]')); | |
288 | const clickableContent = clusterStatusCard.query(By.css('.lead.text-primary')); | |
289 | expect(clickableContent).toBeNull(); | |
290 | }); | |
291 | ||
292 | it('should render "Status" card text that is clickable (popover)', () => { | |
293 | const payload = _.cloneDeep(healthPayload); | |
294 | payload.health['status'] = 'HEALTH_WARN'; | |
295 | payload.health['checks'] = [ | |
296 | { severity: 'HEALTH_WARN', type: 'WRN', summary: { message: 'fake warning' } } | |
297 | ]; | |
298 | ||
299 | getHealthSpy.and.returnValue(of(payload)); | |
300 | fixture.detectChanges(); | |
301 | ||
302 | const clusterStatusCard = fixture.debugElement.query(By.css('cd-card[cardTitle="Status"]')); | |
303 | const clickableContent = clusterStatusCard.query(By.css('.lead.text-primary')); | |
304 | expect(clickableContent).not.toBeNull(); | |
305 | }); | |
306 | ||
307 | describe('features disabled', () => { | |
308 | beforeEach(() => { | |
309 | fakeFeatureTogglesService.and.returnValue( | |
310 | of({ | |
311 | rbd: false, | |
312 | mirroring: false, | |
313 | iscsi: false, | |
314 | cephfs: false, | |
315 | rgw: false | |
316 | }) | |
317 | ); | |
318 | fixture = TestBed.createComponent(DashboardV3Component); | |
319 | component = fixture.componentInstance; | |
320 | }); | |
321 | ||
322 | it('should not render items related to disabled features', () => { | |
323 | fixture.detectChanges(); | |
324 | ||
325 | const iscsiCard = fixture.debugElement.query(By.css('li[id=iscsi-item]')); | |
326 | const rgwCard = fixture.debugElement.query(By.css('li[id=rgw-item]')); | |
327 | const mds = fixture.debugElement.query(By.css('li[id=mds-item]')); | |
328 | ||
329 | expect(iscsiCard).toBeFalsy(); | |
330 | expect(rgwCard).toBeFalsy(); | |
331 | expect(mds).toBeFalsy(); | |
332 | }); | |
333 | }); | |
334 | }); |