Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 3 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -59,4 +59,6 @@ __tests__/e2e/python/code/python
__tests__/e2e/python/code/apt-archives
__tests__/e2e/apt/code/package-lock.json

.env_test
.env_test

CLAUDE.md
9 changes: 9 additions & 0 deletions __tests__/__mocks__/schema.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
{
"type": "object",
"properties": {
"functionName": { "type": "string" },
"region": { "type": "string" },
"runtime": { "type": "string" }
},
"required": ["functionName", "region", "runtime"]
}
21 changes: 21 additions & 0 deletions __tests__/ut/crc64_test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
import { calculateCRC64 } from '../../src/utils/index';

// Mock the entire crc64 module
jest.mock('crc64-ecma182.js', () => ({
crc64File: jest.fn((filePath: string, callback: (err: Error | null, result?: string) => void) => {
callback(null, '1234567890123456');
}),
}));

describe('calculateCRC64', () => {
const mockFilePath = '/tmp/test-file.txt';

beforeEach(() => {
jest.clearAllMocks();
});

it('should calculate CRC64 successfully', async () => {
const result = await calculateCRC64(mockFilePath);
expect(result).toBe('1234567890123456');
});
});
83 changes: 83 additions & 0 deletions __tests__/ut/downloadFile_test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,83 @@
import { downloadFile } from '../../src/utils/index';
import axios from 'axios';
import * as fs from 'fs';
import { Readable, Writable } from 'stream';

// Mock axios and fs
jest.mock('axios');
jest.mock('fs');

describe('downloadFile', () => {
const mockUrl = 'https://example.com/file.zip';
const mockFilePath = '/tmp/test-file.zip';

beforeEach(() => {
jest.clearAllMocks();
});

it('should download file successfully', async () => {
// Create a mock readable stream
const mockStream = new Readable({
read() {
this.push('test data');
this.push(null); // End the stream
},
});

// Mock axios response
(axios as jest.MockedFunction<typeof axios>).mockResolvedValue({
data: mockStream,
} as any);

// Create a mock writable stream
let finishCallback: Function | null = null;
const mockWriteStream = new Writable({
write(chunk: any, encoding: any, callback: any) {
callback();
},
});

// Override the on method to capture the finish callback
const originalOn = mockWriteStream.on.bind(mockWriteStream);
mockWriteStream.on = jest.fn((event: string, handler: Function) => {
if (event === 'finish') {
finishCallback = handler;
}
return originalOn(event, handler);
});

(fs.createWriteStream as jest.MockedFunction<typeof fs.createWriteStream>).mockReturnValue(
mockWriteStream as any,
);

// Start the download
const downloadPromise = downloadFile(mockUrl, mockFilePath);

// Simulate the finish event
setTimeout(() => {
if (finishCallback) {
finishCallback();
}
}, 10);

// Wait for the download to complete
await expect(downloadPromise).resolves.toBeUndefined();

// Verify axios was called correctly
expect(axios).toHaveBeenCalledWith({
url: mockUrl,
method: 'GET',
responseType: 'stream',
});

// Verify createWriteStream was called
expect(fs.createWriteStream).toHaveBeenCalledWith(mockFilePath);
});

it('should throw error when download fails', async () => {
const errorMessage = 'Network error';
(axios as jest.MockedFunction<typeof axios>).mockRejectedValue(new Error(errorMessage));

await expect(downloadFile(mockUrl, mockFilePath)).rejects.toThrow(errorMessage);
});
});
24 changes: 24 additions & 0 deletions __tests__/ut/fc_client_test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
import FC_Client from '../../src/resources/fc/impl/client';
import { ICredentials } from '@serverless-devs/component-interface';
import log from '../../src/logger';
log._set(console);

describe('FC_Client', () => {
const mockCredentials: ICredentials = {
AccountID: 'test-account',
AccessKeyID: 'test-access-key-id',
AccessKeySecret: 'test-access-key-secret',
SecurityToken: 'test-security-token',
};
const mockRegion = 'cn-hangzhou';

beforeEach(() => {
jest.clearAllMocks();
});

it('should create an instance of FC_Client', () => {
const fcClient = new FC_Client(mockRegion, mockCredentials, { timeout: 10000 });
expect(fcClient).toBeInstanceOf(FC_Client);
expect(fcClient.region).toBe(mockRegion);
});
});
83 changes: 83 additions & 0 deletions __tests__/ut/transformCustomDomainProps_test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,83 @@
import { transformCustomDomainProps } from '../../src/utils/index';
import log from '../../src/logger';
log._set(console);

