Skip to content

Commit 4fcc96c

Browse files
test: [DI-30016] - Add spec for Edit notification channel (#13431)
* test: [DI-30016] - Add spec for Edit notification channel * Added changeset: Added automation spec for edit notifiation channel --------- Co-authored-by: shnagend-akamai <142887750+shnagend@users.noreply.github.com> Co-authored-by: santoshp210-akamai <159890961+santoshp210-akamai@users.noreply.github.com>
1 parent 9e2ebb8 commit 4fcc96c

3 files changed

Lines changed: 265 additions & 0 deletions

File tree

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+
Added automation spec for edit notifiation channel ([#13431](https://github.com/linode/manager/pull/13431))
Lines changed: 194 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,194 @@
1+
/**
2+
* @file Integration Tests for CloudPulse Alerting — Notification Channel Edit Validation
3+
*/
4+
import { profileFactory } from '@linode/utilities';
5+
import { mockGetAccount, mockGetUsers } from 'support/intercepts/account';
6+
import {
7+
mockGetAlertChannelById,
8+
mockGetAlertChannels,
9+
mockUpdateAlertChannelById,
10+
mockUpdateAlertChannelByIdError,
11+
} from 'support/intercepts/cloudpulse';
12+
import { mockAppendFeatureFlags } from 'support/intercepts/feature-flags';
13+
import { mockGetProfile } from 'support/intercepts/profile';
14+
import { ui } from 'support/ui';
15+
16+
import {
17+
accountFactory,
18+
accountUserFactory,
19+
flagsFactory,
20+
notificationChannelFactory,
21+
} from 'src/factories';
22+
import { UPDATE_CHANNEL_SUCCESS_MESSAGE } from 'src/features/CloudPulse/Alerts/constants';
23+
24+
// Define mock data for the test.
25+
26+
const mockAccount = accountFactory.build();
27+
const mockUsers = [
28+
accountUserFactory.build({ username: 'user1' }),
29+
accountUserFactory.build({ username: 'user2' }),
30+
...accountUserFactory.buildList(6),
31+
];
32+
const mockProfile = profileFactory.build({
33+
restricted: false,
34+
});
35+
const notificationChannels = notificationChannelFactory.buildList(5);
36+
const editNotificationChannel = notificationChannelFactory.build({
37+
label: 'Test Channel Name',
38+
channel_type: 'email',
39+
details: {
40+
email: {
41+
usernames: ['user1', 'user2'],
42+
},
43+
},
44+
});
45+
const { id, label } = editNotificationChannel;
46+
47+
describe('CloudPulse Alerting - Notification Channel Edit Validation', () => {
48+
/**
49+
* Verifies successful editing of an existing email notification channel with a success snackbar.
50+
* Verifies the update payload sent to the API and the UI listing after the channel is edited.
51+
* Verifies server error handling during channel update failures.
52+
*/
53+
beforeEach(() => {
54+
mockAppendFeatureFlags(flagsFactory.build());
55+
mockGetAccount(mockAccount);
56+
mockGetProfile(mockProfile);
57+
mockGetAlertChannels([...notificationChannels, editNotificationChannel]).as(
58+
'getAlertNotificationChannels'
59+
);
60+
mockGetAlertChannelById(id, editNotificationChannel).as(
61+
'getAlertChannelById'
62+
);
63+
mockGetUsers(mockUsers).as('getAccountUsers');
64+
mockUpdateAlertChannelById(id, {
65+
...editNotificationChannel,
66+
label: 'Updated Channel Name',
67+
}).as('updateAlertChannelById');
68+
69+
// Visit Notification Channels page
70+
cy.visitWithLogin('/alerts/notification-channels');
71+
});
72+
it('should verify edit action menu allows edit of the notification channel, verify payload and UI listing', () => {
73+
// Wait for initial data load
74+
cy.wait('@getAlertNotificationChannels');
75+
76+
// Select the first notification channel to edit
77+
ui.actionMenu
78+
.findByTitle('Action menu for Notification Channel ' + label)
79+
.click();
80+
ui.actionMenuItem.findByTitle('Edit').click();
81+
82+
cy.wait('@getAlertChannelById');
83+
84+
cy.url().should('include', '/alerts/notification-channels/edit/' + id);
85+
86+
// Modify the channel label
87+
const newChannelLabel = 'Updated Channel Name';
88+
89+
cy.findByLabelText('Type').should('be.disabled').and('have.value', 'Email');
90+
cy.findByLabelText('Name').clear();
91+
cy.findByLabelText('Name').type(newChannelLabel);
92+
93+
cy.findByLabelText('Recipients').click();
94+
95+
// Remove one recipient
96+
cy.contains('.MuiChip-label', 'user1')
97+
.should('be.visible')
98+
.parents('.MuiChip-root')
99+
.find('.MuiChip-deleteIcon')
100+
.click();
101+
// Add a different recipient
102+
ui.autocompletePopper.findByTitle('user-3').click();
103+
104+
// Click the Save button
105+
ui.buttonGroup
106+
.findButtonByTitle('Save')
107+
.should('be.visible')
108+
.should('be.enabled')
109+
.click();
110+
111+
// Verify success toast
112+
ui.toast.assertMessage(UPDATE_CHANNEL_SUCCESS_MESSAGE);
113+
114+
// Validate the request payload data
115+
cy.wait('@updateAlertChannelById').then((interception) => {
116+
expect(interception)
117+
.to.have.property('response')
118+
.with.property('statusCode', 200);
119+
120+
const payload = interception.request.body;
121+
122+
// Top-level fields
123+
expect(payload.label).to.equal(newChannelLabel);
124+
125+
// Email details validation
126+
expect(payload.details).to.have.property('email');
127+
expect(payload.details.email.usernames).to.have.length(2);
128+
129+
const expectedRecipients = ['user2', 'user-3'];
130+
131+
expectedRecipients.forEach((username, index) => {
132+
expect(payload.details.email.usernames[index]).to.equal(username);
133+
});
134+
});
135+
136+
// Verify navigation back to Notification Channels listing page
137+
cy.url().should('include', '/alerts/notification-channels');
138+
ui.tabList.find().within(() => {
139+
cy.get('[data-testid="Notification Channels"]').should(
140+
'have.text',
141+
'Notification Channels'
142+
);
143+
});
144+
145+
cy.findByPlaceholderText('Search for Notification Channels').as(
146+
'searchInput'
147+
);
148+
cy.get('@searchInput').clear();
149+
cy.get('@searchInput').type(newChannelLabel);
150+
151+
cy.get('[data-qa="notification-channels-table"]')
152+
.find('tbody:visible')
153+
.within(() => {
154+
cy.get('tr').should('have.length', 1);
155+
cy.get('tr')
156+
.first()
157+
.within(() => {
158+
cy.findByText(newChannelLabel).should('be.visible');
159+
cy.findByText('Email').should('be.visible');
160+
});
161+
});
162+
});
163+
it('should display server error when editing a notification channel fails', () => {
164+
// Simulate server error on update
165+
mockUpdateAlertChannelByIdError(id, 'Internal server error').as(
166+
'updateAlertChannelByIdError'
167+
);
168+
// Wait for initial data load
169+
cy.wait('@getAlertNotificationChannels');
170+
171+
// Select the first notification channel to edit
172+
ui.actionMenu
173+
.findByTitle('Action menu for Notification Channel ' + label)
174+
.click();
175+
ui.actionMenuItem.findByTitle('Edit').click();
176+
177+
cy.wait('@getAlertChannelById');
178+
179+
// Modify the channel label
180+
const newChannelLabel = 'Updated Channel Name';
181+
cy.findByLabelText('Name').clear();
182+
cy.findByLabelText('Name').type(newChannelLabel);
183+
184+
// Click the Save button
185+
ui.buttonGroup
186+
.findButtonByTitle('Save')
187+
.should('be.visible')
188+
.should('be.enabled')
189+
.click();
190+
191+
ui.toast.assertMessage('Internal server error');
192+
cy.url().should('include', '/alerts/notification-channels/edit/' + id);
193+
});
194+
});

packages/manager/cypress/support/intercepts/cloudpulse.ts

Lines changed: 66 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -709,3 +709,69 @@ export const mockCreateAlertChannelError = (
709709
makeErrorResponse(errorPayload, statusCode)
710710
);
711711
};
712+
/**
713+
* Mocks get call for a specific alert channel by ID.
714+
*
715+
* @param {number} id - The ID of the alert channel to retrieve.
716+
* @param {NotificationChannel} channel - The notification channel object to return in the response.
717+
* @returns {Cypress.Chainable<null>} - A Cypress chainable used to continue the test flow.
718+
*/
719+
export const mockGetAlertChannelById = (
720+
id: number,
721+
channel: NotificationChannel
722+
): Cypress.Chainable<null> => {
723+
return cy.intercept(
724+
'GET',
725+
apiMatcher(`/monitor/alert-channels/${id}`),
726+
makeResponse(channel)
727+
);
728+
};
729+
730+
/**
731+
* Mocks put call to update a specific alert channel by ID.
732+
* Intercepts PUT requests to update alert channels and returns the provided channel object.
733+
*
734+
* @param {number} id - The ID of the alert channel to update.
735+
* @param {NotificationChannel} channel - The notification channel object to return in the response.
736+
* @returns {Cypress.Chainable<null>} - A Cypress chainable used to continue the test flow.
737+
*/
738+
export const mockUpdateAlertChannelById = (
739+
id: number,
740+
channel: NotificationChannel
741+
): Cypress.Chainable<null> => {
742+
return cy.intercept(
743+
'PUT',
744+
apiMatcher(`/monitor/alert-channels/${id}`),
745+
makeResponse(channel)
746+
);
747+
};
748+
749+
/**
750+
* Mocks error responses when updating a specific alert channel by ID.
751+
* Intercepts PUT requests to update alert channels and returns an error response.
752+
*
753+
* @param {number} id - The ID of the alert channel to update.
754+
* @param {Object | string} errorPayload - Either an object with field and reason properties for validation errors,
755+
* or a string error message for server errors.
756+
* @param {number} statusCode - The HTTP status code for the error response (default is 400).
757+
* @returns {Cypress.Chainable<null>} - A Cypress chainable used to continue the test flow.
758+
*
759+
* @example
760+
* // Mock a validation error (400)
761+
* mockUpdateAlertChannelByIdError(123, { field: 'name', reason: 'Required' }, 400);
762+
*
763+
* @example
764+
* // Mock a server error (500)
765+
* mockUpdateAlertChannelByIdError(123, 'Internal server error', 500);
766+
*/
767+
export const mockUpdateAlertChannelByIdError = (
768+
id: number,
769+
errorPayload: string | { field: string; reason: string },
770+
statusCode: number = 400
771+
): Cypress.Chainable<null> => {
772+
return cy.intercept(
773+
'PUT',
774+
apiMatcher(`/monitor/alert-channels/${id}`),
775+
makeErrorResponse(errorPayload, statusCode)
776+
);
777+
};

0 commit comments

Comments
 (0)