Skip to content

Commit 969bc1f

Browse files
Merge pull request #857 from DavidRajnoha/test/incident-coo-1.4.0-features
OBSINTA-993: Tests for incident detection coo 1.4.0 changes
2 parents 263f42c + 0ce6466 commit 969bc1f

9 files changed

Lines changed: 3686 additions & 5 deletions

File tree

Lines changed: 165 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,165 @@
1+
/*
2+
Regression test for mixed severity interval boundary times (Section 2.3.3).
3+
4+
Test 1 (OU-1205): Verifies tooltip End times at severity interval boundaries
5+
are rounded to 5-minute precision. Without rounding, consecutive interval end
6+
times can land on non-5-minute values (e.g., "10:58" instead of "11:00").
7+
8+
Test 2 (OU-1221, XFAIL): Verifies Start times shown in incident tooltips match
9+
alert tooltips and alerts table, and that consecutive segment boundaries align
10+
with no 5-minute gap between End of one segment and Start of the next.
11+
*/
12+
13+
import { incidentsPage } from '../../../views/incidents-page';
14+
15+
const MCP = {
16+
namespace: Cypress.env('COO_NAMESPACE'),
17+
packageName: 'cluster-observability-operator',
18+
operatorName: 'Cluster Observability Operator',
19+
config: {
20+
kind: 'UIPlugin',
21+
name: 'monitoring',
22+
},
23+
};
24+
25+
const MP = {
26+
namespace: 'openshift-monitoring',
27+
operatorName: 'Cluster Monitoring Operator',
28+
};
29+
30+
describe('Regression: Mixed Severity Interval Boundary Times', { tags: ['@incidents', '@xfail'] }, () => {
31+
32+
before(() => {
33+
cy.beforeBlockCOO(MCP, MP, { dashboards: false, troubleshootingPanel: false });
34+
});
35+
36+
beforeEach(() => {
37+
cy.mockIncidentFixture('incident-scenarios/21-multi-severity-boundary-times.yaml');
38+
});
39+
40+
const extractTime = (tooltipText: string, field: 'Start' | 'End'): string => {
41+
const afterField = tooltipText.split(field)[1] || '';
42+
const match = afterField.match(/(\d{1,2}:\d{2}(\s*[AP]M)?)/);
43+
return match ? match[1].trim() : '';
44+
};
45+
46+
it('1. Tooltip End times at severity boundaries show 5-minute rounded values', () => {
47+
const verifyEndTimeRounded = (label: string) => {
48+
incidentsPage.elements.tooltip()
49+
.invoke('text')
50+
.then((text) => {
51+
cy.log(`${label} tooltip: "${text}"`);
52+
53+
if (text.match(/End.*---/)) {
54+
cy.log(`${label}: Firing, End shows --- (skipped)`);
55+
return;
56+
}
57+
58+
const endPart = text.split('End')[1];
59+
expect(endPart, `${label}: should contain End time`).to.exist;
60+
61+
const timeMatch = endPart.match(/(\d{1,2}):(\d{2})/);
62+
expect(timeMatch, `${label}: End time should be parseable`).to.not.be.null;
63+
64+
const minutes = parseInt(timeMatch[2], 10);
65+
const remainder = minutes % 5;
66+
expect(remainder, `${label}: End minutes (${minutes}) should be divisible by 5, remainder=${remainder}`).to.equal(0);
67+
cy.log(`${label}: End ${timeMatch[1]}:${timeMatch[2]} - minutes divisible by 5`);
68+
});
69+
};
70+
71+
cy.log('1.1 Verify multi-severity incident loaded');
72+
incidentsPage.clearAllFilters();
73+
incidentsPage.setDays('1 day');
74+
incidentsPage.elements.incidentsChartContainer().should('be.visible');
75+
incidentsPage.elements.incidentsChartBarsGroups().should('have.length', 1);
76+
77+
cy.log('1.2 Verify bar has multiple severity segments');
78+
incidentsPage.getIncidentBarVisibleSegments(0).then((segments) => {
79+
expect(segments.length, 'Multi-severity bar should have at least 2 visible segments')
80+
.to.be.greaterThan(1);
81+
cy.log(`Found ${segments.length} visible severity segments`);
82+
});
83+
84+
cy.log('1.3 Check first segment end time (Info -> Warning boundary)');
85+
incidentsPage.hoverOverIncidentBarSegment(0, 0);
86+
verifyEndTimeRounded('First segment');
87+
88+
cy.log('1.4 Check second segment end time (Warning -> Critical boundary)');
89+
incidentsPage.hoverOverIncidentBarSegment(0, 1);
90+
verifyEndTimeRounded('Second segment');
91+
92+
cy.log('1.5 Check third segment end time (Critical end)');
93+
incidentsPage.hoverOverIncidentBarSegment(0, 2);
94+
verifyEndTimeRounded('Third segment');
95+
96+
cy.log('Verified: All tooltip End times at severity boundaries are at 5-minute precision (OU-1205)');
97+
});
98+
99+
it('2. Start times match between incident tooltip, alert tooltip, and table; consecutive boundaries align',
100+
() => {
101+
cy.log('2.1 Setup: verify multi-severity incident loaded');
102+
incidentsPage.clearAllFilters();
103+
incidentsPage.setDays('1 day');
104+
incidentsPage.elements.incidentsChartContainer().should('be.visible');
105+
incidentsPage.elements.incidentsChartBarsGroups().should('have.length', 1);
106+
cy.pause();
107+
108+
cy.log('2.2 Consecutive interval boundaries: End of segment 1 should equal Start of segment 2');
109+
incidentsPage.hoverOverIncidentBarSegment(0, 0);
110+
incidentsPage.elements.tooltip().invoke('text').then((firstText) => {
111+
const firstEnd = extractTime(firstText, 'End');
112+
cy.log(`First segment End: ${firstEnd}`);
113+
expect(firstEnd, 'First segment End should be parseable').to.not.be.empty;
114+
115+
incidentsPage.hoverOverIncidentBarSegment(0, 1);
116+
incidentsPage.elements.tooltip().invoke('text').then((secondText) => {
117+
const secondStart = extractTime(secondText, 'Start');
118+
cy.log(`Second segment Start: ${secondStart}`);
119+
expect(secondStart, 'Second segment Start should be parseable').to.not.be.empty;
120+
expect(secondStart,
121+
`No 5-min gap: second Start (${secondStart}) should equal first End (${firstEnd})`
122+
).to.equal(firstEnd);
123+
});
124+
});
125+
cy.pause();
126+
127+
cy.log('2.3 Incident tooltip Start vs alert tooltip Start vs alerts table Start');
128+
incidentsPage.hoverOverIncidentBarSegment(0, 0);
129+
incidentsPage.elements.tooltip().invoke('text').then((incidentText) => {
130+
const incidentStart = extractTime(incidentText, 'Start');
131+
cy.log(`Incident tooltip Start: ${incidentStart}`);
132+
expect(incidentStart, 'Incident Start should be parseable').to.not.be.empty;
133+
134+
cy.log('2.4 Select incident and get alert tooltip Start');
135+
incidentsPage.selectIncidentById('monitoring-multi-severity-boundary-001');
136+
incidentsPage.elements.alertsChartCard().should('be.visible');
137+
138+
incidentsPage.hoverOverAlertBar(0);
139+
incidentsPage.elements.alertsChartTooltip().invoke('text').then((alertText) => {
140+
const alertStart = extractTime(alertText, 'Start');
141+
cy.log(`Alert tooltip Start: ${alertStart}`);
142+
expect(incidentStart,
143+
`Incident Start (${incidentStart}) should match alert Start (${alertStart})`
144+
).to.equal(alertStart);
145+
});
146+
147+
cy.log('2.5 Compare incident tooltip Start with alerts table Start');
148+
incidentsPage.getSelectedIncidentAlerts().then((alerts) => {
149+
expect(alerts.length, 'Should have at least 1 alert row').to.be.greaterThan(0);
150+
alerts[0].getStartCell().invoke('text').then((cellText) => {
151+
const tableMatch = cellText.trim().match(/(\d{1,2}:\d{2}(\s*[AP]M)?)/);
152+
expect(tableMatch, 'Table Start time should be parseable').to.not.be.null;
153+
const tableStart = tableMatch[1].trim();
154+
cy.log(`Alerts table Start: ${tableStart}`);
155+
expect(incidentStart,
156+
`Incident Start (${incidentStart}) should match table Start (${tableStart})`
157+
).to.equal(tableStart);
158+
});
159+
});
160+
});
161+
cy.pause();
162+
163+
cy.log('Expected failure: Incident tooltip Start times are 5 minutes off (OU-1221)');
164+
});
165+
});

