mirror of
https://github.com/claude-code-best/claude-code.git
synced 2026-06-15 21:05:51 +00:00
test: Phase 5 — 添加 12 个测试文件 (+209 tests, 1177 total)
新增覆盖: effort, tokenBudget, displayTags, taggedId, controlMessageCompat, MCP normalization/envExpansion, gitConfigParser, formatBriefTimestamp, hyperlink, windowsPaths, notebook Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
139
src/services/mcp/__tests__/envExpansion.test.ts
Normal file
139
src/services/mcp/__tests__/envExpansion.test.ts
Normal file
@@ -0,0 +1,139 @@
|
||||
import { describe, expect, test, beforeEach, afterEach } from "bun:test";
|
||||
import { expandEnvVarsInString } from "../envExpansion";
|
||||
|
||||
describe("expandEnvVarsInString", () => {
|
||||
// Save and restore env vars touched by tests
|
||||
const savedEnv: Record<string, string | undefined> = {};
|
||||
const trackedKeys = [
|
||||
"TEST_HOME",
|
||||
"MISSING",
|
||||
"TEST_A",
|
||||
"TEST_B",
|
||||
"TEST_EMPTY",
|
||||
"TEST_X",
|
||||
"VAR",
|
||||
"TEST_FOUND",
|
||||
];
|
||||
|
||||
beforeEach(() => {
|
||||
for (const key of trackedKeys) {
|
||||
savedEnv[key] = process.env[key];
|
||||
}
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
for (const key of trackedKeys) {
|
||||
if (savedEnv[key] === undefined) {
|
||||
delete process.env[key];
|
||||
} else {
|
||||
process.env[key] = savedEnv[key];
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
test("expands a single env var that exists", () => {
|
||||
process.env.TEST_HOME = "/home/user";
|
||||
const result = expandEnvVarsInString("${TEST_HOME}");
|
||||
expect(result.expanded).toBe("/home/user");
|
||||
expect(result.missingVars).toEqual([]);
|
||||
});
|
||||
|
||||
test("returns original placeholder and tracks missing var when not found", () => {
|
||||
delete process.env.MISSING;
|
||||
const result = expandEnvVarsInString("${MISSING}");
|
||||
expect(result.expanded).toBe("${MISSING}");
|
||||
expect(result.missingVars).toEqual(["MISSING"]);
|
||||
});
|
||||
|
||||
test("uses default value when var is missing and default is provided", () => {
|
||||
delete process.env.MISSING;
|
||||
const result = expandEnvVarsInString("${MISSING:-fallback}");
|
||||
expect(result.expanded).toBe("fallback");
|
||||
expect(result.missingVars).toEqual([]);
|
||||
});
|
||||
|
||||
test("expands multiple vars", () => {
|
||||
process.env.TEST_A = "hello";
|
||||
process.env.TEST_B = "world";
|
||||
const result = expandEnvVarsInString("${TEST_A}/${TEST_B}");
|
||||
expect(result.expanded).toBe("hello/world");
|
||||
expect(result.missingVars).toEqual([]);
|
||||
});
|
||||
|
||||
test("handles mix of found and missing vars", () => {
|
||||
process.env.TEST_FOUND = "yes";
|
||||
delete process.env.MISSING;
|
||||
const result = expandEnvVarsInString("${TEST_FOUND}-${MISSING}");
|
||||
expect(result.expanded).toBe("yes-${MISSING}");
|
||||
expect(result.missingVars).toEqual(["MISSING"]);
|
||||
});
|
||||
|
||||
test("returns plain string unchanged with empty missingVars", () => {
|
||||
const result = expandEnvVarsInString("plain string");
|
||||
expect(result.expanded).toBe("plain string");
|
||||
expect(result.missingVars).toEqual([]);
|
||||
});
|
||||
|
||||
test("expands empty env var value", () => {
|
||||
process.env.TEST_EMPTY = "";
|
||||
const result = expandEnvVarsInString("${TEST_EMPTY}");
|
||||
expect(result.expanded).toBe("");
|
||||
expect(result.missingVars).toEqual([]);
|
||||
});
|
||||
|
||||
test("prefers env var value over default when var exists", () => {
|
||||
process.env.TEST_X = "real";
|
||||
const result = expandEnvVarsInString("${TEST_X:-default}");
|
||||
expect(result.expanded).toBe("real");
|
||||
expect(result.missingVars).toEqual([]);
|
||||
});
|
||||
|
||||
test("handles default value containing colons", () => {
|
||||
// split(':-', 2) means only the first :- is the delimiter
|
||||
delete process.env.TEST_X;
|
||||
const result = expandEnvVarsInString("${TEST_X:-value:-with:-colons}");
|
||||
// The default is "value" because split(':-', 2) gives ["TEST_X", "value"]
|
||||
// Wait -- actually split(':-', 2) on "TEST_X:-value:-with:-colons" gives:
|
||||
// ["TEST_X", "value"] because limit=2 stops at 2 pieces
|
||||
expect(result.expanded).toBe("value");
|
||||
expect(result.missingVars).toEqual([]);
|
||||
});
|
||||
|
||||
test("handles nested-looking syntax as literal (not supported)", () => {
|
||||
// ${${VAR}} - the regex [^}]+ matches "${VAR" (up to first })
|
||||
// so varName would be "${VAR" which won't be found in env
|
||||
delete process.env.VAR;
|
||||
const result = expandEnvVarsInString("${${VAR}}");
|
||||
// The regex \$\{([^}]+)\} matches "${${VAR}" with capture "${VAR"
|
||||
// That env var won't exist, so it stays as "${${VAR}" + remaining "}"
|
||||
expect(result.missingVars).toEqual(["${VAR"]);
|
||||
expect(result.expanded).toBe("${${VAR}}");
|
||||
});
|
||||
|
||||
test("handles empty string input", () => {
|
||||
const result = expandEnvVarsInString("");
|
||||
expect(result.expanded).toBe("");
|
||||
expect(result.missingVars).toEqual([]);
|
||||
});
|
||||
|
||||
test("handles var surrounded by text", () => {
|
||||
process.env.TEST_A = "middle";
|
||||
const result = expandEnvVarsInString("before-${TEST_A}-after");
|
||||
expect(result.expanded).toBe("before-middle-after");
|
||||
expect(result.missingVars).toEqual([]);
|
||||
});
|
||||
|
||||
test("handles default value that is empty string", () => {
|
||||
delete process.env.MISSING;
|
||||
const result = expandEnvVarsInString("${MISSING:-}");
|
||||
expect(result.expanded).toBe("");
|
||||
expect(result.missingVars).toEqual([]);
|
||||
});
|
||||
|
||||
test("does not expand $VAR without braces", () => {
|
||||
process.env.TEST_A = "value";
|
||||
const result = expandEnvVarsInString("$TEST_A");
|
||||
expect(result.expanded).toBe("$TEST_A");
|
||||
expect(result.missingVars).toEqual([]);
|
||||
});
|
||||
});
|
||||
59
src/services/mcp/__tests__/normalization.test.ts
Normal file
59
src/services/mcp/__tests__/normalization.test.ts
Normal file
@@ -0,0 +1,59 @@
|
||||
import { describe, expect, test } from "bun:test";
|
||||
import { normalizeNameForMCP } from "../normalization";
|
||||
|
||||
describe("normalizeNameForMCP", () => {
|
||||
test("returns simple valid name unchanged", () => {
|
||||
expect(normalizeNameForMCP("my-server")).toBe("my-server");
|
||||
});
|
||||
|
||||
test("replaces dots with underscores", () => {
|
||||
expect(normalizeNameForMCP("my.server.name")).toBe("my_server_name");
|
||||
});
|
||||
|
||||
test("replaces spaces with underscores", () => {
|
||||
expect(normalizeNameForMCP("my server")).toBe("my_server");
|
||||
});
|
||||
|
||||
test("replaces special characters with underscores", () => {
|
||||
expect(normalizeNameForMCP("server@v2!")).toBe("server_v2_");
|
||||
});
|
||||
|
||||
test("returns already valid name unchanged", () => {
|
||||
expect(normalizeNameForMCP("valid_name-123")).toBe("valid_name-123");
|
||||
});
|
||||
|
||||
test("returns empty string for empty input", () => {
|
||||
expect(normalizeNameForMCP("")).toBe("");
|
||||
});
|
||||
|
||||
test("handles claude.ai prefix: collapses consecutive underscores and strips edges", () => {
|
||||
// "claude.ai My Server" -> replace invalid -> "claude_ai_My_Server"
|
||||
// starts with "claude.ai " so collapse + strip -> "claude_ai_My_Server"
|
||||
expect(normalizeNameForMCP("claude.ai My Server")).toBe(
|
||||
"claude_ai_My_Server"
|
||||
);
|
||||
});
|
||||
|
||||
test("handles claude.ai prefix with consecutive invalid chars", () => {
|
||||
// "claude.ai ...test..." -> replace invalid -> "claude_ai____test___"
|
||||
// collapse consecutive _ -> "claude_ai_test_"
|
||||
// strip leading/trailing _ -> "claude_ai_test"
|
||||
expect(normalizeNameForMCP("claude.ai ...test...")).toBe("claude_ai_test");
|
||||
});
|
||||
|
||||
test("non-claude.ai name preserves consecutive underscores", () => {
|
||||
// "a..b" -> "a__b", no claude.ai prefix so no collapse
|
||||
expect(normalizeNameForMCP("a..b")).toBe("a__b");
|
||||
});
|
||||
|
||||
test("non-claude.ai name preserves trailing underscores", () => {
|
||||
expect(normalizeNameForMCP("name!")).toBe("name_");
|
||||
});
|
||||
|
||||
test("handles claude.ai prefix that results in only underscores", () => {
|
||||
// "claude.ai ..." -> replace invalid -> "claude_ai____"
|
||||
// collapse -> "claude_ai_"
|
||||
// strip trailing -> "claude_ai"
|
||||
expect(normalizeNameForMCP("claude.ai ...")).toBe("claude_ai");
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user