Skip to content

Commit 3c3ee44

Browse files
authored
test(redshift): unit tests for cluster parameter change reboot handler (#27788)
Closes #27730. ---- *By submitting this pull request, I confirm that my contribution is made under the terms of the Apache-2.0 license*
1 parent 576bb2a commit 3c3ee44

File tree

1 file changed

+367
-0
lines changed

1 file changed

+367
-0
lines changed
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,367 @@
1+
const mockRebootCluster = jest.fn();
2+
const mockDescribeClusters = jest.fn();
3+
const mockRedshift = {
4+
rebootCluster: mockRebootCluster,
5+
describeClusters: mockDescribeClusters,
6+
};
7+
8+
jest.mock('@aws-sdk/client-redshift', () => {
9+
return {
10+
Redshift: jest.fn(() => mockRedshift),
11+
};
12+
});
13+
14+
jest.setTimeout(35_000);
15+
16+
import { handler } from '../lib/cluster-parameter-change-reboot-handler';
17+
18+
describe('cluster-parameter-change-reboot-handler', () => {
19+
20+
beforeEach(() => {
21+
jest.spyOn(global, 'setTimeout');
22+
});
23+
24+
afterEach(() => {
25+
jest.clearAllMocks();
26+
});
27+
28+
test('reboots cluster with "pending-reboot" status', async () => {
29+
// GIVEN
30+
mockDescribeClusters.mockImplementation(() => {
31+
return {
32+
Clusters: [{
33+
ClusterParameterGroups: [{
34+
ParameterGroupName: 'parameter-group-name',
35+
ParameterApplyStatus: 'pending-reboot',
36+
}],
37+
}],
38+
};
39+
});
40+
41+
const event: Partial<AWSLambda.CloudFormationCustomResourceEvent> = {
42+
RequestType: 'Create',
43+
ResourceProperties: {
44+
ServiceToken: 'service-token',
45+
ClusterId: 'cluster-id',
46+
ParameterGroupName: 'parameter-group-name',
47+
},
48+
};
49+
50+
// WHEN
51+
await invokeHandler(event);
52+
53+
// THEN
54+
expect(mockRebootCluster).toHaveBeenCalled();
55+
});
56+
57+
test('reboots cluster with "apply-deferred" status', async () => {
58+
// GIVEN
59+
mockDescribeClusters.mockImplementation(() => {
60+
return {
61+
Clusters: [{
62+
ClusterParameterGroups: [{
63+
ParameterGroupName: 'parameter-group-name',
64+
ParameterApplyStatus: 'apply-deferred',
65+
}],
66+
}],
67+
};
68+
});
69+
70+
const event: Partial<AWSLambda.CloudFormationCustomResourceEvent> = {
71+
RequestType: 'Create',
72+
ResourceProperties: {
73+
ServiceToken: 'service-token',
74+
ClusterId: 'cluster-id',
75+
ParameterGroupName: 'parameter-group-name',
76+
},
77+
};
78+
79+
// WHEN
80+
await invokeHandler(event);
81+
82+
// THEN
83+
expect(mockRebootCluster).toHaveBeenCalled();
84+
});
85+
86+
test('reboots cluster with "apply-error" status', async () => {
87+
// GIVEN
88+
mockDescribeClusters.mockImplementation(() => {
89+
return {
90+
Clusters: [{
91+
ClusterParameterGroups: [{
92+
ParameterGroupName: 'parameter-group-name',
93+
ParameterApplyStatus: 'apply-deferred',
94+
}],
95+
}],
96+
};
97+
});
98+
99+
const event: Partial<AWSLambda.CloudFormationCustomResourceEvent> = {
100+
RequestType: 'Create',
101+
ResourceProperties: {
102+
ServiceToken: 'service-token',
103+
ClusterId: 'cluster-id',
104+
ParameterGroupName: 'parameter-group-name',
105+
},
106+
};
107+
108+
// WHEN
109+
await invokeHandler(event);
110+
111+
// THEN
112+
expect(mockRebootCluster).toHaveBeenCalled();
113+
});
114+
115+
test('retries cluster with "applying" status', async () => {
116+
// GIVEN
117+
mockDescribeClusters
118+
.mockImplementationOnce(() => {
119+
return {
120+
Clusters: [{
121+
ClusterParameterGroups: [{
122+
ParameterGroupName: 'parameter-group-name',
123+
ParameterApplyStatus: 'applying',
124+
}],
125+
}],
126+
};
127+
}).mockImplementationOnce(() => {
128+
return {
129+
Clusters: [{
130+
ClusterParameterGroups: [{
131+
ParameterGroupName: 'parameter-group-name',
132+
ParameterApplyStatus: 'pending-reboot',
133+
}],
134+
}],
135+
};
136+
});
137+
138+
const event: Partial<AWSLambda.CloudFormationCustomResourceEvent> = {
139+
RequestType: 'Create',
140+
ResourceProperties: {
141+
ServiceToken: 'service-token',
142+
ClusterId: 'cluster-id',
143+
ParameterGroupName: 'parameter-group-name',
144+
},
145+
};
146+
147+
// WHEN
148+
await invokeHandler(event);
149+
150+
// THEN
151+
expect(setTimeout).toHaveBeenCalled();
152+
expect(mockDescribeClusters).toHaveBeenCalledTimes(2);
153+
expect(mockRebootCluster).toHaveBeenCalledTimes(1);
154+
});
155+
156+
test('retries cluster with "retry" status', async () => {
157+
// GIVEN
158+
mockDescribeClusters
159+
.mockImplementationOnce(() => {
160+
return {
161+
Clusters: [{
162+
ClusterParameterGroups: [{
163+
ParameterGroupName: 'parameter-group-name',
164+
ParameterApplyStatus: 'retry',
165+
}],
166+
}],
167+
};
168+
}).mockImplementationOnce(() => {
169+
return {
170+
Clusters: [{
171+
ClusterParameterGroups: [{
172+
ParameterGroupName: 'parameter-group-name',
173+
ParameterApplyStatus: 'pending-reboot',
174+
}],
175+
}],
176+
};
177+
});
178+
179+
const event: Partial<AWSLambda.CloudFormationCustomResourceEvent> = {
180+
RequestType: 'Create',
181+
ResourceProperties: {
182+
ServiceToken: 'service-token',
183+
ClusterId: 'cluster-id',
184+
ParameterGroupName: 'parameter-group-name',
185+
},
186+
};
187+
188+
// WHEN
189+
await invokeHandler(event);
190+
191+
// THEN
192+
expect(setTimeout).toHaveBeenCalled();
193+
expect(mockDescribeClusters).toHaveBeenCalledTimes(2);
194+
expect(mockRebootCluster).toHaveBeenCalledTimes(1);
195+
});
196+
197+
test('retries if rebootCluster throws InvalidClusterStateFault error', async () => {
198+
// GIVEN
199+
mockDescribeClusters.mockImplementation(() => {
200+
return {
201+
Clusters: [{
202+
ClusterParameterGroups: [{
203+
ParameterGroupName: 'parameter-group-name',
204+
ParameterApplyStatus: 'pending-reboot',
205+
}],
206+
}],
207+
};
208+
});
209+
mockRebootCluster
210+
.mockImplementationOnce(async () => {
211+
const { InvalidClusterStateFault } = jest.requireActual('@aws-sdk/client-redshift');
212+
return Promise.reject(new InvalidClusterStateFault());
213+
})
214+
.mockImplementationOnce(jest.fn());
215+
216+
const event: Partial<AWSLambda.CloudFormationCustomResourceEvent> = {
217+
RequestType: 'Create',
218+
ResourceProperties: {
219+
ServiceToken: 'service-token',
220+
ClusterId: 'cluster-id',
221+
ParameterGroupName: 'parameter-group-name',
222+
},
223+
};
224+
225+
// WHEN
226+
await invokeHandler(event);
227+
228+
// THEN
229+
expect(setTimeout).toHaveBeenCalled();
230+
expect(mockDescribeClusters).toHaveBeenCalledTimes(1);
231+
expect(mockRebootCluster).toHaveBeenCalledTimes(2);
232+
});
233+
234+
test('fails if rebootCluster throws generic error', async () => {
235+
// GIVEN
236+
mockDescribeClusters.mockImplementation(() => {
237+
return {
238+
Clusters: [{
239+
ClusterParameterGroups: [{
240+
ParameterGroupName: 'parameter-group-name',
241+
ParameterApplyStatus: 'pending-reboot',
242+
}],
243+
}],
244+
};
245+
});
246+
mockRebootCluster.mockImplementation(async () => {
247+
return Promise.reject(new Error('error'));
248+
});
249+
250+
const event: Partial<AWSLambda.CloudFormationCustomResourceEvent> = {
251+
RequestType: 'Create',
252+
ResourceProperties: {
253+
ServiceToken: 'service-token',
254+
ClusterId: 'cluster-id',
255+
ParameterGroupName: 'parameter-group-name',
256+
},
257+
};
258+
259+
// WHEN
260+
// THEN
261+
await expect(() => invokeHandler(event)).rejects.toThrow('error');
262+
});
263+
264+
test('fails if cannot find cluster details', async () => {
265+
// GIVEN
266+
mockDescribeClusters.mockImplementation(() => {
267+
return {
268+
Clusters: [{}],
269+
};
270+
});
271+
272+
const event: Partial<AWSLambda.CloudFormationCustomResourceEvent> = {
273+
RequestType: 'Create',
274+
ResourceProperties: {
275+
ServiceToken: 'service-token',
276+
ClusterId: 'cluster-id',
277+
ParameterGroupName: 'parameter-group-name',
278+
},
279+
};
280+
281+
// WHEN
282+
// THEN
283+
await expect(() =>invokeHandler(event))
284+
.rejects.toThrow(/Unable to find any Parameter Groups associated with ClusterId "cluster-id"./);
285+
});
286+
287+
test('fails if cannot find cluster parameter group', async () => {
288+
// GIVEN
289+
mockDescribeClusters.mockImplementation(() => {
290+
return {
291+
Clusters: [{
292+
ClusterParameterGroups: [{
293+
ParameterGroupName: 'unknown',
294+
ParameterApplyStatus: 'pending-reboot',
295+
}],
296+
}],
297+
};
298+
});
299+
300+
const event: Partial<AWSLambda.CloudFormationCustomResourceEvent> = {
301+
RequestType: 'Create',
302+
ResourceProperties: {
303+
ServiceToken: 'service-token',
304+
ClusterId: 'cluster-id',
305+
ParameterGroupName: 'parameter-group-name',
306+
},
307+
};
308+
309+
// WHEN
310+
// THEN
311+
await expect(() =>invokeHandler(event))
312+
.rejects.toThrow(/Unable to find Parameter Group named "parameter-group-name" associated with ClusterId "cluster-id"./);
313+
});
314+
315+
test('does not reboot if request type is Delete', async () => {
316+
// GIVEN
317+
const event: Partial<AWSLambda.CloudFormationCustomResourceEvent> = {
318+
RequestType: 'Delete',
319+
ResourceProperties: {
320+
ServiceToken: 'service-token',
321+
ClusterId: 'cluster-id',
322+
ParameterGroupName: 'parameter-group-name',
323+
},
324+
};
325+
326+
// WHEN
327+
await invokeHandler(event);
328+
329+
// THEN
330+
expect(mockRebootCluster).not.toHaveBeenCalled();
331+
});
332+
333+
test('does not reboot if apply status is not recognized', async () => {
334+
// GIVEN
335+
mockDescribeClusters.mockImplementation(() => {
336+
return {
337+
Clusters: [{
338+
ClusterParameterGroups: [{
339+
ParameterGroupName: 'parameter-group-name',
340+
ParameterApplyStatus: 'other-status',
341+
}],
342+
}],
343+
};
344+
});
345+
346+
const event: Partial<AWSLambda.CloudFormationCustomResourceEvent> = {
347+
RequestType: 'Create',
348+
ResourceProperties: {
349+
ServiceToken: 'service-token',
350+
ClusterId: 'cluster-id',
351+
ParameterGroupName: 'parameter-group-name',
352+
},
353+
};
354+
355+
// WHEN
356+
await invokeHandler(event);
357+
358+
// THEN
359+
expect(mockRebootCluster).not.toHaveBeenCalled();
360+
});
361+
});
362+
363+
// helper function to get around TypeScript expecting a complete event object,
364+
// even though our tests only need some of the fields
365+
async function invokeHandler(event: Partial<AWSLambda.CloudFormationCustomResourceEvent>) {
366+
return handler(event as AWSLambda.CloudFormationCustomResourceEvent);
367+
}

0 commit comments

Comments
 (0)