Skip to content

Commit cba8765

Browse files
authored
Merge pull request #70 from devsapp/fix-endpoint
fix: improve provision config handling and support cn-heyuan-acdr-1 …
2 parents 271b8b2 + f597f89 commit cba8765

File tree

16 files changed

+477
-53
lines changed

16 files changed

+477
-53
lines changed

.gitignore

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -59,4 +59,6 @@ __tests__/e2e/python/code/python
5959
__tests__/e2e/python/code/apt-archives
6060
__tests__/e2e/apt/code/package-lock.json
6161

62-
.env_test
62+
.env_test
63+
64+
CLAUDE.md

__tests__/__mocks__/schema.json

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
{
2+
"type": "object",
3+
"properties": {
4+
"functionName": { "type": "string" },
5+
"region": { "type": "string" },
6+
"runtime": { "type": "string" }
7+
},
8+
"required": ["functionName", "region", "runtime"]
9+
}

__tests__/ut/crc64_test.ts

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
import { calculateCRC64 } from '../../src/utils/index';
2+
3+
// Mock the entire crc64 module
4+
jest.mock('crc64-ecma182.js', () => ({
5+
crc64File: jest.fn((filePath: string, callback: (err: Error | null, result?: string) => void) => {
6+
callback(null, '1234567890123456');
7+
}),
8+
}));
9+
10+
describe('calculateCRC64', () => {
11+
const mockFilePath = '/tmp/test-file.txt';
12+
13+
beforeEach(() => {
14+
jest.clearAllMocks();
15+
});
16+
17+
it('should calculate CRC64 successfully', async () => {
18+
const result = await calculateCRC64(mockFilePath);
19+
expect(result).toBe('1234567890123456');
20+
});
21+
});

__tests__/ut/downloadFile_test.ts

Lines changed: 83 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,83 @@
1+
import { downloadFile } from '../../src/utils/index';
2+
import axios from 'axios';
3+
import * as fs from 'fs';
4+
import { Readable, Writable } from 'stream';
5+
6+
// Mock axios and fs
7+
jest.mock('axios');
8+
jest.mock('fs');
9+
10+
describe('downloadFile', () => {
11+
const mockUrl = 'https://example.com/file.zip';
12+
const mockFilePath = '/tmp/test-file.zip';
13+
14+
beforeEach(() => {
15+
jest.clearAllMocks();
16+
});
17+
18+
it('should download file successfully', async () => {
19+
// Create a mock readable stream
20+
const mockStream = new Readable({
21+
read() {
22+
this.push('test data');
23+
this.push(null); // End the stream
24+
},
25+
});
26+
27+
// Mock axios response
28+
(axios as jest.MockedFunction<typeof axios>).mockResolvedValue({
29+
data: mockStream,
30+
} as any);
31+
32+
// Create a mock writable stream
33+
let finishCallback: Function | null = null;
34+
const mockWriteStream = new Writable({
35+
write(chunk: any, encoding: any, callback: any) {
36+
callback();
37+
},
38+
});
39+
40+
// Override the on method to capture the finish callback
41+
const originalOn = mockWriteStream.on.bind(mockWriteStream);
42+
mockWriteStream.on = jest.fn((event: string, handler: Function) => {
43+
if (event === 'finish') {
44+
finishCallback = handler;
45+
}
46+
return originalOn(event, handler);
47+
});
48+
49+
(fs.createWriteStream as jest.MockedFunction<typeof fs.createWriteStream>).mockReturnValue(
50+
mockWriteStream as any,
51+
);
52+
53+
// Start the download
54+
const downloadPromise = downloadFile(mockUrl, mockFilePath);
55+
56+
// Simulate the finish event
57+
setTimeout(() => {
58+
if (finishCallback) {
59+
finishCallback();
60+
}
61+
}, 10);
62+
63+
// Wait for the download to complete
64+
await expect(downloadPromise).resolves.toBeUndefined();
65+
66+
// Verify axios was called correctly
67+
expect(axios).toHaveBeenCalledWith({
68+
url: mockUrl,
69+
method: 'GET',
70+
responseType: 'stream',
71+
});
72+
73+
// Verify createWriteStream was called
74+
expect(fs.createWriteStream).toHaveBeenCalledWith(mockFilePath);
75+
});
76+
77+
it('should throw error when download fails', async () => {
78+
const errorMessage = 'Network error';
79+
(axios as jest.MockedFunction<typeof axios>).mockRejectedValue(new Error(errorMessage));
80+
81+
await expect(downloadFile(mockUrl, mockFilePath)).rejects.toThrow(errorMessage);
82+
});
83+
});

