Skip to content

Commit 00a1f17

Browse files
authored
Merge pull request #86 from devsapp/add-ut
2 parents 79e0d24 + 5d2706e commit 00a1f17

34 files changed

+9230
-105
lines changed

.gitignore

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -61,4 +61,5 @@ __tests__/e2e/apt/code/package-lock.json
6161

6262
.env_test
6363

64-
CLAUDE.md
64+
CLAUDE.md
65+
agent-prompt.md

__tests__/ut/alias_test.ts

Lines changed: 304 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,304 @@
1+
import Alias from '../../src/subCommands/alias';
2+
import FC from '../../src/resources/fc';
3+
import logger from '../../src/logger';
4+
import { IInputs } from '../../src/interface';
5+
import { promptForConfirmOrDetails, tableShow } from '../../src/utils';
6+
7+
// Mock dependencies
8+
jest.mock('../../src/resources/fc');
9+
jest.mock('../../src/logger', () => {
10+
const mockLogger = {
11+
log: jest.fn(),
12+
info: jest.fn(),
13+
debug: jest.fn(),
14+
warn: jest.fn(),
15+
write: jest.fn(),
16+
error: jest.fn(),
17+
output: jest.fn(),
18+
spin: jest.fn(),
19+
tips: jest.fn(),
20+
append: jest.fn(),
21+
tipsOnce: jest.fn(),
22+
warnOnce: jest.fn(),
23+
writeOnce: jest.fn(),
24+
};
25+
return {
26+
__esModule: true,
27+
default: mockLogger,
28+
};
29+
});
30+
jest.mock('../../src/utils');
31+
32+
describe('Alias', () => {
33+
let mockInputs: IInputs;
34+
let alias: Alias;
35+
let mockFcInstance: any;
36+
37+
beforeEach(() => {
38+
mockInputs = {
39+
cwd: '/test',
40+
baseDir: '/test',
41+
name: 'test-app',
42+
props: {
43+
region: 'cn-hangzhou',
44+
functionName: 'test-function',
45+
runtime: 'nodejs18',
46+
handler: 'index.handler',
47+
code: './code',
48+
},
49+
command: 'alias',
50+
args: ['list'],
51+
yaml: {
52+
path: '/test/s.yaml',
53+
},
54+
resource: {
55+
name: 'test-resource',
56+
component: 'fc3',
57+
access: 'default',
58+
},
59+
outputs: {},
60+
getCredential: jest.fn().mockResolvedValue({
61+
AccountID: '123456789',
62+
AccessKeyID: 'test-key',
63+
AccessKeySecret: 'test-secret',
64+
}),
65+
};
66+
67+
// Mock FC methods
68+
mockFcInstance = {
69+
listAlias: jest.fn().mockResolvedValue([{ aliasName: 'test-alias', versionId: '1' }]),
70+
getAlias: jest.fn().mockResolvedValue({ aliasName: 'test-alias', versionId: '1' }),
71+
publishAlias: jest.fn().mockResolvedValue({ aliasName: 'test-alias', versionId: '1' }),
72+
removeAlias: jest.fn().mockResolvedValue({}),
73+
getVersionLatest: jest.fn().mockResolvedValue({ versionId: '1' }),
74+
};
75+
76+
// Mock FC constructor to return our mock instance
77+
(FC as any).mockImplementation(() => mockFcInstance);
78+
79+
(promptForConfirmOrDetails as jest.Mock).mockResolvedValue(true);
80+
(tableShow as jest.Mock).mockReturnValue(undefined);
81+
});
82+
83+
afterEach(() => {
84+
jest.clearAllMocks();
85+
});
86+
87+
describe('constructor', () => {
88+
it('should create Alias instance with valid inputs for list command', () => {
89+
alias = new Alias(mockInputs);
90+
expect(alias).toBeInstanceOf(Alias);
91+
expect(alias.subCommand).toBe('list');
92+
});
93+
94+
it('should create Alias instance with valid inputs for get command', () => {
95+
mockInputs.args = ['get', '--alias-name', 'test-alias'];
96+
alias = new Alias(mockInputs);
97+
expect(alias).toBeInstanceOf(Alias);
98+
expect(alias.subCommand).toBe('get');
99+
});
100+
101+
it('should throw error when subCommand is not provided', () => {
102+
mockInputs.args = [];
103+
expect(() => new Alias(mockInputs)).toThrow(
104+
'Command "undefined" not found, Please use "s cli fc3 alias -h" to query how to use the command',
105+
);
106+
});
107+
108+
it('should throw error when subCommand is invalid', () => {
109+
mockInputs.args = ['invalid'];
110+
expect(() => new Alias(mockInputs)).toThrow(
111+
'Command "invalid" not found, Please use "s cli fc3 alias -h" to query how to use the command',
112+
);
113+
});
114+
115+
it('should throw error when region is not specified', () => {
116+
delete mockInputs.props.region;
117+
mockInputs.args = ['list'];
118+
expect(() => new Alias(mockInputs)).toThrow('Region not specified');
119+
});
120+
121+
it('should throw error when functionName is not specified', () => {
122+
delete mockInputs.props.functionName;
123+
mockInputs.args = ['list'];
124+
expect(() => new Alias(mockInputs)).toThrow(
125+
'Function name not specified, please specify --function-name',
126+
);
127+
});
128+
});
129+
130+
describe('list', () => {
131+
beforeEach(() => {
132+
mockInputs.args = ['list'];
133+
alias = new Alias(mockInputs);
134+
});
135+
136+
it('should list aliases', async () => {
137+
const result = await alias.list();
138+
expect(result).toBeDefined();
139+
expect(mockFcInstance.listAlias).toHaveBeenCalledWith('test-function');
140+
});
141+
142+
it('should show aliases in table format when table flag is provided', async () => {
143+
mockInputs.args = ['list', '--table'];
144+
alias = new Alias(mockInputs);
145+
await alias.list();
146+
expect(tableShow).toHaveBeenCalled();
147+
});
148+
});
149+
150+
describe('get', () => {
151+
beforeEach(() => {
152+
mockInputs.args = ['get', '--alias-name', 'test-alias'];
153+
alias = new Alias(mockInputs);
154+
});
155+
156+
it('should get alias configuration', async () => {
157+
const result = await alias.get();
158+
expect(result).toBeDefined();
159+
expect(mockFcInstance.getAlias).toHaveBeenCalledWith('test-function', 'test-alias');
160+
});
161+
162+
it('should throw error when aliasName is not specified', async () => {
163+
mockInputs.args = ['get'];
164+
alias = new Alias(mockInputs);
165+
await expect(alias.get()).rejects.toThrow(
166+
'Alias name not specified, please specify alias name',
167+
);
168+
});
169+
});
170+
171+
describe('publish', () => {
172+
beforeEach(() => {
173+
mockInputs.args = ['publish', '--alias-name', 'test-alias', '--version-id', '1'];
174+
alias = new Alias(mockInputs);
175+
});
176+
177+
it('should publish alias', async () => {
178+
const result = await alias.publish();
179+
expect(result).toBeDefined();
180+
expect(mockFcInstance.publishAlias).toHaveBeenCalledWith(
181+
'test-function',
182+
'test-alias',
183+
expect.objectContaining({
184+
aliasName: 'test-alias',
185+
versionId: '1',
186+
}),
187+
);
188+
});
189+
190+
it('should throw error when aliasName is not specified', async () => {
191+
mockInputs.args = ['publish', '--version-id', '1'];
192+
alias = new Alias(mockInputs);
193+
await expect(alias.publish()).rejects.toThrow(
194+
'Alias name not specified, please specify --alias-name',
195+
);
196+
});
197+
198+
it('should throw error when versionId is not specified', async () => {
199+
mockInputs.args = ['publish', '--alias-name', 'test-alias'];
200+
alias = new Alias(mockInputs);
201+
await expect(alias.publish()).rejects.toThrow(
202+
'Version ID not specified, please specify --version-id',
203+
);
204+
});
205+
206+
it('should resolve latest version', async () => {
207+
mockInputs.args = ['publish', '--alias-name', 'test-alias', '--version-id', 'latest'];
208+
alias = new Alias(mockInputs);
209+
await alias.publish();
210+
expect(mockFcInstance.getVersionLatest).toHaveBeenCalledWith('test-function');
211+
expect(mockFcInstance.publishAlias).toHaveBeenCalledWith(
212+
'test-function',
213+
'test-alias',
214+
expect.objectContaining({
215+
versionId: '1',
216+
}),
217+
);
218+
});
219+
220+
it('should throw error when latest version is not found', async () => {
221+
mockFcInstance.getVersionLatest.mockResolvedValueOnce({});
222+
mockInputs.args = ['publish', '--alias-name', 'test-alias', '--version-id', 'latest'];
223+
alias = new Alias(mockInputs);
224+
await expect(alias.publish()).rejects.toThrow('Not found versionId in the test-function');
225+
});
226+
227+
it('should parse additionalVersionWeight JSON', async () => {
228+
mockInputs.args = [
229+
'publish',
230+
'--alias-name',
231+
'test-alias',
232+
'--version-id',
233+
'1',
234+
'--additionalVersionWeight',
235+
'{"2":0.2}',
236+
];
237+
alias = new Alias(mockInputs);
238+
await alias.publish();
239+
expect(mockFcInstance.publishAlias).toHaveBeenCalledWith(
240+
'test-function',
241+
'test-alias',
242+
expect.objectContaining({
243+
additionalVersionWeight: { '2': 0.2 },
244+
}),
245+
);
246+
});
247+
248+
it('should throw error when additionalVersionWeight is not valid JSON', async () => {
249+
mockInputs.args = [
250+
'publish',
251+
'--alias-name',
252+
'test-alias',
253+
'--version-id',
254+
'1',
255+
'--additionalVersionWeight',
256+
'invalid-json',
257+
];
258+
alias = new Alias(mockInputs);
259+
await expect(alias.publish()).rejects.toThrow(
260+
'The incoming additionalVersionWeight is not a JSON. e.g.: The grayscale version is 1, accounting for 20%: \'{"1":0.2}\'',
261+
);
262+
});
263+
});
264+
265+
describe('remove', () => {
266+
beforeEach(() => {
267+
mockInputs.args = ['remove', '--alias-name', 'test-alias', '--assume-yes'];
268+
alias = new Alias(mockInputs);
269+
});
270+
271+
it('should remove alias', async () => {
272+
await alias.remove();
273+
expect(mockFcInstance.removeAlias).toHaveBeenCalledWith('test-function', 'test-alias');
274+
});
275+
276+
it('should throw error when aliasName is not specified', async () => {
277+
mockInputs.args = ['remove', '--assume-yes'];
278+
alias = new Alias(mockInputs);
279+
await expect(alias.remove()).rejects.toThrow(
280+
'Alias name not specified, please specify --alias-name',
281+
);
282+
});
283+
284+
it('should prompt for confirmation when --assume-yes is not provided', async () => {
285+
mockInputs.args = ['remove', '--alias-name', 'test-alias'];
286+
alias = new Alias(mockInputs);
287+
await alias.remove();
288+
expect(promptForConfirmOrDetails).toHaveBeenCalledWith(
289+
'Are you sure you want to delete the test-function function test-alias alias?',
290+
);
291+
});
292+
293+
it('should skip removal when user declines confirmation', async () => {
294+
(promptForConfirmOrDetails as jest.Mock).mockResolvedValueOnce(false);
295+
mockInputs.args = ['remove', '--alias-name', 'test-alias'];
296+
alias = new Alias(mockInputs);
297+
await alias.remove();
298+
expect(mockFcInstance.removeAlias).not.toHaveBeenCalled();
299+
expect(logger.debug).toHaveBeenCalledWith(
300+
'Skip remove test-function function test-alias alias?',
301+
);
302+
});
303+
});
304+
});

0 commit comments

Comments
 (0)