web/cypress/e2e/incidents/regression/03.reg_api_calls.cy.ts

Lines changed: 45 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,20 @@
11
/*
2-
Regression test for Silences Not Applied Correctly (Section 3.2)
2+
Regression tests for API Calls and Data Loading (Section 3)
33
4-
BUG: Silences were being matched by name only, not by name + namespace + severity.
5-
This test verifies that silence matching uses: alertname + namespace + severity.
4+
Tests:
5+
1. Silences Not Applied Correctly (Section 3.2)
6+
BUG: Silences were being matched by name only, not by name + namespace + severity.
7+
This test verifies that silence matching uses: alertname + namespace + severity.
68
7-
While targeting the bug, it verifies the basic Silences Implementation.
9+
2. Permission Denied Handling (Section 3.5)
10+
Tests graceful handling of 403 Forbidden responses from rules/silences endpoints.
11+
Incidents page should still function when user lacks permissions to view rules/silences.
812
9-
Verifies: OU-1020, OU-706
13+
Verifies: OU-1020, OU-706, OU-1213
1014
*/
1115

1216
import { incidentsPage } from '../../../views/incidents-page';
17+
import { nav } from '../../../views/nav';
1318

1419
const MCP = {
1520
namespace: Cypress.env('COO_NAMESPACE'),
@@ -123,4 +128,39 @@ describe('Regression: Silences Not Applied Correctly', { tags: ['@incidents'] },
123128
});
124129
});
125130

