Skip to content

Commit d27dbe0

Browse files
authored
Merge pull request #464 from TencentCloudBase/fix/runtime-validation-stability
fix(runtime-validation): 🧪 stabilize event runtime validation tests
2 parents 75db88c + 261fc4a commit d27dbe0

3 files changed

Lines changed: 64 additions & 34 deletions

File tree

mcp/src/tools/functions.test.ts

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,9 @@
11
import { beforeEach, describe, expect, it, vi } from "vitest";
22
import {
33
buildFunctionOperationErrorMessage,
4+
DEFAULT_RUNTIME,
45
registerFunctionTools,
6+
resolveEventFunctionRuntime,
57
shouldInstallDependencyForFunction,
68
} from "./functions.js";
79
import type { ExtendedMcpServer } from "../server.js";
@@ -111,6 +113,20 @@ describe("functions tool helpers", () => {
111113
expect(message).toContain("package.json");
112114
});
113115

116+
it("normalizes supported Event runtimes with whitespace", () => {
117+
expect(resolveEventFunctionRuntime("Python 3.9")).toBe("Python3.9");
118+
expect(resolveEventFunctionRuntime("Php 7.4")).toBe("Php7.4");
119+
});
120+
121+
it("falls back to the default runtime when Event runtime is omitted", () => {
122+
expect(resolveEventFunctionRuntime(undefined)).toBe(DEFAULT_RUNTIME);
123+
});
124+
125+
it("rejects unsupported Event runtimes with a helpful message", () => {
126+
expect(() => resolveEventFunctionRuntime("Ruby3.2")).toThrow(//);
127+
expect(() => resolveEventFunctionRuntime("Ruby3.2")).toThrow(/Python3.9/);
128+
});
129+
114130
it("guides HTTP functions through anonymous-access follow-up without auto-creating gateway access", async () => {
115131
const result = await tools.manageFunctions.handler({
116132
action: "createFunction",

mcp/src/tools/functions.ts

Lines changed: 22 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -308,6 +308,21 @@ export function shouldInstallDependencyForFunction(
308308
return true;
309309
}
310310

311+
export function resolveEventFunctionRuntime(runtime: unknown): string {
312+
if (typeof runtime !== "string" || !runtime.trim()) {
313+
return DEFAULT_RUNTIME;
314+
}
315+
316+
const normalizedRuntime = runtime.replace(/\s+/g, "");
317+
if ((ALL_SUPPORTED_RUNTIMES as readonly string[]).includes(normalizedRuntime)) {
318+
return normalizedRuntime;
319+
}
320+
321+
throw new Error(
322+
`不支持的运行时环境: "${String(runtime)}"\n\n支持的运行时:\n${formatRuntimeList()}`,
323+
);
324+
}
325+
311326
export function buildFunctionOperationErrorMessage(
312327
operation: "createFunction" | "updateFunctionCode",
313328
functionName: string,
@@ -820,26 +835,16 @@ export function registerFunctionTools(server: ExtendedMcpServer) {
820835
);
821836

822837
if (func.type !== "HTTP") {
823-
if (!func.runtime || typeof func.runtime !== "string") {
824-
func.runtime = DEFAULT_RUNTIME;
825-
} else {
826-
const normalizedRuntime = func.runtime.replace(/\s+/g, "");
827-
if ((ALL_SUPPORTED_RUNTIMES as readonly string[]).includes(normalizedRuntime)) {
828-
func.runtime = normalizedRuntime;
829-
} else if (func.runtime.includes(" ")) {
830-
console.warn(
831-
`检测到 runtime 参数包含空格: "${func.runtime}",已自动移除空格`,
832-
);
833-
func.runtime = normalizedRuntime;
834-
}
835-
}
838+
const originalRuntime = typeof func.runtime === "string" ? func.runtime : undefined;
839+
func.runtime = resolveEventFunctionRuntime(func.runtime);
836840

837841
if (
838-
typeof func.runtime !== "string" ||
839-
!(ALL_SUPPORTED_RUNTIMES as readonly string[]).includes(func.runtime)
842+
typeof originalRuntime === "string" &&
843+
originalRuntime.includes(" ") &&
844+
originalRuntime.replace(/\s+/g, "") === func.runtime
840845
) {
841-
throw new Error(
842-
`不支持的运行时环境: "${String(func.runtime)}"\n\n支持的运行时:\n${formatRuntimeList()}`,
846+
console.warn(
847+
`检测到 runtime 参数包含空格: "${originalRuntime}",已自动移除空格`,
843848
);
844849
}
845850
}

tests/runtime-validation.test.js

Lines changed: 26 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -34,8 +34,16 @@ if (existsSync(envPath)) {
3434
console.log('✅ Loaded .env file from:', envPath);
3535
}
3636

37-
// Helper function to delay
38-
const delay = (ms) => new Promise(resolve => setTimeout(resolve, ms));
37+
async function waitForToolList(client, timeoutMs = 5000) {
38+
return Promise.race([
39+
client.listTools(),
40+
new Promise((_, reject) => {
41+
setTimeout(() => {
42+
reject(new Error(`MCP server did not become ready within ${timeoutMs}ms`));
43+
}, timeoutMs);
44+
}),
45+
]);
46+
}
3947

4048
// Detect if real CloudBase credentials are available
4149
function hasCloudBaseCredentials() {
@@ -48,16 +56,19 @@ function hasCloudBaseCredentials() {
4856
describe('Runtime Validation - Event Function Multi-Language Support', () => {
4957
let testClient = null;
5058
let testTransport = null;
51-
const testFunctionsDir = join(__dirname, '../temp-runtime-test');
5259
const timestamp = Date.now();
60+
const testFunctionsDir = join(__dirname, `../temp-runtime-test-${timestamp}`);
5361

5462
beforeAll(async () => {
5563
if (!hasCloudBaseCredentials()) {
5664
console.log('⚠️ No CloudBase credentials, skipping runtime validation tests');
5765
return;
5866
}
5967

60-
// Create test client
68+
if (!existsSync(testFunctionsDir)) {
69+
mkdirSync(testFunctionsDir, { recursive: true });
70+
}
71+
6172
const client = new Client({
6273
name: 'runtime-validation-client',
6374
version: '1.0.0',
@@ -67,35 +78,33 @@ describe('Runtime Validation - Event Function Multi-Language Support', () => {
6778

6879
const serverPath = join(__dirname, '../mcp/dist/cli.cjs');
6980
const transport = new StdioClientTransport({
70-
command: 'node',
81+
command: process.execPath,
7182
args: [serverPath],
7283
env: {
7384
...process.env,
7485
}
7586
});
7687

77-
await client.connect(transport);
78-
await delay(2000);
79-
80-
testClient = client;
81-
testTransport = transport;
82-
83-
// Create test functions directory
84-
if (!existsSync(testFunctionsDir)) {
85-
mkdirSync(testFunctionsDir, { recursive: true });
88+
try {
89+
await client.connect(transport);
90+
await waitForToolList(client, 5000);
91+
testClient = client;
92+
testTransport = transport;
93+
} catch (error) {
94+
await transport.close().catch(() => {});
95+
throw error;
8696
}
87-
});
97+
}, 30000);
8898

8999
afterAll(async () => {
90100
if (testTransport) {
91101
await testTransport.close();
92102
}
93103

94-
// Clean up test directory
95104
if (existsSync(testFunctionsDir)) {
96105
rmSync(testFunctionsDir, { recursive: true, force: true });
97106
}
98-
});
107+
}, 10000);
99108

100109
// Helper function to create a simple Python Event function
101110
function createPythonFunction(functionName) {

0 commit comments

Comments
 (0)