describe('transformCustomDomainProps', () => {
it('should transform custom domain props correctly', () => {
const local = {
domainName: 'example.com',
protocol: 'HTTP',
certConfig: { certId: 'cert-123' },
tlsConfig: { minVersion: 'TLSv1.2' },
authConfig: { authType: 'anonymous' },
wafConfig: { enable: true },
route: {
path: '/api/*',
serviceName: 'my-service',
},
};

const region = 'cn-hangzhou';
const functionName = 'my-function';

const result = transformCustomDomainProps(local, region, functionName);

expect(result).toEqual({
region,
domainName: 'example.com',
protocol: 'HTTP',
certConfig: { certId: 'cert-123' },
tlsConfig: { minVersion: 'TLSv1.2' },
authConfig: { authType: 'anonymous' },
wafConfig: { enable: true },
routeConfig: {
routes: [
{
path: '/api/*',
serviceName: 'my-service',
functionName,
},
],
},
});
});

it('should handle empty route', () => {
const local = {
domainName: 'example.com',
protocol: 'HTTP',
route: {},
};

const region = 'cn-hangzhou';
const functionName = 'my-function';

const result = transformCustomDomainProps(local, region, functionName);

expect(result.routeConfig.routes[0]).toEqual({
functionName,
});
});

it('should filter out undefined values', () => {
const local = {
domainName: 'example.com',
protocol: 'HTTP',
certConfig: undefined,
route: {
path: '/api/*',
},
};

const region = 'cn-hangzhou';
const functionName = 'my-function';

const result = transformCustomDomainProps(local, region, functionName);

expect(result).not.toHaveProperty('certConfig');
expect(result.routeConfig.routes[0]).toEqual({
path: '/api/*',
functionName,
});
});
});
80 changes: 80 additions & 0 deletions __tests__/ut/utils_functions_test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,80 @@
import { isAuto, isAutoVpcConfig, getTimeZone, sleep } from '../../src/utils/index';
import log from '../../src/logger';
log._set(console);

describe('Utils functions', () => {
describe('isAuto', () => {
it('should return true for "AUTO" string', () => {
expect(isAuto('AUTO')).toBe(true);
expect(isAuto('auto')).toBe(true);
expect(isAuto('Auto')).toBe(true);
});

it('should return false for non-"AUTO" strings', () => {
expect(isAuto('manual')).toBe(false);
expect(isAuto('other')).toBe(false);
});

it('should return false for non-string values', () => {
expect(isAuto(123)).toBe(false);
expect(isAuto(null)).toBe(false);
expect(isAuto(undefined)).toBe(false);
expect(isAuto({})).toBe(false);
expect(isAuto([])).toBe(false);
});
});

describe('isAutoVpcConfig', () => {
it('should return true for "AUTO" string', () => {
expect(isAutoVpcConfig('AUTO')).toBe(true);
expect(isAutoVpcConfig('auto')).toBe(true);
});

it('should return true for object with AUTO vSwitchIds', () => {
const config = {
vpcId: 'vpc-123',
vSwitchIds: 'auto',
};
expect(isAutoVpcConfig(config)).toBe(true);
});

it('should return true for object with AUTO securityGroupId', () => {
const config = {
vpcId: 'vpc-123',
securityGroupId: 'auto',
};
expect(isAutoVpcConfig(config)).toBe(true);
});

it('should return false for object without vpcId', () => {
const config = {
vSwitchIds: 'auto',
};
expect(isAutoVpcConfig(config)).toBe(false);
});

it('should return false for non-auto configs', () => {
const config = {
vpcId: 'vpc-123',
vSwitchIds: 'vsw-123',
};
expect(isAutoVpcConfig(config)).toBe(false);
});
});

describe('getTimeZone', () => {
it('should return a valid timezone string', () => {
const tz = getTimeZone();
expect(tz).toMatch(/^UTC[+-]\d+$/);
});
});

describe('sleep', () => {
it('should resolve after specified time', async () => {
const start = Date.now();
await sleep(0.01); // 10ms
const end = Date.now();
expect(end - start).toBeGreaterThanOrEqual(10);
});
});
});
17 changes: 17 additions & 0 deletions __tests__/ut/verify_simple_test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
import verify from '../../src/utils/verify';
import logger from '../../src/logger';
logger._set(console);

describe('verify', () => {
it('should not throw errors for valid props', () => {
const props = {
functionName: 'test-function',
region: 'cn-hangzhou' as const,
runtime: 'nodejs18' as const,
handler: 'index.handler',
code: '/code',
};

expect(() => verify(props)).not.toThrow();
});
});
37 changes: 37 additions & 0 deletions __tests__/ut/verify_test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
import verify from '../../src/utils/verify';
import logger from '../../src/logger';

// Mock logger
jest.mock('../../src/logger', () => ({
debug: jest.fn(),
}));

describe('verify', () => {
beforeEach(() => {
jest.clearAllMocks();
});

it('should validate correct props without errors', () => {
const props = {
functionName: 'test-function',
region: 'cn-hangzhou' as const,
runtime: 'nodejs18' as const,
};

verify(props);

expect(logger.debug).toHaveBeenCalledWith(expect.stringContaining('Validating file path:'));
});

it('should log validation errors for invalid props', () => {
const props = {
functionName: 'test-function',
region: 'cn-hangzhou' as const,
// missing required runtime
} as any; // Cast to any to bypass TypeScript checks for this test

verify(props);

expect(logger.debug).toHaveBeenCalledWith(expect.stringContaining('Validating file path:'));
});
});
5 changes: 5 additions & 0 deletions src/default/config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -79,4 +79,9 @@ export const FC_INSTANCE_EXEC_TIMEOUT: number = parseInt(
10,
);

export const FC_CONTAINER_ACCELERATED_TIMEOUT: number = parseInt(
process.env.FC_CONTAINER_ACCELERATED_TIMEOUT || '3',
10,
);

export const FC_DEPLOY_RETRY_COUNT = 3;
Loading
Loading