import { describe, it, expect, vi, beforeEach } from "vitest";
import { parseArgs } from "../args.js";
// Mock process.exit
vi.spyOn(process, "exit").mockImplementation(
(code?: number | string | null | undefined) => {
throw new Error(`process.exit(${code})`);
},
);
// Mock console.error
const mockConsoleError = vi
.spyOn(console, "error")
.mockImplementation(() => {});
describe("CLI Args", () => {
beforeEach(() => {
vi.clearAllMocks();
vi.unstubAllEnvs();
delete process.env["MYSQL_HOST"];
delete process.env["MYSQL_USER"];
delete process.env["MYSQL_PASSWORD"];
delete process.env["MYSQL_DATABASE"];
delete process.env["MYSQL_MCP_TOOL_FILTER"];
delete process.env["OAUTH_ENABLED"];
});
describe("parseArgs", () => {
it("should parse mysql connection string flag", () => {
const result = parseArgs(["--mysql", "mysql://user:pass@host:3306/db"]);
expect(result.databases).toHaveLength(1);
expect(result.databases[0]).toMatchObject({
host: "host",
username: "user",
password: "pass",
database: "db",
port: 3306,
});
});
it("should parse individual mysql flags", () => {
const result = parseArgs([
"--mysql-host",
"localhost",
"--mysql-user",
"root",
"--mysql-password",
"secret",
"--mysql-database",
"testdb",
"--mysql-port",
"3307",
]);
expect(result.databases).toHaveLength(1);
expect(result.databases[0]).toMatchObject({
host: "localhost",
username: "root",
password: "secret",
database: "testdb",
port: 3307,
});
});
it("should use environment variables for fallback", () => {
vi.stubEnv("MYSQL_HOST", "env-host");
vi.stubEnv("MYSQL_USER", "env-user");
const result = parseArgs([]);
// Partial config won't create a database entry unless user AND database are present
expect(result.databases).toHaveLength(0);
vi.unstubAllEnvs();
});
it("should print help and exit when --help flag is used", () => {
const result = parseArgs(["--help"]);
expect(result.shouldExit).toBe(true);
expect(mockConsoleError).toHaveBeenCalledWith(
expect.stringContaining("Usage: mysql-mcp [options]"),
);
});
it("should print help and exit when -h flag is used", () => {
const result = parseArgs(["-h"]);
expect(result.shouldExit).toBe(true);
expect(mockConsoleError).toHaveBeenCalledWith(
expect.stringContaining("Usage: mysql-mcp [options]"),
);
});
it("should use TOOL_FILTER environment variable", () => {
vi.stubEnv("TOOL_FILTER", "-admin");
const result = parseArgs([]);
expect(result.config.toolFilter).toBe("-admin");
vi.unstubAllEnvs();
});
it("should prefer MYSQL_MCP_TOOL_FILTER over TOOL_FILTER", () => {
vi.stubEnv("TOOL_FILTER", "-admin");
vi.stubEnv("MYSQL_MCP_TOOL_FILTER", "-security");
const result = parseArgs([]);
expect(result.config.toolFilter).toBe("-security");
vi.unstubAllEnvs();
});
it("should load oauth config from environment variables", () => {
vi.stubEnv("OAUTH_ENABLED", "true");
vi.stubEnv("OAUTH_ISSUER", "https://env-issuer.com");
vi.stubEnv("OAUTH_AUDIENCE", "env-aud");
vi.stubEnv("OAUTH_JWKS_URI", "https://jwks");
vi.stubEnv("OAUTH_CLOCK_TOLERANCE", "120");
const result = parseArgs([]);
expect(result.oauth).toBeDefined();
expect(result.oauth?.enabled).toBe(true);
expect(result.oauth?.issuer).toBe("https://env-issuer.com");
expect(result.oauth?.audience).toBe("env-aud");
expect(result.oauth?.jwksUri).toBe("https://jwks");
expect(result.oauth?.clockTolerance).toBe(120);
vi.unstubAllEnvs();
});
it("should build database config from environment variables if no arguments provided", () => {
vi.stubEnv("MYSQL_HOST", "env-host");
vi.stubEnv("MYSQL_USER", "env-user");
vi.stubEnv("MYSQL_PASSWORD", "env-pass");
vi.stubEnv("MYSQL_DATABASE", "env-db");
vi.stubEnv("MYSQL_PORT", "3307");
vi.stubEnv("MYSQL_POOL_SIZE", "20");
const result = parseArgs([]);
expect(result.databases).toHaveLength(1);
expect(result.databases[0]).toEqual(
expect.objectContaining({
host: "env-host",
username: "env-user",
port: 3307,
database: "env-db",
}),
);
expect(result.databases[0].pool?.connectionLimit).toBe(20);
vi.unstubAllEnvs();
});
it("should parse transport flags", () => {
const result = parseArgs(["--transport", "sse", "--port", "8080"]);
expect(result.config.transport).toBe("sse");
expect(result.config.port).toBe(8080);
const resultShort = parseArgs(["-t", "http", "-p", "9090"]);
expect(resultShort.config.transport).toBe("http");
expect(resultShort.config.port).toBe(9090);
});
it("should parse pool config flags", () => {
vi.stubEnv("MYSQL_HOST", "localhost");
vi.stubEnv("MYSQL_USER", "user");
vi.stubEnv("MYSQL_DATABASE", "db");
const resultEnv = parseArgs([
"--pool-size",
"25",
"--pool-timeout",
"6000",
"--pool-queue-limit",
"150",
]);
expect(resultEnv.databases[0].pool?.connectionLimit).toBe(25);
expect(resultEnv.databases[0].pool?.acquireTimeout).toBe(6000);
expect(resultEnv.databases[0].pool?.queueLimit).toBe(150);
vi.unstubAllEnvs();
});
it("should parse OAuth flags", () => {
const result = parseArgs([
"--oauth-enabled",
"--oauth-issuer",
"https://auth.com",
"--oauth-audience",
"api",
"--oauth-jwks-uri",
"https://jwks",
"--oauth-clock-tolerance",
"30",
]);
expect(result.oauth).toBeDefined();
expect(result.oauth?.enabled).toBe(true);
expect(result.oauth?.issuer).toBe("https://auth.com");
expect(result.oauth?.audience).toBe("api");
expect(result.oauth?.jwksUri).toBe("https://jwks");
expect(result.oauth?.clockTolerance).toBe(30);
});
it("should exit error if value argument looks like a flag", () => {
// Case: --mysql-user -flag
expect(() => parseArgs(["--mysql-user", "-flag"])).toThrow(
"process.exit(1)",
);
});
it("should NOT add database if required fields are missing despite some being present", () => {
// Case: Host provided, but User missing in both CLI and Env
vi.stubEnv("MYSQL_DATABASE", "env-db");
// No MYSQL_USER
const result = parseArgs(["--mysql-host", "cli-host"]);
expect(result.databases).toHaveLength(0);
vi.unstubAllEnvs();
});
it("should fallback to localhost if MYSQL_HOST is missing but others are present", () => {
vi.stubEnv("MYSQL_USER", "env-user");
vi.stubEnv("MYSQL_DATABASE", "env-db");
// No MYSQL_HOST
const result = parseArgs(["--mysql-user", "cli-user"]);
expect(result.databases[0].host).toBe("localhost");
expect(result.databases[0].username).toBe("cli-user");
vi.unstubAllEnvs();
});
it("should parse --version flag", () => {
const result = parseArgs(["--version"]);
expect(result.shouldExit).toBe(true);
expect(mockConsoleError).toHaveBeenCalledWith(
expect.stringContaining("mysql-mcp version"),
);
});
it("should print help when --help flag is used", () => {
const result = parseArgs(["--help"]);
expect(result.shouldExit).toBe(true);
expect(mockConsoleError).toHaveBeenCalledWith(
expect.stringContaining("Usage: mysql-mcp [options]"),
);
});
});
});