import { afterEach, beforeEach, describe, expect, it } from "vitest";
import "./setup.js";
import createServer from "../src/server/smithery.js";
import { getServerContext } from "../src/mcp/server.js";
import { closeServerDefensively, invokeInitialize } from "./helpers/server.js";
const ORIGINAL_ENV = { ...process.env };
beforeEach(() => {
process.env = { ...ORIGINAL_ENV };
delete process.env.CLICKUP_TOKEN;
delete process.env.CLICKUP_DEFAULT_TEAM_ID;
delete process.env.CLICKUP_PRIMARY_LANGUAGE;
delete process.env.CLICKUP_BASE_URL;
delete process.env.CLICKUP_AUTH_SCHEME;
delete process.env.REQUEST_TIMEOUT_MS;
delete process.env.DEFAULT_HEADERS_JSON;
});
afterEach(() => {
process.env = { ...ORIGINAL_ENV };
});
describe("defensive config normalization", () => {
it("handles NaN defaultTeamId gracefully", async () => {
const server = await createServer({
config: {
apiToken: "test-token",
defaultTeamId: NaN
}
});
try {
await invokeInitialize(server);
const context = getServerContext(server);
// NaN should be normalized to undefined
expect(context.session.defaultTeamId).toBeUndefined();
} finally {
await closeServerDefensively(server);
}
});
it("handles Infinity defaultTeamId gracefully", async () => {
const server = await createServer({
config: {
apiToken: "test-token",
defaultTeamId: Infinity
}
});
try {
await invokeInitialize(server);
const context = getServerContext(server);
// Infinity should be normalized to undefined
expect(context.session.defaultTeamId).toBeUndefined();
} finally {
await closeServerDefensively(server);
}
});
it("handles negative Infinity defaultTeamId gracefully", async () => {
const server = await createServer({
config: {
apiToken: "test-token",
defaultTeamId: -Infinity
}
});
try {
await invokeInitialize(server);
const context = getServerContext(server);
// -Infinity should be normalized to undefined
expect(context.session.defaultTeamId).toBeUndefined();
} finally {
await closeServerDefensively(server);
}
});
it("handles malformed config without throwing", async () => {
const server = await createServer({
config: {
apiToken: 12345 as any, // wrong type
defaultTeamId: "not-a-number" as any, // wrong type
baseUrl: null as any, // null instead of string
primaryLanguage: ["array", "value"] as any // array instead of string
}
});
try {
await invokeInitialize(server);
const context = getServerContext(server);
// Should have normalized everything
expect(context.session).toBeDefined();
expect(context.session.baseUrl).toBe("https://api.clickup.com/api/v2");
} finally {
await closeServerDefensively(server);
}
});
it("logs successful config parsing", async () => {
const logs: string[] = [];
const originalLog = console.log;
console.log = (...args: unknown[]) => {
logs.push(JSON.stringify(args));
originalLog(...args);
};
try {
const server = await createServer({
config: {
apiToken: "test-token",
defaultTeamId: 12345,
primaryLanguage: "en-US"
}
});
try {
// Should have logged successful config normalization
const configLog = logs.find(log => log.includes("config_normalized"));
expect(configLog).toBeDefined();
// Should have logged config merge
const mergeLog = logs.find(log => log.includes("config_merged"));
expect(mergeLog).toBeDefined();
} finally {
await closeServerDefensively(server);
}
} finally {
console.log = originalLog;
}
});
it("falls back to environment when config has invalid defaultTeamId", async () => {
process.env.CLICKUP_DEFAULT_TEAM_ID = "99999";
const server = await createServer({
config: {
apiToken: "test-token",
defaultTeamId: NaN
}
});
try {
await invokeInitialize(server);
const context = getServerContext(server);
// Should use environment value when config is invalid
expect(context.session.defaultTeamId).toBe(99999);
} finally {
await closeServerDefensively(server);
}
});
it("handles completely empty malformed config", async () => {
const server = await createServer({
config: null as any
});
try {
const response = await invokeInitialize(server);
expect(response).toBeDefined();
const context = getServerContext(server);
expect(context.session).toBeDefined();
} finally {
await closeServerDefensively(server);
}
});
it("handles config with extra unknown fields", async () => {
const server = await createServer({
config: {
apiToken: "test-token",
defaultTeamId: 12345,
unknownField: "should be stripped",
anotherUnknown: 123
} as any
});
try {
const response = await invokeInitialize(server);
expect(response).toBeDefined();
const context = getServerContext(server);
expect(context.session.apiToken).toBe("test-token");
expect(context.session.defaultTeamId).toBe(12345);
} finally {
await closeServerDefensively(server);
}
});
it("handles zero defaultTeamId correctly", async () => {
const server = await createServer({
config: {
apiToken: "test-token",
defaultTeamId: 0
}
});
try {
await invokeInitialize(server);
const context = getServerContext(server);
// Zero is a valid value, should be preserved
expect(context.session.defaultTeamId).toBe(0);
} finally {
await closeServerDefensively(server);
}
});
it("handles negative requestTimeoutMs gracefully", async () => {
const server = await createServer({
config: {
apiToken: "test-token",
requestTimeoutMs: -5000
}
});
try {
await invokeInitialize(server);
const context = getServerContext(server);
// Negative timeout should be normalized to default (30s)
expect(context.session.requestTimeout).toBe(30);
} finally {
await closeServerDefensively(server);
}
});
it("config merge source logging is accurate", async () => {
process.env.CLICKUP_DEFAULT_TEAM_ID = "88888";
const logs: string[] = [];
const originalLog = console.log;
console.log = (...args: unknown[]) => {
logs.push(JSON.stringify(args));
originalLog(...args);
};
try {
const server = await createServer({
config: {
apiToken: "config-token",
primaryLanguage: "fr-FR"
}
});
try {
const mergeLog = logs.find(log => log.includes("config_merged"));
expect(mergeLog).toBeDefined();
// Should indicate smithery for fields from config
expect(mergeLog).toContain("smithery");
// Should indicate env for defaultTeamId (from environment)
expect(mergeLog).toContain("env");
} finally {
await closeServerDefensively(server);
}
} finally {
console.log = originalLog;
}
});
});