Skip to main content
Glama

USQL MCP Server

by jvm
tools.test.ts18.4 kB
/** * Unit tests for all tool handlers */ import { handleExecuteQuery } from "../../src/tools/execute-query.js"; import { handleListDatabases } from "../../src/tools/list-databases.js"; import { handleListTables } from "../../src/tools/list-tables.js"; import { handleDescribeTable } from "../../src/tools/describe-table.js"; import { handleExecuteScript } from "../../src/tools/execute-script.js"; import * as processExecutor from "../../src/usql/process-executor.js"; import * as config from "../../src/usql/config.js"; import * as connection from "../../src/usql/connection.js"; // Mock dependencies jest.mock("../../src/usql/process-executor.js"); jest.mock("../../src/usql/config.js"); jest.mock("../../src/usql/connection.js"); describe("Tool Handlers", () => { const mockExecuteUsqlQuery = processExecutor.executeUsqlQuery as jest.MockedFunction< typeof processExecutor.executeUsqlQuery >; const mockResolveConnectionStringOrDefault = config.resolveConnectionStringOrDefault as jest.MockedFunction< typeof config.resolveConnectionStringOrDefault >; const mockGetQueryTimeout = config.getQueryTimeout as jest.MockedFunction< typeof config.getQueryTimeout >; const mockValidateConnectionString = connection.validateConnectionString as jest.MockedFunction< typeof connection.validateConnectionString >; beforeEach(() => { jest.clearAllMocks(); mockResolveConnectionStringOrDefault.mockReturnValue("postgres://localhost/testdb"); mockGetQueryTimeout.mockReturnValue(undefined); mockValidateConnectionString.mockReturnValue(true); }); describe("handleExecuteQuery", () => { it("executes a valid query successfully", async () => { mockExecuteUsqlQuery.mockResolvedValue({ stdout: '{"rows": [{"id": 1}]}', stderr: "", exitCode: 0, }); const result = await handleExecuteQuery({ query: "SELECT * FROM users", connection_string: "postgres://localhost/db", }); expect(result.format).toBe("json"); expect(result.content).toBe('{"rows": [{"id": 1}]}'); expect(mockExecuteUsqlQuery).toHaveBeenCalledWith( "postgres://localhost/testdb", "SELECT * FROM users", { timeout: undefined, format: "json" } ); }); it("respects output_format parameter", async () => { mockExecuteUsqlQuery.mockResolvedValue({ stdout: "id,name\n1,John", stderr: "", exitCode: 0, }); const result = await handleExecuteQuery({ query: "SELECT * FROM users", output_format: "csv", }); expect(result.format).toBe("csv"); expect(result.content).toBe("id,name\n1,John"); expect(mockExecuteUsqlQuery).toHaveBeenCalledWith( expect.any(String), expect.any(String), expect.objectContaining({ format: "csv" }) ); }); it("throws error when query is missing", async () => { await expect( handleExecuteQuery({ query: "" } as any) ).rejects.toMatchObject({ error: "InvalidInput", message: expect.stringContaining("query is required"), }); }); it("throws error when query is not a string", async () => { await expect( handleExecuteQuery({ query: 123 } as any) ).rejects.toMatchObject({ error: "InvalidInput", message: expect.stringContaining("query is required"), }); }); it("throws error when connection string is invalid", async () => { mockValidateConnectionString.mockReturnValue(false); await expect( handleExecuteQuery({ query: "SELECT 1", connection_string: "invalid", }) ).rejects.toMatchObject({ error: expect.any(String), message: expect.stringContaining("Invalid connection string"), }); }); it("throws error when connection resolution fails", async () => { mockResolveConnectionStringOrDefault.mockImplementation(() => { throw new Error("No connection configured"); }); await expect( handleExecuteQuery({ query: "SELECT 1" }) ).rejects.toMatchObject({ error: "InvalidConnection", message: expect.stringContaining("Failed to resolve connection"), }); }); it("handles query execution errors", async () => { mockExecuteUsqlQuery.mockResolvedValue({ stdout: "", stderr: "ERROR: syntax error at or near SELECT", exitCode: 1, }); await expect( handleExecuteQuery({ query: "SELCT * FROM users" }) ).rejects.toMatchObject({ error: "QueryExecutionError", message: expect.stringContaining("ERROR: syntax error"), }); }); it("respects timeout_ms parameter", async () => { mockExecuteUsqlQuery.mockResolvedValue({ stdout: "{}", stderr: "", exitCode: 0, }); await handleExecuteQuery({ query: "SELECT 1", timeout_ms: 5000, }); expect(mockExecuteUsqlQuery).toHaveBeenCalledWith( expect.any(String), expect.any(String), expect.objectContaining({ timeout: 5000 }) ); }); it("uses unlimited timeout when timeout_ms is null", async () => { mockExecuteUsqlQuery.mockResolvedValue({ stdout: "{}", stderr: "", exitCode: 0, }); await handleExecuteQuery({ query: "SELECT 1", timeout_ms: null, }); expect(mockExecuteUsqlQuery).toHaveBeenCalledWith( expect.any(String), expect.any(String), expect.objectContaining({ timeout: undefined }) ); }); it("falls back to default timeout when timeout_ms not provided", async () => { mockGetQueryTimeout.mockReturnValue(30000); mockExecuteUsqlQuery.mockResolvedValue({ stdout: "{}", stderr: "", exitCode: 0, }); await handleExecuteQuery({ query: "SELECT 1" }); expect(mockExecuteUsqlQuery).toHaveBeenCalledWith( expect.any(String), expect.any(String), expect.objectContaining({ timeout: 30000 }) ); }); it("sanitizes connection string in error details", async () => { mockExecuteUsqlQuery.mockRejectedValue(new Error("Connection failed")); await expect( handleExecuteQuery({ query: "SELECT 1", connection_string: "postgres://user:password@host/db", }) ).rejects.toMatchObject({ details: expect.objectContaining({ connectionString: expect.stringContaining("***"), }), }); }); }); describe("handleListDatabases", () => { it("lists databases successfully", async () => { mockExecuteUsqlQuery.mockResolvedValue({ stdout: '{"databases": ["db1", "db2"]}', stderr: "", exitCode: 0, }); const result = await handleListDatabases({}); expect(result.format).toBe("json"); expect(result.content).toBe('{"databases": ["db1", "db2"]}'); expect(mockExecuteUsqlQuery).toHaveBeenCalledWith( "postgres://localhost/testdb", "\\l", expect.objectContaining({ format: "json" }) ); }); it("respects output_format parameter", async () => { mockExecuteUsqlQuery.mockResolvedValue({ stdout: "name,owner\ndb1,admin\ndb2,user", stderr: "", exitCode: 0, }); const result = await handleListDatabases({ output_format: "csv" }); expect(result.format).toBe("csv"); expect(mockExecuteUsqlQuery).toHaveBeenCalledWith( expect.any(String), "\\l", expect.objectContaining({ format: "csv" }) ); }); it("throws error when connection string is invalid", async () => { mockValidateConnectionString.mockReturnValue(false); await expect( handleListDatabases({ connection_string: "invalid" }) ).rejects.toMatchObject({ error: expect.any(String), message: expect.stringContaining("Invalid connection string"), }); }); it("handles list databases errors", async () => { mockExecuteUsqlQuery.mockResolvedValue({ stdout: "", stderr: "ERROR: permission denied", exitCode: 1, }); await expect(handleListDatabases({})).rejects.toMatchObject({ error: "ListDatabasesError", message: expect.stringContaining("permission denied"), }); }); it("respects timeout_ms parameter", async () => { mockExecuteUsqlQuery.mockResolvedValue({ stdout: "{}", stderr: "", exitCode: 0, }); await handleListDatabases({ timeout_ms: 10000 }); expect(mockExecuteUsqlQuery).toHaveBeenCalledWith( expect.any(String), expect.any(String), expect.objectContaining({ timeout: 10000 }) ); }); }); describe("handleListTables", () => { it("lists tables successfully", async () => { mockExecuteUsqlQuery.mockResolvedValue({ stdout: '{"tables": ["users", "orders"]}', stderr: "", exitCode: 0, }); const result = await handleListTables({}); expect(result.format).toBe("json"); expect(result.content).toBe('{"tables": ["users", "orders"]}'); expect(mockExecuteUsqlQuery).toHaveBeenCalledWith( "postgres://localhost/testdb", "\\dt", expect.objectContaining({ format: "json" }) ); }); it("respects output_format parameter", async () => { mockExecuteUsqlQuery.mockResolvedValue({ stdout: "table_name\nusers\norders", stderr: "", exitCode: 0, }); const result = await handleListTables({ output_format: "csv" }); expect(result.format).toBe("csv"); }); it("handles list tables errors", async () => { mockExecuteUsqlQuery.mockResolvedValue({ stdout: "", stderr: "ERROR: database not found", exitCode: 1, }); await expect(handleListTables({})).rejects.toMatchObject({ error: "ListTablesError", message: expect.stringContaining("database not found"), }); }); it("includes database parameter in error context", async () => { mockExecuteUsqlQuery.mockRejectedValue(new Error("Connection failed")); await expect( handleListTables({ database: "mydb" }) ).rejects.toMatchObject({ details: expect.objectContaining({ database: "mydb", }), }); }); }); describe("handleDescribeTable", () => { it("describes a table successfully", async () => { mockExecuteUsqlQuery.mockResolvedValue({ stdout: '{"columns": [{"name": "id", "type": "integer"}]}', stderr: "", exitCode: 0, }); const result = await handleDescribeTable({ table: "users" }); expect(result.format).toBe("json"); expect(result.content).toContain("columns"); expect(mockExecuteUsqlQuery).toHaveBeenCalledWith( "postgres://localhost/testdb", "\\d users", expect.objectContaining({ format: "json" }) ); }); it("throws error when table parameter is missing", async () => { await expect( handleDescribeTable({ table: "" } as any) ).rejects.toMatchObject({ error: "InvalidInput", message: expect.stringContaining("table is required"), }); }); it("throws error when table parameter is not a string", async () => { await expect( handleDescribeTable({ table: 123 } as any) ).rejects.toMatchObject({ error: "InvalidInput", message: expect.stringContaining("table is required"), }); }); it("throws TableNotFound when output is empty with exitCode 0", async () => { mockExecuteUsqlQuery.mockResolvedValue({ stdout: "", stderr: "", exitCode: 0, }); await expect( handleDescribeTable({ table: "nonexistent" }) ).rejects.toMatchObject({ error: "TableNotFound", message: expect.stringContaining("nonexistent"), }); }); it("throws TableNotFound when output is whitespace only", async () => { mockExecuteUsqlQuery.mockResolvedValue({ stdout: " \n\n ", stderr: "", exitCode: 0, }); await expect( handleDescribeTable({ table: "missing" }) ).rejects.toMatchObject({ error: "TableNotFound", message: expect.stringContaining("missing"), }); }); it("handles describe table errors with stderr", async () => { mockExecuteUsqlQuery.mockResolvedValue({ stdout: "", stderr: "ERROR: relation does not exist", exitCode: 1, }); await expect( handleDescribeTable({ table: "users" }) ).rejects.toMatchObject({ error: "DescribeTableError", message: expect.stringContaining("relation does not exist"), }); }); it("includes table and database in error context", async () => { mockExecuteUsqlQuery.mockRejectedValue(new Error("Connection failed")); await expect( handleDescribeTable({ table: "users", database: "mydb" }) ).rejects.toMatchObject({ details: expect.objectContaining({ table: "users", database: "mydb", }), }); }); it("respects output_format parameter", async () => { mockExecuteUsqlQuery.mockResolvedValue({ stdout: "column,type\nid,integer", stderr: "", exitCode: 0, }); const result = await handleDescribeTable({ table: "users", output_format: "csv", }); expect(result.format).toBe("csv"); }); }); describe("handleExecuteScript", () => { it("executes a script successfully", async () => { mockExecuteUsqlQuery.mockResolvedValue({ stdout: '{"result": "success"}', stderr: "", exitCode: 0, }); const result = await handleExecuteScript({ script: "CREATE TABLE test (id INT); INSERT INTO test VALUES (1);", }); expect(result.format).toBe("json"); expect(result.content).toBe('{"result": "success"}'); expect(mockExecuteUsqlQuery).toHaveBeenCalledWith( "postgres://localhost/testdb", "CREATE TABLE test (id INT); INSERT INTO test VALUES (1);", expect.objectContaining({ format: "json" }) ); }); it("throws error when script is missing", async () => { await expect( handleExecuteScript({ script: "" } as any) ).rejects.toMatchObject({ error: "InvalidInput", message: expect.stringContaining("script is required"), }); }); it("throws error when script is not a string", async () => { await expect( handleExecuteScript({ script: null } as any) ).rejects.toMatchObject({ error: "InvalidInput", message: expect.stringContaining("script is required"), }); }); it("throws error when script is empty after trimming", async () => { await expect( handleExecuteScript({ script: " \n\n " }) ).rejects.toMatchObject({ error: "InvalidInput", message: expect.stringContaining("script cannot be empty"), }); }); it("logs warning for DROP TABLE statements", async () => { const consoleWarnSpy = jest.spyOn(console, "error").mockImplementation(); mockExecuteUsqlQuery.mockResolvedValue({ stdout: "{}", stderr: "", exitCode: 0, }); await handleExecuteScript({ script: "DROP TABLE users; CREATE TABLE users (id INT);", }); // Script should still execute (warning only) expect(mockExecuteUsqlQuery).toHaveBeenCalled(); consoleWarnSpy.mockRestore(); }); it("logs warning for DROP DATABASE statements", async () => { const consoleWarnSpy = jest.spyOn(console, "error").mockImplementation(); mockExecuteUsqlQuery.mockResolvedValue({ stdout: "{}", stderr: "", exitCode: 0, }); await handleExecuteScript({ script: "DROP DATABASE old_db; CREATE DATABASE new_db;", }); expect(mockExecuteUsqlQuery).toHaveBeenCalled(); consoleWarnSpy.mockRestore(); }); it("handles script execution errors", async () => { mockExecuteUsqlQuery.mockResolvedValue({ stdout: "", stderr: "ERROR: syntax error in statement 2", exitCode: 1, }); await expect( handleExecuteScript({ script: "SELECT 1; SELCT 2;" }) ).rejects.toMatchObject({ error: "ScriptExecutionError", message: expect.stringContaining("syntax error"), }); }); it("respects output_format parameter", async () => { mockExecuteUsqlQuery.mockResolvedValue({ stdout: "result\nsuccess", stderr: "", exitCode: 0, }); const result = await handleExecuteScript({ script: "SELECT 1", output_format: "csv", }); expect(result.format).toBe("csv"); }); it("respects timeout_ms parameter", async () => { mockExecuteUsqlQuery.mockResolvedValue({ stdout: "{}", stderr: "", exitCode: 0, }); await handleExecuteScript({ script: "SELECT 1", timeout_ms: 15000, }); expect(mockExecuteUsqlQuery).toHaveBeenCalledWith( expect.any(String), expect.any(String), expect.objectContaining({ timeout: 15000 }) ); }); it("includes script length in error context", async () => { mockExecuteUsqlQuery.mockRejectedValue(new Error("Execution failed")); const longScript = "SELECT * FROM users;".repeat(100); await expect( handleExecuteScript({ script: longScript }) ).rejects.toMatchObject({ details: expect.objectContaining({ scriptLength: longScript.length, }), }); }); it("trims whitespace from script before execution", async () => { mockExecuteUsqlQuery.mockResolvedValue({ stdout: "{}", stderr: "", exitCode: 0, }); await handleExecuteScript({ script: "\n\n SELECT 1; \n\n", }); expect(mockExecuteUsqlQuery).toHaveBeenCalledWith( expect.any(String), "SELECT 1;", expect.any(Object) ); }); }); });

MCP directory API

We provide all the information about MCP servers via our MCP API.

curl -X GET 'https://glama.ai/api/mcp/v1/servers/jvm/usql-mcp'

If you have feedback or need assistance with the MCP directory API, please join our Discord server