Skip to content

Commit b7d15a9

Browse files
test: [DI-29822] - Add spec for create notification channel (#13383)
* test: [DI-29822] - Add spec for create notification channel * Added changeset: Add spec for create nofitication channel * addressing review comments --------- Co-authored-by: shnagend-akamai <142887750+shnagend@users.noreply.github.com> Co-authored-by: venkatmano-akamai <vmangalr@akamai.com>
1 parent bffcecc commit b7d15a9

File tree

6 files changed

+297
-1
lines changed

6 files changed

+297
-1
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 spec for create nofitication channel ([#13383](https://github.com/linode/manager/pull/13383))
Lines changed: 237 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,237 @@
1+
/**
2+
* @file Integration Tests for CloudPulse Alerting — Notification Channel Creation Validation
3+
*/
4+
import { profileFactory } from '@linode/utilities';
5+
import { mockGetAccount, mockGetUsers } from 'support/intercepts/account';
6+
import {
7+
mockCreateAlertChannelError,
8+
mockCreateAlertChannelSuccess,
9+
mockGetAlertChannels,
10+
} from 'support/intercepts/cloudpulse';
11+
import { mockAppendFeatureFlags } from 'support/intercepts/feature-flags';
12+
import { mockGetProfile } from 'support/intercepts/profile';
13+
import { ui } from 'support/ui';
14+
15+
import {
16+
accountFactory,
17+
accountUserFactory,
18+
flagsFactory,
19+
notificationChannelFactory,
20+
} from 'src/factories';
21+
import { CREATE_CHANNEL_SUCCESS_MESSAGE } from 'src/features/CloudPulse/Alerts/constants';
22+
23+
// Define mock data for the test.
24+
25+
const mockAccount = accountFactory.build();
26+
const mockProfile = profileFactory.build({
27+
restricted: false,
28+
});
29+
const notificationChannels = notificationChannelFactory.buildList(5);
30+
const createNotificationChannel = notificationChannelFactory.build({
31+
label: 'Test Channel Name',
32+
channel_type: 'email',
33+
content: {
34+
email: {
35+
email_addresses: ['user1', 'user2'],
36+
message: 'You have a new Alert',
37+
subject: 'Sample Alert',
38+
},
39+
},
40+
});
41+
42+
describe('CloudPulse Alerting - Notification Channel Creation Validation', () => {
43+
/**
44+
* Verify successful creation of a new email notification channel with success snackbar
45+
* Verifies the payload sent to the API and the UI listing of the newly created channel.
46+
* Verifies server error handling during channel creation.
47+
*/
48+
beforeEach(() => {
49+
mockGetAccount(mockAccount);
50+
mockGetProfile(mockProfile);
51+
const mockflags = flagsFactory.build({
52+
aclpAlerting: {
53+
notificationChannels: true,
54+
},
55+
});
56+
mockAppendFeatureFlags(mockflags);
57+
mockGetAlertChannels(notificationChannels).as(
58+
'getAlertNotificationChannels'
59+
);
60+
mockCreateAlertChannelSuccess(createNotificationChannel).as(
61+
'createAlertChannelNew'
62+
);
63+
64+
// Mock 2 users for recipient selection
65+
const users = [
66+
accountUserFactory.build({ username: 'user1' }),
67+
accountUserFactory.build({ username: 'user2' }),
68+
];
69+
70+
mockGetUsers(users).as('getAccountUsers');
71+
72+
// Visit Notification Channels page
73+
cy.visitWithLogin('/alerts/notification-channels');
74+
});
75+
it('should create email notification channel, verify payload and UI listing', () => {
76+
// Open Create Channel page
77+
ui.button
78+
.findByTitle('Create Channel')
79+
.should('be.visible')
80+
.and('be.enabled')
81+
.click();
82+
83+
// Verify breadcrumb heading
84+
ui.breadcrumb.find().within(() => {
85+
cy.contains('Notification Channels').should('be.visible');
86+
cy.contains('Create Channel').should('be.visible');
87+
});
88+
89+
// Verify Channel Settings heading
90+
ui.heading.findByText('Channel Settings').should('be.visible');
91+
92+
// Select notification type (Email)
93+
cy.get('[data-qa-textfield-label="Type"]').should('be.visible');
94+
ui.autocomplete
95+
.findByLabel('channel-type-select')
96+
.should('be.visible')
97+
.click();
98+
ui.autocompletePopper.findByTitle('Email').click();
99+
100+
// Enter channel name
101+
cy.get('[data-qa-textfield-label="Name"]').should('be.visible');
102+
cy.findByPlaceholderText('Enter a name for the channel')
103+
.should('be.visible')
104+
.type('Test Channel Name');
105+
106+
cy.get('[data-qa-textfield-helper-text="true"]')
107+
.should('be.visible')
108+
.and('have.text', 'Select up to 10 Recipients');
109+
110+
// Open recipients autocomplete and select users
111+
cy.get('[data-qa-textfield-label="Recipients"]').should('be.visible');
112+
ui.autocomplete
113+
.findByLabel('recipients-select')
114+
.should('be.visible')
115+
.click();
116+
ui.autocompletePopper.findByTitle('user1').click();
117+
ui.autocompletePopper.findByTitle('user2').click();
118+
119+
// Verify selected chips
120+
cy.get('[data-tag-index]')
121+
.should('have.length', 2)
122+
.each(($chip, index) => {
123+
const expectedUsers = ['user1', 'user2'];
124+
cy.wrap($chip)
125+
.find('.MuiChip-label')
126+
.should('contain.text', expectedUsers[index]);
127+
});
128+
129+
// Verify Cancel button is enabled
130+
ui.buttonGroup
131+
.findButtonByTitle('Cancel')
132+
.should('be.visible')
133+
.and('be.enabled');
134+
135+
// Verify Submit button is enabled and click
136+
ui.buttonGroup
137+
.findButtonByTitle('Submit')
138+
.should('be.visible')
139+
.and('be.enabled')
140+
.click();
141+
142+
// Validate API request payload for notification channel creation
143+
cy.wait('@createAlertChannelNew').then((interception) => {
144+
expect(interception)
145+
.to.have.property('response')
146+
.with.property('statusCode', 200);
147+
148+
const payload = interception.request.body;
149+
150+
// Top-level fields
151+
expect(payload.label).to.equal('Test Channel Name');
152+
expect(payload.channel_type).to.equal('email');
153+
154+
// Email details validation
155+
expect(payload.details).to.have.property('email');
156+
expect(payload.details.email.usernames).to.have.length(2);
157+
158+
const expectedRecipients = ['user1', 'user2'];
159+
160+
expectedRecipients.forEach((username, index) => {
161+
expect(payload.details.email.usernames[index]).to.equal(username);
162+
});
163+
});
164+
165+
// Verify success toast
166+
ui.toast.assertMessage(CREATE_CHANNEL_SUCCESS_MESSAGE);
167+
168+
cy.wait('@getAlertNotificationChannels');
169+
170+
// Verify navigation back to Notification Channels listing page
171+
cy.url().should('include', '/alerts/notification-channels');
172+
ui.tabList.find().within(() => {
173+
cy.get('[data-testid="Notification Channels"]').should(
174+
'have.text',
175+
'Notification Channels'
176+
);
177+
});
178+
// Verify the newly created channel appears in the listing
179+
const expected = createNotificationChannel;
180+
181+
cy.findByPlaceholderText('Search for Notification Channels').as(
182+
'searchInput'
183+
);
184+
cy.get('@searchInput').clear();
185+
cy.get('@searchInput').type(expected.label);
186+
187+
cy.get('[data-qa="notification-channels-table"]')
188+
.find('tbody:visible')
189+
.within(() => {
190+
cy.get('tr').should('have.length', 1);
191+
cy.get('tr')
192+
.first()
193+
.within(() => {
194+
cy.findByText(expected.label).should('be.visible');
195+
cy.findByText('Email').should('be.visible');
196+
});
197+
});
198+
});
199+
it('should display server related message when API returns an error during channel creation', () => {
200+
mockCreateAlertChannelError('Internal Server Error', 500).as(
201+
'createAlertChannelServerError'
202+
);
203+
204+
cy.visitWithLogin('/alerts/notification-channels');
205+
206+
// Open Create Channel drawer
207+
ui.button
208+
.findByTitle('Create Channel')
209+
.should('be.visible')
210+
.and('be.enabled')
211+
.click();
212+
213+
// Select notification type
214+
ui.autocomplete.findByLabel('channel-type-select').click();
215+
ui.autocompletePopper.findByTitle('Email').click();
216+
217+
// Enter channel name
218+
cy.findByPlaceholderText('Enter a name for the channel').type(
219+
'Error Channel'
220+
);
221+
222+
// Select recipients
223+
ui.autocomplete.findByLabel('recipients-select').click();
224+
ui.autocompletePopper.findByTitle('user1').click();
225+
226+
// Submit form
227+
ui.buttonGroup.findButtonByTitle('Submit').click();
228+
229+
// Wait for the intercepted API call
230+
cy.wait('@createAlertChannelServerError')
231+
.its('response.statusCode')
232+
.should('eq', 500);
233+
234+
// Verify toast message
235+
ui.toast.assertMessage('Internal Server Error');
236+
});
237+
});

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

Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -664,3 +664,48 @@ export const mockDeleteChannelError = (
664664
}
665665
);
666666
};
667+
668+
/**
669+
* Mocks successful creation of an alert channel (200).
670+
* Intercepts POST requests to create alert channels and returns the provided channel object.
671+
*
672+
* @param {NotificationChannel} channel - The notification channel object to return in the response.
673+
* @returns {Cypress.Chainable<null>} - A Cypress chainable used to continue the test flow.
674+
*/
675+
export const mockCreateAlertChannelSuccess = (
676+
channel: NotificationChannel
677+
): Cypress.Chainable<null> => {
678+
return cy.intercept(
679+
'POST',
680+
apiMatcher('/monitor/alert-channels'),
681+
makeResponse(channel) // defaults to 200
682+
);
683+
};
684+
685+
/**
686+
* Mocks error responses when creating alert channels.
687+
* Intercepts POST requests to create alert channels and returns an error response.
688+
*
689+
* @param {Object | string} errorPayload - Either an object with field and reason properties for validation errors,
690+
* or a string error message for server errors.
691+
* @param {number} statusCode - The HTTP status code for the error response (default is 400).
692+
* @returns {Cypress.Chainable<null>} - A Cypress chainable used to continue the test flow.
693+
*
694+
* @example
695+
* // Mock a validation error (400)
696+
* mockCreateAlertChannelError({ field: 'name', reason: 'Required' }, 400);
697+
*
698+
* @example
699+
* // Mock a server error (500)
700+
* mockCreateAlertChannelError('Internal server error', 500);
701+
*/
702+
export const mockCreateAlertChannelError = (
703+
errorPayload: string | { field: string; reason: string },
704+
statusCode: number = 400
705+
): Cypress.Chainable<null> => {
706+
return cy.intercept(
707+
'POST',
708+
apiMatcher('/monitor/alert-channels'),
709+
makeErrorResponse(errorPayload, statusCode)
710+
);
711+
};

packages/manager/src/features/CloudPulse/Alerts/NotificationChannels/CreateChannel/CreateNotificationChannel.tsx

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -94,12 +94,19 @@ export const CreateNotificationChannel = () => {
9494
return (
9595
<Paper sx={{ paddingLeft: 1, paddingRight: 1, paddingTop: 2 }}>
9696
<Breadcrumb
97+
breadcrumbDataAttrs={{
98+
'data-qa-breadcrumb': true,
99+
}}
97100
crumbOverrides={overrides}
98101
pathname="/NotificationChannels/Create Channel"
99102
/>
100103
<FormProvider {...formMethods}>
101104
<form onSubmit={onSubmit}>
102-
<Typography marginTop={2} variant="h2">
105+
<Typography
106+
data-qa-header="Channel Settings"
107+
marginTop={2}
108+
variant="h2"
109+
>
103110
Channel Settings
104111
</Typography>
105112
<Controller

packages/manager/src/features/CloudPulse/Alerts/NotificationChannels/CreateChannel/NotificationChannelTypeSelect.tsx

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,7 @@ export const NotificationChannelTypeSelect = React.memo(
3838

3939
return (
4040
<Autocomplete
41+
data-qa-autocomplete="channel-type-select"
4142
data-testid="channel-type-select"
4243
disableClearable={disabled}
4344
disabled={disabled}

packages/manager/src/features/CloudPulse/Alerts/NotificationChannels/CreateChannel/NotificationRecipients.tsx

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -81,6 +81,7 @@ export const NotificationRecipients = React.memo(
8181

8282
return (
8383
<Autocomplete
84+
data-qa-autocomplete="recipients-select"
8485
data-testid="recipients-select"
8586
disableSelectAll={recipientsLimitReached}
8687
errorText={

0 commit comments

Comments
 (0)