Skip to content

Commit 287ddb8

Browse files
authored
improve: add more test (#17)
improve: add more test - [x] improve test coverage for rosClient - [x] improve test coverage for iacHelper - [x] improve test coverage for getVersion Refs: --------- Signed-off-by: seven <zilisheng1996@gmail.com>
1 parent d7453e0 commit 287ddb8

File tree

5 files changed

+181
-15
lines changed

5 files changed

+181
-15
lines changed

jest.config.js

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22
module.exports = {
33
preset: 'ts-jest',
44
testEnvironment: 'node',
5+
testTimeout: 30000,
56
testPathIgnorePatterns: ['/node_modules/', '/dist/'],
67
};
78

tests/common/getVersion.test.ts

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
import { getVersion } from '../../src/common';
2+
3+
jest.mock('../../package.json', () => ({
4+
version: '1.0.0',
5+
}));
6+
7+
describe('getVersion', () => {
8+
it('should return the version from package.json', () => {
9+
expect(getVersion()).toBe('1.0.0');
10+
});
11+
});

tests/common/iacHelper.test.ts

Lines changed: 66 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -1,27 +1,78 @@
1+
import * as ros from '@alicloud/ros-cdk-core';
12
import * as ossDeployment from '@alicloud/ros-cdk-ossdeployment';
2-
import { getFileSource } from '../../src/common';
3+
import { getFileSource, replaceReference } from '../../src/common';
34
import fs from 'node:fs';
5+
import { context } from '../fixtures/contextFixture';
46

57
jest.mock('@alicloud/ros-cdk-ossdeployment');
6-
8+
jest.mock('@alicloud/ros-cdk-core');
79
const fcName = 'testFunction';
810
const location = 'tests/fixtures/artifacts/artifact.zip';
911

10-
describe('getFileSource', () => {
11-
it('should return the correct ossDeployment source', () => {
12-
getFileSource(fcName, location);
13-
expect(ossDeployment.Source.asset).toHaveBeenCalledWith(
14-
`${process.cwd()}/${location}`,
15-
{},
16-
`${fcName}/50861cd99a3a678356030f5f189300af.zip`,
17-
);
12+
describe('Unit test for iacHelper', () => {
13+
beforeEach(() => {
14+
jest.restoreAllMocks();
15+
});
16+
describe('Unit test for getFileSource', () => {
17+
it('should return the correct ossDeployment source', () => {
18+
getFileSource(fcName, location);
19+
expect(ossDeployment.Source.asset).toHaveBeenCalledWith(
20+
`${process.cwd()}/${location}`,
21+
{},
22+
`${fcName}/50861cd99a3a678356030f5f189300af.zip`,
23+
);
24+
});
25+
26+
it('should throw an error if the path is a directory', () => {
27+
jest.spyOn(fs, 'lstatSync').mockReturnValue({ isDirectory: () => true } as fs.Stats);
28+
29+
expect(() => getFileSource(fcName, location)).toThrow(
30+
'The provided path is a directory, not a file.',
31+
);
32+
});
1833
});
1934

20-
it('should throw an error if the path is a directory', () => {
21-
jest.spyOn(fs, 'lstatSync').mockReturnValue({ isDirectory: () => true } as fs.Stats);
35+
describe('Unit test for replaceReference', () => {
36+
it('should return the value if it does not contain any reference', () => {
37+
const value = 'testValue';
38+
expect(replaceReference(value, context)).toEqual(value);
39+
expect(ros.Fn.ref).not.toHaveBeenCalled();
40+
});
41+
42+
it('should return the value if it contains a stage reference', () => {
43+
const value = 'testValue-${ctx.stage}';
44+
expect(replaceReference(value, context)).toEqual('testValue-test');
45+
46+
expect(ros.Fn.ref).not.toHaveBeenCalled();
47+
});
48+
49+
it('should return the value if it match a vars reference', () => {
50+
replaceReference('${vars.testVar}', context);
51+
52+
expect(ros.Fn.ref).toHaveBeenCalledWith('testVar');
53+
});
54+
55+
it('should return the value if it match a stages reference', () => {
56+
replaceReference('${stages.testStage}', context);
57+
expect(ros.Fn.findInMap).toHaveBeenCalledWith('stages', 'test', 'testStage');
58+
});
59+
60+
it('should return the value if it contains a stages reference', () => {
61+
replaceReference('abcd-${stages.testVar}-efg', context);
62+
63+
expect(ros.Fn.sub).toHaveBeenCalledWith('abcd-${testVar}-efg');
64+
});
65+
66+
it('should return the value if it contains both stages reference and vars references', () => {
67+
replaceReference('abcd-${stages.testVar}-efg-${vars.newTestVar}-hij', context);
68+
69+
expect(ros.Fn.sub).toHaveBeenCalledWith('abcd-${testVar}-efg-${newTestVar}-hij');
70+
});
71+
72+
it('should return the value if it contains a functions reference', () => {
73+
replaceReference('${functions.testFunction}', context);
2274

23-
expect(() => getFileSource(fcName, location)).toThrow(
24-
'The provided path is a directory, not a file.',
25-
);
75+
expect(ros.Fn.getAtt).toHaveBeenCalledWith('testFunction', 'FunctionName');
76+
});
2677
});
2778
});

tests/common/rosClient.test.ts

Lines changed: 88 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,88 @@
1+
import { logger, rosStackDeploy } from '../../src/common';
2+
import { context } from '../fixtures/contextFixture';
3+
import { lang } from '../../src/lang';
4+
5+
const mockedCreateStack = jest.fn();
6+
const mockedUpdateStack = jest.fn();
7+
const mockedListStacks = jest.fn();
8+
const mockedGetStack = jest.fn();
9+
10+
jest.mock('@alicloud/ros20190910', () => ({
11+
...jest.requireActual('@alicloud/ros20190910'),
12+
__esModule: true,
13+
default: jest.fn().mockImplementation(() => ({
14+
createStack: () => mockedCreateStack(),
15+
updateStack: () => mockedUpdateStack(),
16+
listStacks: () => mockedListStacks(),
17+
getStack: () => mockedGetStack(),
18+
})),
19+
}));
20+
21+
jest.mock('../../src/common/logger');
22+
23+
describe('Unit test for rosClient', () => {
24+
beforeEach(() => {
25+
jest.clearAllMocks();
26+
});
27+
28+
it('should create a new stack if it does not exist', async () => {
29+
mockedListStacks.mockResolvedValue({ statusCode: 200, body: { stacks: [] } });
30+
mockedCreateStack.mockResolvedValue({ body: { stackId: 'newStackId' } });
31+
mockedGetStack.mockResolvedValue({ body: { status: 'CREATE_COMPLETE' } });
32+
33+
await rosStackDeploy('newStack', {}, context);
34+
35+
expect(mockedCreateStack).toHaveBeenCalled();
36+
expect(logger.info).toHaveBeenCalledWith(expect.stringContaining('createStack success'));
37+
});
38+
39+
it('should update an existing stack if it exists', async () => {
40+
mockedListStacks.mockResolvedValue({
41+
statusCode: 200,
42+
body: { stacks: [{ stackId: 'existingStackId', Status: 'CREATE_COMPLETE' }] },
43+
});
44+
mockedUpdateStack.mockResolvedValue({ body: { stackId: 'existingStackId' } });
45+
mockedGetStack.mockResolvedValue({ body: { status: 'UPDATE_COMPLETE' } });
46+
47+
await rosStackDeploy('existingStack', {}, context);
48+
49+
expect(mockedUpdateStack).toHaveBeenCalled();
50+
expect(logger.info).toHaveBeenCalledWith(expect.stringContaining('stackUpdate success'));
51+
});
52+
53+
it('should throw an error if the stack is in progress', async () => {
54+
mockedListStacks.mockResolvedValue({
55+
statusCode: 200,
56+
body: { stacks: [{ stackId: 'inProgressStackId', Status: 'CREATE_IN_PROGRESS' }] },
57+
});
58+
59+
await expect(rosStackDeploy('inProgressStack', {}, context)).rejects.toThrow(
60+
'fail to update stack, because stack status is CREATE_IN_PROGRESS',
61+
);
62+
});
63+
64+
it('should notify user with warning logs when update completely same stack', async () => {
65+
mockedListStacks.mockResolvedValue({
66+
statusCode: 200,
67+
body: { stacks: [{ stackId: 'existingStackId', Status: 'CREATE_COMPLETE' }] },
68+
});
69+
mockedUpdateStack.mockRejectedValueOnce({
70+
data: { statusCode: 400, Message: 'Update the completely same stack' },
71+
});
72+
mockedGetStack.mockResolvedValue({ body: { status: 'UPDATE_COMPLETE' } });
73+
74+
await rosStackDeploy('existingStack', {}, context);
75+
76+
expect(logger.warn).toHaveBeenCalledWith(`${lang.__('UPDATE_COMPLETELY_SAME_STACK')}`);
77+
});
78+
79+
it('should throw error when deploy stack failed', async () => {
80+
mockedListStacks.mockResolvedValue({ statusCode: 200, body: { stacks: [] } });
81+
mockedCreateStack.mockResolvedValueOnce({ body: { stackId: 'newStackId' } });
82+
mockedGetStack.mockResolvedValue({ body: { status: 'ROLLBACK_COMPLETE' } });
83+
84+
await expect(rosStackDeploy('newStack', {}, context)).rejects.toThrow(
85+
`Stack operation failed with status: ROLLBACK_COMPLETE`,
86+
);
87+
});
88+
});

tests/fixtures/contextFixture.ts

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
import { ActionContext } from '../../src/types';
2+
3+
export const context: ActionContext = {
4+
stage: 'test',
5+
stackName: 'testStack',
6+
region: 'cn-hangzhou',
7+
accessKeyId: 'testAccessKeyId',
8+
accessKeySecret: 'testAccessKeySecret',
9+
iacLocation: 'path/to/iac/location',
10+
parameters: [
11+
{ key: 'testVar', value: 'testVarValue' },
12+
{ key: 'newTestVar', value: 'newTestVarValue' },
13+
],
14+
tags: [{ key: 'owner', value: 'geekfun' }],
15+
};

0 commit comments

Comments
 (0)