Skip to content

Commit 0f3a68b

Browse files
test: [DI-23514] - Add Automation for edit functionality of user defined alert. (linode#11719)
* test[DI-23514]:Add Automation for Edit functionality of User defined alert * test[DI-23514]:Add Automation for Edit functionality of User defined alert * fixing issue of the specs * Moving tooltip messages to the constants file * fixing timerange-verification spec issue * fixing timerange-verification spec issue * fixing timerange-verification spec issue * Rename the variable isStart instead of isCurrent.
1 parent 83c03e2 commit 0f3a68b

File tree

5 files changed

+426
-16
lines changed

5 files changed

+426
-16
lines changed
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
"@linode/manager": Tests
3+
---
4+
5+
Add Automation for edit functionality of user defined alert ([#11719](https://github.com/linode/manager/pull/11719))
Lines changed: 381 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,381 @@
1+
/**
2+
* @file Integration Tests for the CloudPulse Edit Alert Page.
3+
*
4+
* This file contains Cypress tests for the Edit Alert page of the CloudPulse application.
5+
* It verifies that alert details are correctly displayed, interactive, and editable.
6+
*/
7+
8+
import {
9+
EVALUATION_PERIOD_DESCRIPTION,
10+
METRIC_DESCRIPTION_DATA_FIELD,
11+
POLLING_INTERVAL_DESCRIPTION,
12+
SEVERITY_LEVEL_DESCRIPTION,
13+
} from 'support/constants/cloudpulse';
14+
import { widgetDetails } from 'support/constants/widgets';
15+
import { mockGetAccount } from 'support/intercepts/account';
16+
import {
17+
mockCreateAlertDefinition,
18+
mockGetAlertChannels,
19+
mockGetAlertDefinitions,
20+
mockGetAllAlertDefinitions,
21+
mockGetCloudPulseMetricDefinitions,
22+
mockGetCloudPulseServices,
23+
mockUpdateAlertDefinitions,
24+
} from 'support/intercepts/cloudpulse';
25+
import { mockGetDatabases } from 'support/intercepts/databases';
26+
import { mockAppendFeatureFlags } from 'support/intercepts/feature-flags';
27+
import { mockGetRegions } from 'support/intercepts/regions';
28+
import { ui } from 'support/ui';
29+
30+
import {
31+
accountFactory,
32+
alertDefinitionFactory,
33+
alertFactory,
34+
cpuRulesFactory,
35+
dashboardMetricFactory,
36+
databaseFactory,
37+
memoryRulesFactory,
38+
notificationChannelFactory,
39+
regionFactory,
40+
triggerConditionFactory,
41+
} from 'src/factories';
42+
import { formatDate } from 'src/utilities/formatDate';
43+
44+
import type { Database } from '@linode/api-v4';
45+
import type { Flags } from 'src/featureFlags';
46+
47+
// Feature flag setup
48+
const flags: Partial<Flags> = { aclp: { beta: true, enabled: true } };
49+
const mockAccount = accountFactory.build();
50+
51+
// Mock alert definition
52+
const customAlertDefinition = alertDefinitionFactory.build({
53+
channel_ids: [1],
54+
description: 'update-description',
55+
entity_ids: ['1', '2', '3', '4', '5'],
56+
label: 'Alert-1',
57+
rule_criteria: {
58+
rules: [cpuRulesFactory.build(), memoryRulesFactory.build()],
59+
},
60+
severity: 0,
61+
tags: [''],
62+
trigger_conditions: triggerConditionFactory.build(),
63+
});
64+
65+
// Mock alert details
66+
const alertDetails = alertFactory.build({
67+
alert_channels: [{ id: 1 }],
68+
created_by: 'user1',
69+
description: 'My Custom Description',
70+
entity_ids: ['2'],
71+
label: 'Alert-2',
72+
rule_criteria: {
73+
rules: [cpuRulesFactory.build(), memoryRulesFactory.build()],
74+
},
75+
service_type: 'dbaas',
76+
severity: 0,
77+
tags: [''],
78+
trigger_conditions: triggerConditionFactory.build(),
79+
type: 'user',
80+
updated: new Date().toISOString(),
81+
});
82+
83+
const { description, id, label, service_type, updated } = alertDetails;
84+
85+
// Mock regions
86+
const regions = [
87+
regionFactory.build({
88+
capabilities: ['Managed Databases'],
89+
id: 'us-ord',
90+
label: 'Chicago, IL',
91+
}),
92+
regionFactory.build({
93+
capabilities: ['Managed Databases'],
94+
id: 'us-east',
95+
label: 'Newark',
96+
}),
97+
];
98+
99+
// Mock databases
100+
const databases: Database[] = databaseFactory.buildList(5).map((db, index) => ({
101+
...db,
102+
engine: 'mysql',
103+
id: index,
104+
region: regions[index % regions.length].id,
105+
status: 'active',
106+
type: 'MySQL',
107+
}));
108+
109+
// Mock metric definitions
110+
const { metrics } = widgetDetails.dbaas;
111+
const metricDefinitions = metrics.map(({ name, title, unit }) =>
112+
dashboardMetricFactory.build({ label: title, metric: name, unit })
113+
);
114+
115+
// Mock notification channels
116+
const notificationChannels = notificationChannelFactory.build({
117+
channel_type: 'email',
118+
id: 1,
119+
label: 'Channel-1',
120+
type: 'custom',
121+
});
122+
123+
describe('Integration Tests for Edit Alert', () => {
124+
/*
125+
* - Confirms that the Edit Alert page loads with the correct alert details.
126+
* - Verifies that the alert form contains the appropriate pre-filled data from the mock alert.
127+
* - Confirms that rule criteria values are correctly displayed.
128+
* - Verifies that the correct notification channel details are displayed.
129+
* - Ensures the tooltip descriptions for the alert configuration are visible and contain the correct content.
130+
* - Confirms that the correct regions, databases, and metrics are available for selection in the form.
131+
* - Verifies that the user can successfully edit and submit changes to the alert.
132+
* - Confirms that the UI handles updates to alert data correctly and submits them via the API.
133+
* - Confirms that the API request matches the expected data structure and values upon saving the updated alert.
134+
* - Verifies that the user is redirected back to the Alert Definitions List page after saving changes.
135+
* - Ensures a success toast notification appears after the alert is updated.
136+
* - Confirms that the alert is listed correctly with the updated configuration on the Alert Definitions List page.
137+
*/
138+
beforeEach(() => {
139+
// Mocking various API responses
140+
mockAppendFeatureFlags(flags);
141+
mockGetAccount(mockAccount);
142+
mockGetRegions(regions);
143+
mockGetCloudPulseServices([alertDetails.service_type]);
144+
mockGetAllAlertDefinitions([alertDetails]).as('getAlertDefinitionsList');
145+
mockGetAlertDefinitions(service_type, id, alertDetails).as(
146+
'getAlertDefinitions'
147+
);
148+
mockGetDatabases(databases).as('getDatabases');
149+
mockUpdateAlertDefinitions(service_type, id, alertDetails).as(
150+
'updateDefinitions'
151+
);
152+
mockCreateAlertDefinition(service_type, customAlertDefinition).as(
153+
'createAlertDefinition'
154+
);
155+
mockGetCloudPulseMetricDefinitions(service_type, metricDefinitions);
156+
mockGetAlertChannels([notificationChannels]);
157+
});
158+
159+
// Define an interface for rule values
160+
interface RuleCriteria {
161+
aggregationType: string;
162+
dataField: string;
163+
operator: string;
164+
threshold: string;
165+
}
166+
167+
// Mapping of interface keys to data attributes
168+
const fieldSelectors: Record<keyof RuleCriteria, string> = {
169+
aggregationType: 'aggregation-type',
170+
dataField: 'data-field',
171+
operator: 'operator',
172+
threshold: 'threshold',
173+
};
174+
175+
// Function to assert rule values
176+
const assertRuleValues = (ruleIndex: number, rule: RuleCriteria) => {
177+
cy.get(`[data-testid="rule_criteria.rules.${ruleIndex}-id"]`).within(() => {
178+
(Object.keys(rule) as (keyof RuleCriteria)[]).forEach((key) => {
179+
cy.get(
180+
`[data-qa-metric-threshold="rule_criteria.rules.${ruleIndex}-${fieldSelectors[key]}"]`
181+
)
182+
.should('be.visible')
183+
.find('input')
184+
.should('have.value', rule[key]);
185+
});
186+
});
187+
};
188+
189+
it('should correctly display the details of the alert in the Edit Alert page', () => {
190+
cy.visitWithLogin(`/monitor/alerts/definitions/edit/${service_type}/${id}`);
191+
cy.wait('@getAlertDefinitions');
192+
193+
// Verify form fields
194+
cy.findByLabelText('Name').should('have.value', label);
195+
cy.findByLabelText('Description (optional)').should(
196+
'have.value',
197+
description
198+
);
199+
cy.findByLabelText('Service')
200+
.should('be.disabled')
201+
.should('have.value', 'Databases');
202+
cy.findByLabelText('Severity').should('have.value', 'Severe');
203+
204+
// Verify alert resource selection
205+
cy.get('[data-qa-alert-table="true"]')
206+
.contains('[data-qa-alert-cell*="resource"]', 'database-3')
207+
.parents('tr')
208+
.find('[type="checkbox"]')
209+
.should('be.checked');
210+
211+
// Verify alert resource selection count message
212+
cy.get('[data-testid="selection_notice"]').should(
213+
'contain',
214+
'1 of 5 resources are selected.'
215+
);
216+
217+
// Assert rule values 1
218+
assertRuleValues(0, {
219+
aggregationType: 'Average',
220+
dataField: 'CPU Utilization',
221+
operator: '==',
222+
threshold: '1000',
223+
});
224+
225+
// Assert rule values 2
226+
assertRuleValues(1, {
227+
aggregationType: 'Average',
228+
dataField: 'Memory Usage',
229+
operator: '==',
230+
threshold: '1000',
231+
});
232+
233+
// Verify that tooltip messages are displayed correctly with accurate content.
234+
ui.tooltip.findByText(METRIC_DESCRIPTION_DATA_FIELD).should('be.visible');
235+
ui.tooltip.findByText(SEVERITY_LEVEL_DESCRIPTION).should('be.visible');
236+
ui.tooltip.findByText(EVALUATION_PERIOD_DESCRIPTION).should('be.visible');
237+
ui.tooltip.findByText(POLLING_INTERVAL_DESCRIPTION).should('be.visible');
238+
239+
// Assert dimension filters
240+
const dimensionFilters = [
241+
{ field: 'State of CPU', operator: 'Equal', value: 'User' },
242+
];
243+
244+
dimensionFilters.forEach((filter, index) => {
245+
cy.get(
246+
`[data-qa-dimension-filter="rule_criteria.rules.0.dimension_filters.${index}-data-field"]`
247+
)
248+
.should('be.visible')
249+
.find('input')
250+
.should('have.value', filter.field);
251+
252+
cy.get(
253+
`[data-qa-dimension-filter="rule_criteria.rules.0.dimension_filters.${index}-operator"]`
254+
)
255+
.should('be.visible')
256+
.find('input')
257+
.should('have.value', filter.operator);
258+
259+
cy.get(
260+
`[data-qa-dimension-filter="rule_criteria.rules.0.dimension_filters.${index}-value"]`
261+
)
262+
.should('be.visible')
263+
.find('input')
264+
.should('have.value', filter.value);
265+
});
266+
267+
// Verify notification details
268+
cy.get('[data-qa-notification="notification-channel-0"]').within(() => {
269+
cy.get('[data-qa-channel]').should('have.text', 'Channel-1');
270+
cy.get('[data-qa-type]').next().should('have.text', 'Email');
271+
cy.get('[data-qa-channel-details]').should(
272+
'have.text',
273+
'[email protected]@test.com'
274+
);
275+
});
276+
});
277+
278+
it('successfully updated alert details and verified that the API request matches the expected test data.', () => {
279+
cy.visitWithLogin(`/monitor/alerts/definitions/edit/${service_type}/${id}`);
280+
cy.wait('@getAlertDefinitions');
281+
282+
// Make changes to alert form
283+
cy.findByLabelText('Name').clear();
284+
cy.findByLabelText('Name').type('Alert-2');
285+
cy.findByLabelText('Description (optional)').clear();
286+
cy.findByLabelText('Description (optional)').type('update-description');
287+
cy.findByLabelText('Service').should('be.disabled');
288+
ui.autocomplete.findByLabel('Severity').clear();
289+
ui.autocomplete.findByLabel('Severity').type('Info');
290+
ui.autocompletePopper.findByTitle('Info').should('be.visible').click();
291+
cy.get('[data-qa-notice="true"]')
292+
.find('button')
293+
.contains('Select All')
294+
.click();
295+
296+
cy.get(
297+
'[data-qa-metric-threshold="rule_criteria.rules.0-data-field"]'
298+
).within(() => {
299+
ui.button.findByAttribute('aria-label', 'Clear').click();
300+
});
301+
302+
cy.get('[data-testid="rule_criteria.rules.0-id"]').within(() => {
303+
ui.autocomplete.findByLabel('Data Field').type('Disk I/O');
304+
ui.autocompletePopper.findByTitle('Disk I/O').click();
305+
ui.autocomplete.findByLabel('Aggregation Type').type('Minimum');
306+
ui.autocompletePopper.findByTitle('Minimum').click();
307+
ui.autocomplete.findByLabel('Operator').type('>');
308+
ui.autocompletePopper.findByTitle('>').click();
309+
cy.get('[data-qa-threshold]').should('be.visible').clear();
310+
cy.get('[data-qa-threshold]').should('be.visible').type('2000');
311+
});
312+
313+
// click on the submit button
314+
ui.buttonGroup
315+
.find()
316+
.find('button')
317+
.filter('[type="submit"]')
318+
.should('be.visible')
319+
.should('be.enabled')
320+
.click();
321+
322+
cy.wait('@updateDefinitions').then(({ request }) => {
323+
// Assert the API request data
324+
expect(request.body.label).to.equal('Alert-2');
325+
expect(request.body.description).to.equal('update-description');
326+
expect(request.body.severity).to.equal(3);
327+
expect(request.body.entity_ids).to.have.members([
328+
'0',
329+
'1',
330+
'2',
331+
'3',
332+
'4',
333+
]);
334+
expect(request.body.channel_ids[0]).to.equal(1);
335+
expect(request.body).to.have.property('trigger_conditions');
336+
expect(request.body.trigger_conditions.criteria_condition).to.equal(
337+
'ALL'
338+
);
339+
expect(
340+
request.body.trigger_conditions.evaluation_period_seconds
341+
).to.equal(300);
342+
expect(request.body.trigger_conditions.polling_interval_seconds).to.equal(
343+
300
344+
);
345+
expect(request.body.trigger_conditions.trigger_occurrences).to.equal(5);
346+
expect(request.body.rule_criteria.rules[0].threshold).to.equal(2000);
347+
expect(request.body.rule_criteria.rules[0].operator).to.equal('gt');
348+
expect(request.body.rule_criteria.rules[0].aggregate_function).to.equal(
349+
'min'
350+
);
351+
expect(request.body.rule_criteria.rules[0].metric).to.equal(
352+
'system_disk_OPS_total'
353+
);
354+
expect(request.body.rule_criteria.rules[1].aggregate_function).to.equal(
355+
'avg'
356+
);
357+
expect(request.body.rule_criteria.rules[1].metric).to.equal(
358+
'system_memory_usage_by_resource'
359+
);
360+
expect(request.body.rule_criteria.rules[1].operator).to.equal('eq');
361+
expect(request.body.rule_criteria.rules[1].threshold).to.equal(1000);
362+
363+
// Verify URL redirection and toast notification
364+
cy.url().should('endWith', 'monitor/alerts/definitions');
365+
ui.toast.assertMessage('Alert successfully updated.');
366+
367+
// Confirm that Alert is listed on landing page with expected configuration.
368+
cy.findByText('Alert-2')
369+
.closest('tr')
370+
.within(() => {
371+
cy.findByText('Alert-2').should('be.visible');
372+
cy.findByText('Enabled').should('be.visible');
373+
cy.findByText('Databases').should('be.visible');
374+
cy.findByText('user1').should('be.visible');
375+
cy.findByText(
376+
formatDate(updated, { format: 'MMM dd, yyyy, h:mm a' })
377+
).should('be.visible');
378+
});
379+
});
380+
});
381+
});

0 commit comments

Comments
 (0)