131+
describe('Regression: Permission Denied Handling', { tags: ['@incidents'] }, () => {
126132

133+
before(() => {
134+
cy.beforeBlockCOO(MCP, MP, { dashboards: false, troubleshootingPanel: false });
135+
});
136+
137+
beforeEach(() => {
138+
cy.log('Mock all API endpoints as 403 Forbidden');
139+
cy.mockPermissionDenied();
140+
cy.log('Navigate to Observe -> Incidents');
141+
// Using custom navigation commands to avoid waiting for the page to load which never happens in this test
142+
nav.sidenav.clickNavLink(['Observe', 'Alerting']);
143+
nav.tabs.switchTab('Incidents');
144+
145+
});
146+
147+
it('Page displays access denied state when all API endpoints return 403 Forbidden', () => {
148+
cy.log('1.1 Verify 403 requests were intercepted');
149+
const waitTimeout = { timeout: 120000 };
150+
cy.wait('@rulesPermissionDenied', waitTimeout)
151+
.its('response').should('exist')
152+
.its('statusCode').should('eq', 403);
153+
cy.wait('@silencesPermissionDenied', waitTimeout)
154+
.its('response').should('exist')
155+
.its('statusCode').should('eq', 403);
156+
cy.wait('@prometheusQueryRangePermissionDenied', waitTimeout)
157+
.its('response').should('exist')
158+
.its('statusCode').should('eq', 403);
159+
160+
cy.log('1.2 Verify access denied empty state is displayed');
161+
cy.byTestID('access-denied').should('be.visible');
162+
cy.byTestID('access-denied').should('contain.text', 'You don\'t have access to this section due to cluster policy');
163+
164+
cy.log('Verified: Page displays restricted access state for permission denied');
165+
});
166+
});
Lines changed: 96 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,96 @@
1+
/*
2+
Regression tests for UI issues that manifest under stress conditions (high alert/incident counts).
3+
4+
Section 5.1: Alerts chart padding with 100+ alerts (OU-1123)
5+
- Height calculation should not produce excessive padding pushing bars below the visible area.
6+
- A smaller gap with ~500+ alerts is accepted as a known limitation.
7+
8+
Note: Stress fixtures use a short timeline (1h) to avoid a known Math.max/min call stack
9+
overflow when many alerts share a group_id with long timelines. The padding behavior under
10+
test is independent of timeline length. See WIP.stress-test-1000-alerts.cy.ts to reproduce
11+
the overflow with the original 5d timeline.
12+
*/
13+
14+
import { incidentsPage } from '../../../views/incidents-page';
15+
16+
const MCP = {
17+
namespace: Cypress.env('COO_NAMESPACE'),
18+
packageName: 'cluster-observability-operator',
19+
operatorName: 'Cluster Observability Operator',
20+
config: {
21+
kind: 'UIPlugin',
22+
name: 'monitoring',
23+
},
24+
};
25+
26+
const MP = {
27+
namespace: 'openshift-monitoring',
28+
operatorName: 'Cluster Monitoring Operator',
29+
};
30+
31+
const MAX_GAP_STANDARD = 250;
32+
const MAX_GAP_RELAXED = 500;
33+
34+
describe('Regression: Stress Testing UI', { tags: ['@incidents'] }, () => {
35+
36+
before(() => {
37+
cy.beforeBlockCOO(MCP, MP, { dashboards: false, troubleshootingPanel: false });
38+
});
39+
40+
it('5.1 No excessive padding between chart top and alert bars for 100, 200, and 500 alerts', () => {
41+
const verifyAlertBarPadding = (
42+
fixtureFile: string,
43+
incidentId: string,
44+
maxGap: number,
45+
label: string
46+
) => {
47+
cy.mockIncidentFixture(`incident-scenarios/${fixtureFile}`);
48+
incidentsPage.clearAllFilters();
49+
incidentsPage.setDays('1 day');
50+
51+
incidentsPage.elements.incidentsChartBarsGroups().should('have.length', 1);
52+
incidentsPage.selectIncidentById(incidentId);
53+
54+
incidentsPage.elements.alertsChartCard().should('be.visible');
55+
incidentsPage.elements.alertsChartBarsVisiblePaths()
56+
.should('have.length.greaterThan', 0);
57+
58+
incidentsPage.elements.alertsChartContainer().first().scrollIntoView();
59+
incidentsPage.elements.alertsChartContainer().first().then(($container) => {
60+
const containerTop = $container[0].getBoundingClientRect().top;
61+
incidentsPage.getAlertBarRect(0).then((barRect) => {
62+
const gap = barRect.top - containerTop;
63+
cy.log(`${label}: Gap between container top and first alert bar = ${gap}px`);
64+
expect(gap, `${label}: first alert bar should start near chart top`).to.be.lessThan(maxGap);
65+
expect(gap, `${label}: gap should be non-negative`).to.be.at.least(0);
66+
});
67+
});
68+
};
69+
70+
cy.log('5.1.1 Verify no excessive padding with 100 alerts');
71+
verifyAlertBarPadding(
72+
'15-stress-test-100-alerts.yaml',
73+
'cluster-wide-failure-100-alerts',
74+
MAX_GAP_STANDARD,
75+
'100 alerts'
76+
);
77+
78+
cy.log('5.1.2 Verify no excessive padding with 200 alerts');
79+
verifyAlertBarPadding(
80+
'16-stress-test-200-alerts.yaml',
81+
'cluster-wide-failure-200-alerts',
82+
MAX_GAP_STANDARD,
83+
'200 alerts'
84+
);
85+
86+
cy.log('5.1.3 Verify accepted limitation with 500 alerts (relaxed threshold)');
87+
verifyAlertBarPadding(
88+
'17-stress-test-500-alerts.yaml',
89+
'cluster-wide-failure-500-alerts',
90+
MAX_GAP_RELAXED,
91+
'500 alerts'
92+
);
93+
94+
cy.log('Verified: Alert bars have no excessive padding for 100, 200, and 500 alerts');
95+
});
96+
});

0 commit comments

Comments
 (0)