Skip to content

Commit a93c0ab

Browse files
authored
Merge pull request #88 from devsapp/add-test
Add comprehensive unit tests for deploy/impl modules and fix related …
2 parents 18865d0 + 6c54c46 commit a93c0ab

File tree

11 files changed

+3417
-1
lines changed

11 files changed

+3417
-1
lines changed
Lines changed: 343 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,343 @@
1+
import AsyncInvokeConfig from '../../../../../src/subCommands/deploy/impl/async_invoke_config';
2+
import { IInputs } from '../../../../../src/interface';
3+
import logger from '../../../../../src/logger';
4+
import { GetApiType } from '../../../../../src/resources/fc';
5+
6+
// Mocks
7+
jest.mock('../../../../../src/resources/fc');
8+
jest.mock('../../../../../src/utils');
9+
10+
describe('AsyncInvokeConfig', () => {
11+
let mockInputs: IInputs;
12+
let asyncInvokeConfig: AsyncInvokeConfig;
13+
let mockOpts: any;
14+
15+
beforeEach(() => {
16+
// Mock inputs
17+
mockInputs = {
18+
props: {
19+
region: 'cn-hangzhou',
20+
functionName: 'test-function',
21+
asyncInvokeConfig: {
22+
destinationConfig: {
23+
onSuccess: {
24+
destination: 'acs:fc:cn-hangzhou:123456789:function:test-on-success',
25+
},
26+
onFailure: {
27+
destination: 'acs:fc:cn-hangzhou:123456789:function:test-on-failure',
28+
},
29+
},
30+
maxAsyncEventAgeInSeconds: 3600,
31+
maxAsyncRetryAttempts: 3,
32+
},
33+
},
34+
credential: {
35+
AccountID: 'test-account-id',
36+
AccessKeyID: 'test-access-key-id',
37+
AccessKeySecret: 'test-access-key-secret',
38+
Region: 'cn-hangzhou',
39+
},
40+
args: [],
41+
argsObj: [],
42+
baseDir: '/test/base/dir',
43+
} as any;
44+
45+
mockOpts = {
46+
yes: true,
47+
};
48+
49+
// Mock logger methods
50+
logger.debug = jest.fn();
51+
logger.info = jest.fn();
52+
logger.warn = jest.fn();
53+
logger.error = jest.fn();
54+
logger.write = jest.fn();
55+
});
56+
57+
afterEach(() => {
58+
jest.clearAllMocks();
59+
});
60+
61+
describe('constructor', () => {
62+
it('should initialize correctly with async invoke config', () => {
63+
asyncInvokeConfig = new AsyncInvokeConfig(mockInputs, mockOpts);
64+
65+
expect(asyncInvokeConfig.functionName).toBe('test-function');
66+
expect(asyncInvokeConfig.local).toEqual(mockInputs.props?.asyncInvokeConfig);
67+
});
68+
69+
it('should initialize correctly with empty async invoke config', () => {
70+
const inputsWithoutAsyncInvokeConfig = {
71+
...mockInputs,
72+
props: {
73+
...mockInputs.props,
74+
asyncInvokeConfig: {},
75+
},
76+
};
77+
78+
asyncInvokeConfig = new AsyncInvokeConfig(inputsWithoutAsyncInvokeConfig, mockOpts);
79+
80+
expect(asyncInvokeConfig.functionName).toBe('test-function');
81+
expect(asyncInvokeConfig.local).toEqual({});
82+
});
83+
});
84+
85+
describe('before', () => {
86+
it('should call getRemote and plan', async () => {
87+
asyncInvokeConfig = new AsyncInvokeConfig(mockInputs, mockOpts);
88+
// Use a different approach to test private methods indirectly
89+
const getRemoteSpy = jest
90+
.spyOn(asyncInvokeConfig as any, 'getRemote')
91+
.mockResolvedValue(Promise.resolve());
92+
const planSpy = jest
93+
.spyOn(asyncInvokeConfig as any, 'plan')
94+
.mockResolvedValue(Promise.resolve());
95+
96+
await asyncInvokeConfig.before();
97+
98+
expect(getRemoteSpy).toHaveBeenCalled();
99+
expect(planSpy).toHaveBeenCalled();
100+
});
101+
});
102+
103+
describe('run', () => {
104+
it('should put async invoke config when needDeploy is true and local config is not empty', async () => {
105+
asyncInvokeConfig = new AsyncInvokeConfig(mockInputs, mockOpts);
106+
asyncInvokeConfig.needDeploy = true;
107+
108+
// Mock fcSdk
109+
const mockFcSdk = {
110+
putAsyncInvokeConfig: jest.fn().mockResolvedValue({}),
111+
};
112+
Object.defineProperty(asyncInvokeConfig, 'fcSdk', {
113+
value: mockFcSdk,
114+
writable: true,
115+
});
116+
117+
const result = await asyncInvokeConfig.run();
118+
119+
expect(asyncInvokeConfig.fcSdk.putAsyncInvokeConfig).toHaveBeenCalledWith(
120+
'test-function',
121+
'LATEST',
122+
expect.objectContaining({
123+
destinationConfig: {
124+
onSuccess: {
125+
destination: 'acs:fc:cn-hangzhou:123456789:function:test-on-success',
126+
},
127+
onFailure: {
128+
destination: 'acs:fc:cn-hangzhou:123456789:function:test-on-failure',
129+
},
130+
},
131+
maxAsyncEventAgeInSeconds: 3600,
132+
maxAsyncRetryAttempts: 3,
133+
}),
134+
);
135+
expect(result).toBe(true);
136+
});
137+
138+
it('should attempt to create async invoke config when needDeploy is false but remote config is empty', async () => {
139+
asyncInvokeConfig = new AsyncInvokeConfig(mockInputs, mockOpts);
140+
asyncInvokeConfig.needDeploy = false;
141+
asyncInvokeConfig.remote = {};
142+
143+
// Mock fcSdk
144+
const mockFcSdk = {
145+
putAsyncInvokeConfig: jest.fn().mockResolvedValue({}),
146+
};
147+
Object.defineProperty(asyncInvokeConfig, 'fcSdk', {
148+
value: mockFcSdk,
149+
writable: true,
150+
});
151+
152+
const result = await asyncInvokeConfig.run();
153+
154+
expect(asyncInvokeConfig.fcSdk.putAsyncInvokeConfig).toHaveBeenCalledWith(
155+
'test-function',
156+
'LATEST',
157+
expect.objectContaining({
158+
destinationConfig: {
159+
onSuccess: {
160+
destination: 'acs:fc:cn-hangzhou:123456789:function:test-on-success',
161+
},
162+
onFailure: {
163+
destination: 'acs:fc:cn-hangzhou:123456789:function:test-on-failure',
164+
},
165+
},
166+
maxAsyncEventAgeInSeconds: 3600,
167+
maxAsyncRetryAttempts: 3,
168+
}),
169+
);
170+
expect(result).toBe(false);
171+
});
172+
173+
it('should skip deployment when needDeploy is false and remote config exists', async () => {
174+
asyncInvokeConfig = new AsyncInvokeConfig(mockInputs, mockOpts);
175+
asyncInvokeConfig.needDeploy = false;
176+
asyncInvokeConfig.remote = {
177+
destinationConfig: {
178+
onSuccess: {
179+
destination: 'acs:fc:cn-hangzhou:123456789:function:test-on-success',
180+
},
181+
onFailure: {
182+
destination: 'acs:fc:cn-hangzhou:123456789:function:test-on-failure',
183+
},
184+
},
185+
maxAsyncEventAgeInSeconds: 3600,
186+
maxAsyncRetryAttempts: 3,
187+
};
188+
189+
// Mock fcSdk
190+
const mockFcSdk = {
191+
putAsyncInvokeConfig: jest.fn(),
192+
};
193+
Object.defineProperty(asyncInvokeConfig, 'fcSdk', {
194+
value: mockFcSdk,
195+
writable: true,
196+
});
197+
198+
const result = await asyncInvokeConfig.run();
199+
200+
expect(asyncInvokeConfig.fcSdk.putAsyncInvokeConfig).not.toHaveBeenCalled();
201+
expect(result).toBe(false);
202+
});
203+
204+
it('should handle empty local config', async () => {
205+
const inputsWithoutAsyncInvokeConfig = {
206+
...mockInputs,
207+
props: {
208+
...mockInputs.props,
209+
asyncInvokeConfig: {},
210+
},
211+
};
212+
213+
asyncInvokeConfig = new AsyncInvokeConfig(inputsWithoutAsyncInvokeConfig, mockOpts);
214+
215+
const result = await asyncInvokeConfig.run();
216+
217+
expect(result).toBe(true);
218+
});
219+
});
220+
221+
describe('getRemote', () => {
222+
it('should get remote async invoke config', async () => {
223+
asyncInvokeConfig = new AsyncInvokeConfig(mockInputs, mockOpts);
224+
225+
// Mock fcSdk
226+
const mockFcSdk = {
227+
getAsyncInvokeConfig: jest.fn().mockResolvedValue({
228+
destinationConfig: {
229+
onSuccess: {
230+
destination: 'acs:fc:cn-hangzhou:123456789:function:test-on-success',
231+
},
232+
onFailure: {
233+
destination: 'acs:fc:cn-hangzhou:123456789:function:test-on-failure',
234+
},
235+
},
236+
maxAsyncEventAgeInSeconds: 3600,
237+
maxAsyncRetryAttempts: 3,
238+
qualifier: 'LATEST',
239+
}),
240+
};
241+
Object.defineProperty(asyncInvokeConfig, 'fcSdk', {
242+
value: mockFcSdk,
243+
writable: true,
244+
});
245+
246+
// Call the method through the public before method which calls getRemote
247+
await (asyncInvokeConfig as any).getRemote();
248+
249+
expect(asyncInvokeConfig.fcSdk.getAsyncInvokeConfig).toHaveBeenCalledWith(
250+
'test-function',
251+
'LATEST',
252+
GetApiType.simpleUnsupported,
253+
);
254+
expect(asyncInvokeConfig.remote).toEqual({
255+
destinationConfig: {
256+
onSuccess: {
257+
destination: 'acs:fc:cn-hangzhou:123456789:function:test-on-success',
258+
},
259+
onFailure: {
260+
destination: 'acs:fc:cn-hangzhou:123456789:function:test-on-failure',
261+
},
262+
},
263+
maxAsyncEventAgeInSeconds: 3600,
264+
maxAsyncRetryAttempts: 3,
265+
qualifier: 'LATEST',
266+
});
267+
});
268+
269+
it('should handle error when getting remote async invoke config', async () => {
270+
asyncInvokeConfig = new AsyncInvokeConfig(mockInputs, mockOpts);
271+
272+
// Mock fcSdk
273+
const mockFcSdk = {
274+
getAsyncInvokeConfig: jest.fn().mockRejectedValue(new Error('get error')),
275+
};
276+
Object.defineProperty(asyncInvokeConfig, 'fcSdk', {
277+
value: mockFcSdk,
278+
writable: true,
279+
});
280+
281+
// Call the method through the public before method which calls getRemote
282+
await (asyncInvokeConfig as any).getRemote();
283+
284+
expect(asyncInvokeConfig.fcSdk.getAsyncInvokeConfig).toHaveBeenCalledWith(
285+
'test-function',
286+
'LATEST',
287+
GetApiType.simpleUnsupported,
288+
);
289+
expect(asyncInvokeConfig.remote).toEqual({});
290+
expect(logger.debug).toHaveBeenCalledWith(
291+
'Get remote asyncInvokeConfig of test-function error: get error',
292+
);
293+
});
294+
});
295+
296+
describe('plan', () => {
297+
it('should set needDeploy to true when remote is empty', async () => {
298+
asyncInvokeConfig = new AsyncInvokeConfig(mockInputs, mockOpts);
299+
asyncInvokeConfig.remote = {};
300+
301+
await (asyncInvokeConfig as any).plan();
302+
303+
expect(asyncInvokeConfig.needDeploy).toBe(true);
304+
});
305+
306+
it('should set needDeploy to true when diffResult is empty', async () => {
307+
asyncInvokeConfig = new AsyncInvokeConfig(mockInputs, mockOpts);
308+
asyncInvokeConfig.remote = {
309+
destinationConfig: {
310+
onSuccess: {
311+
destination: 'acs:fc:cn-hangzhou:123456789:function:test-on-success',
312+
},
313+
onFailure: {
314+
destination: 'acs:fc:cn-hangzhou:123456789:function:test-on-failure',
315+
},
316+
},
317+
maxAsyncEventAgeInSeconds: 3600,
318+
maxAsyncRetryAttempts: 3,
319+
};
320+
asyncInvokeConfig.local = {
321+
destinationConfig: {
322+
onSuccess: {
323+
destination: 'acs:fc:cn-hangzhou:123456789:function:test-on-success',
324+
},
325+
onFailure: {
326+
destination: 'acs:fc:cn-hangzhou:123456789:function:test-on-failure',
327+
},
328+
},
329+
maxAsyncEventAgeInSeconds: 3600,
330+
maxAsyncRetryAttempts: 3,
331+
};
332+
333+
// Mock diffConvertYaml
334+
jest.mock('@serverless-devs/diff', () => ({
335+
diffConvertYaml: jest.fn().mockReturnValue({ diffResult: {}, show: '' }),
336+
}));
337+
338+
await (asyncInvokeConfig as any).plan();
339+
340+
expect(asyncInvokeConfig.needDeploy).toBe(true);
341+
});
342+
});
343+
});

0 commit comments

Comments
 (0)