__tests__/ut/fc_client_test.ts

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
import FC_Client from '../../src/resources/fc/impl/client';
2+
import { ICredentials } from '@serverless-devs/component-interface';
3+
import log from '../../src/logger';
4+
log._set(console);
5+
6+
describe('FC_Client', () => {
7+
const mockCredentials: ICredentials = {
8+
AccountID: 'test-account',
9+
AccessKeyID: 'test-access-key-id',
10+
AccessKeySecret: 'test-access-key-secret',
11+
SecurityToken: 'test-security-token',
12+
};
13+
const mockRegion = 'cn-hangzhou';
14+
15+
beforeEach(() => {
16+
jest.clearAllMocks();
17+
});
18+
19+
it('should create an instance of FC_Client', () => {
20+
const fcClient = new FC_Client(mockRegion, mockCredentials, { timeout: 10000 });
21+
expect(fcClient).toBeInstanceOf(FC_Client);
22+
expect(fcClient.region).toBe(mockRegion);
23+
});
24+
});
Lines changed: 83 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,83 @@
1+
import { transformCustomDomainProps } from '../../src/utils/index';
2+
import log from '../../src/logger';
3+
log._set(console);
4+
5+
describe('transformCustomDomainProps', () => {
6+
it('should transform custom domain props correctly', () => {
7+
const local = {
8+
domainName: 'example.com',
9+
protocol: 'HTTP',
10+
certConfig: { certId: 'cert-123' },
11+
tlsConfig: { minVersion: 'TLSv1.2' },
12+
authConfig: { authType: 'anonymous' },
13+
wafConfig: { enable: true },
14+
route: {
15+
path: '/api/*',
16+
serviceName: 'my-service',
17+
},
18+
};
19+
20+
const region = 'cn-hangzhou';
21+
const functionName = 'my-function';
22+
23+
const result = transformCustomDomainProps(local, region, functionName);
24+
25+
expect(result).toEqual({
26+
region,
27+
domainName: 'example.com',
28+
protocol: 'HTTP',
29+
certConfig: { certId: 'cert-123' },
30+
tlsConfig: { minVersion: 'TLSv1.2' },
31+
authConfig: { authType: 'anonymous' },
32+
wafConfig: { enable: true },
33+
routeConfig: {
34+
routes: [
35+
{
36+
path: '/api/*',
37+
serviceName: 'my-service',
38+
functionName,
39+
},
40+
],
41+
},
42+
});
43+
});
44+
45+
it('should handle empty route', () => {
46+
const local = {
47+
domainName: 'example.com',
48+
protocol: 'HTTP',
49+
route: {},
50+
};
51+
52+
const region = 'cn-hangzhou';
53+
const functionName = 'my-function';
54+
55+
const result = transformCustomDomainProps(local, region, functionName);
56+
57+
expect(result.routeConfig.routes[0]).toEqual({
58+
functionName,
59+
});
60+
});
61+
62+
it('should filter out undefined values', () => {
63+
const local = {
64+
domainName: 'example.com',
65+
protocol: 'HTTP',
66+
certConfig: undefined,
67+
route: {
68+
path: '/api/*',
69+
},
70+
};
71+
72+
const region = 'cn-hangzhou';
73+
const functionName = 'my-function';
74+
75+
const result = transformCustomDomainProps(local, region, functionName);
76+
77+
expect(result).not.toHaveProperty('certConfig');
78+
expect(result.routeConfig.routes[0]).toEqual({
79+
path: '/api/*',
80+
functionName,
81+
});
82+
});
83+
});
Lines changed: 80 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,80 @@
1+
import { isAuto, isAutoVpcConfig, getTimeZone, sleep } from '../../src/utils/index';
2+
import log from '../../src/logger';
3+
log._set(console);
4+
5+
describe('Utils functions', () => {
6+
describe('isAuto', () => {
7+
it('should return true for "AUTO" string', () => {
8+
expect(isAuto('AUTO')).toBe(true);
9+
expect(isAuto('auto')).toBe(true);
10+
expect(isAuto('Auto')).toBe(true);
11+
});
12+
13+
it('should return false for non-"AUTO" strings', () => {
14+
expect(isAuto('manual')).toBe(false);
15+
expect(isAuto('other')).toBe(false);
16+
});
17+
18+
it('should return false for non-string values', () => {
19+
expect(isAuto(123)).toBe(false);
20+
expect(isAuto(null)).toBe(false);
21+
expect(isAuto(undefined)).toBe(false);
22+
expect(isAuto({})).toBe(false);
23+
expect(isAuto([])).toBe(false);
24+
});
25+
});
26+
27+
describe('isAutoVpcConfig', () => {
28+
it('should return true for "AUTO" string', () => {
29+
expect(isAutoVpcConfig('AUTO')).toBe(true);
30+
expect(isAutoVpcConfig('auto')).toBe(true);
31+
});
32+
33+
it('should return true for object with AUTO vSwitchIds', () => {
34+
const config = {
35+
vpcId: 'vpc-123',
36+
vSwitchIds: 'auto',
37+
};
38+
expect(isAutoVpcConfig(config)).toBe(true);
39+
});
40+
41+
it('should return true for object with AUTO securityGroupId', () => {
42+
const config = {
43+
vpcId: 'vpc-123',
44+
securityGroupId: 'auto',
45+
};
46+
expect(isAutoVpcConfig(config)).toBe(true);
47+
});
48+
49+
it('should return false for object without vpcId', () => {
50+
const config = {
51+
vSwitchIds: 'auto',
52+
};
53+
expect(isAutoVpcConfig(config)).toBe(false);
54+
});
55+
56+
it('should return false for non-auto configs', () => {
57+
const config = {
58+
vpcId: 'vpc-123',
59+
vSwitchIds: 'vsw-123',
60+
};
61+
expect(isAutoVpcConfig(config)).toBe(false);
62+
});
63+
});
64+
65+
describe('getTimeZone', () => {
66+
it('should return a valid timezone string', () => {
67+
const tz = getTimeZone();
68+
expect(tz).toMatch(/^UTC[+-]\d+$/);
69+
});
70+
});
71+
72+
describe('sleep', () => {
73+
it('should resolve after specified time', async () => {
74+
const start = Date.now();
75+
await sleep(0.01); // 10ms
76+
const end = Date.now();
77+
expect(end - start).toBeGreaterThanOrEqual(10);
78+
});
79+
});
80+
});

__tests__/ut/verify_simple_test.ts

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
import verify from '../../src/utils/verify';
2+
import logger from '../../src/logger';
3+
logger._set(console);
4+
5+
describe('verify', () => {
6+
it('should not throw errors for valid props', () => {
7+
const props = {
8+
functionName: 'test-function',
9+
region: 'cn-hangzhou' as const,
10+
runtime: 'nodejs18' as const,
11+
handler: 'index.handler',
12+
code: '/code',
13+
};
14+
15+
expect(() => verify(props)).not.toThrow();
16+
});
17+
});

__tests__/ut/verify_test.ts

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
import verify from '../../src/utils/verify';
2+
import logger from '../../src/logger';
3+
4+
// Mock logger
5+
jest.mock('../../src/logger', () => ({
6+
debug: jest.fn(),
7+
}));
8+
9+
describe('verify', () => {
10+
beforeEach(() => {
11+
jest.clearAllMocks();
12+
});
13+
14+
it('should validate correct props without errors', () => {
15+
const props = {
16+
functionName: 'test-function',
17+
region: 'cn-hangzhou' as const,
18+
runtime: 'nodejs18' as const,
19+
};
20+
21+
verify(props);
22+
23+
expect(logger.debug).toHaveBeenCalledWith(expect.stringContaining('Validating file path:'));
24+
});
25+
26+
it('should log validation errors for invalid props', () => {
27+
const props = {
28+
functionName: 'test-function',
29+
region: 'cn-hangzhou' as const,
30+
// missing required runtime
31+
} as any; // Cast to any to bypass TypeScript checks for this test
32+
33+
verify(props);
34+
35+
expect(logger.debug).toHaveBeenCalledWith(expect.stringContaining('Validating file path:'));
36+
});
37+
});

src/default/config.ts

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -79,4 +79,9 @@ export const FC_INSTANCE_EXEC_TIMEOUT: number = parseInt(
7979
10,
8080
);
8181

82+
export const FC_CONTAINER_ACCELERATED_TIMEOUT: number = parseInt(
83+
process.env.FC_CONTAINER_ACCELERATED_TIMEOUT || '3',
84+
10,
85+
);
86+
8287
export const FC_DEPLOY_RETRY_COUNT = 3;

0 commit comments

Comments
 (0)