Skip to main content
Glama
IBM

IBM i MCP Server

Official
by IBM
yamlSqlExecutor.parameterModes.test.ts32.9 kB
/** * @fileoverview Integration tests for YamlSqlExecutor parameter binding * Tests parameter binding and SQL execution with YAML parameter definitions */ import { describe, it, expect, beforeEach, vi, Mock } from "vitest"; // Mock the SourceManager BEFORE importing anything that depends on it vi.mock("../../../src/ibmi-mcp-server/services/sourceManager.js"); // Mock the scheduling module to prevent scheduler instantiation vi.mock("../../../src/utils/scheduling/index.js", () => ({ schedulerService: vi.fn(), })); import { SQLToolFactory } from "../../../src/ibmi-mcp-server/utils/config/toolFactory.js"; import { SourceManager } from "../../../src/ibmi-mcp-server/services/sourceManager.js"; import { SqlToolParameter } from "../../../src/ibmi-mcp-server/schemas/index.js"; import { requestContextService } from "../../../src/utils/internal/requestContext.js"; describe("SQLToolFactory - Parameter Binding", () => { let mockSourceManager: { executeQuery: Mock; }; let testContext: ReturnType< typeof requestContextService.createRequestContext >; beforeEach(() => { // Create mock source manager mockSourceManager = { executeQuery: vi.fn(), }; // Initialize SQLToolFactory with mock SQLToolFactory.initialize(mockSourceManager as unknown as SourceManager); // Create test context testContext = requestContextService.createRequestContext({ operation: "TestSQLToolFactory", }); // Reset mocks vi.clearAllMocks(); // Setup default successful response mockSourceManager.executeQuery.mockResolvedValue({ success: true, data: [ { id: 1, name: "test_user", age: 30 }, { id: 2, name: "another_user", age: 25 }, ], metadata: { affectedRows: 0 }, }); }); describe("Parameter Binding (Secure Parameters)", () => { it("should execute SQL with named parameters using parameter binding", async () => { const toolName = "get_user_by_name"; const sourceName = "test_source"; const sql = "SELECT * FROM users WHERE name = :username AND age > :minAge"; const parameters = { username: "john_doe", minAge: 18, }; const parameterDefinitions: SqlToolParameter[] = [ { name: "username", type: "string", description: "User name" }, { name: "minAge", type: "integer", description: "Minimum age", min: 0 }, ]; const result = await SQLToolFactory.executeStatementWithParameters( toolName, sourceName, sql, parameters, parameterDefinitions, testContext, ); expect(result.data).toBeDefined(); expect(result.parameterMetadata?.mode).toBe("parameters"); expect(result.parameterMetadata?.parameterCount).toBe(2); // Verify the source manager was called with processed SQL and parameters expect(mockSourceManager.executeQuery).toHaveBeenCalledWith( sourceName, "SELECT * FROM users WHERE name = ? AND age > ?", ["john_doe", 18], expect.any(Object), // context ); }); it("should execute SQL with positional parameters", async () => { const sql = "SELECT * FROM users WHERE name = ? AND age > ?"; const parameters = { "0": "john_doe", "1": 18, }; const parameterDefinitions: SqlToolParameter[] = [ { name: "0", type: "string" }, { name: "1", type: "integer" }, ]; const result = await SQLToolFactory.executeStatementWithParameters( "test_tool", "test_source", sql, parameters, parameterDefinitions, testContext, ); expect(result.data).toBeDefined(); expect(mockSourceManager.executeQuery).toHaveBeenCalledWith( "test_source", "SELECT * FROM users WHERE name = ? AND age > ?", ["john_doe", 18], expect.any(Object), ); }); it("should handle array parameters for IN clauses", async () => { const sql = "SELECT * FROM users WHERE id IN (:userIds) AND status = :status"; const parameters = { userIds: [1, 2, 3], status: "active", }; const parameterDefinitions: SqlToolParameter[] = [ { name: "userIds", type: "array", itemType: "integer" }, { name: "status", type: "string", enum: ["active", "inactive"] }, ]; const result = await SQLToolFactory.executeStatementWithParameters( "get_users_by_ids", "test_source", sql, parameters, parameterDefinitions, testContext, ); expect(result.data).toBeDefined(); expect(mockSourceManager.executeQuery).toHaveBeenCalledWith( "test_source", "SELECT * FROM users WHERE id IN (?, ?, ?) AND status = ?", [1, 2, 3, "active"], expect.any(Object), ); }); it("should validate parameters and reject invalid values", async () => { const sql = "SELECT * FROM users WHERE age = :age"; const parameters = { age: "not-a-number", // Invalid integer }; const parameterDefinitions: SqlToolParameter[] = [ { name: "age", type: "integer", min: 0, max: 120 }, ]; await expect( SQLToolFactory.executeStatementWithParameters( "test_tool", "test_source", sql, parameters, parameterDefinitions, testContext, ), ).rejects.toThrow("Parameter validation failed"); // Should not call executeQuery due to validation failure expect(mockSourceManager.executeQuery).not.toHaveBeenCalled(); }); it("should handle missing required parameters", async () => { const sql = "SELECT * FROM users WHERE name = :username AND age > :minAge"; const parameters = { username: "john_doe", // minAge is missing }; const parameterDefinitions: SqlToolParameter[] = [ { name: "username", type: "string", required: true }, { name: "minAge", type: "integer", required: true }, ]; await expect( SQLToolFactory.executeStatementWithParameters( "test_tool", "test_source", sql, parameters, parameterDefinitions, testContext, ), ).rejects.toThrow("Parameter validation failed"); }); it("should use default values for optional parameters", async () => { const sql = "SELECT * FROM users WHERE name = :username AND active = :active"; const parameters = { username: "john_doe", // active is not provided, should use default }; const parameterDefinitions: SqlToolParameter[] = [ { name: "username", type: "string", required: true }, { name: "active", type: "boolean", default: true, required: false }, ]; const result = await SQLToolFactory.executeStatementWithParameters( "test_tool", "test_source", sql, parameters, parameterDefinitions, testContext, ); expect(result.data).toBeDefined(); expect(mockSourceManager.executeQuery).toHaveBeenCalledWith( "test_source", "SELECT * FROM users WHERE name = ? AND active = ?", ["john_doe", 1], // Boolean default true converted to 1 expect.any(Object), ); }); }); describe("No Parameters", () => { it("should use SQL as-is when no parameter definitions provided", async () => { const sql = "SELECT COUNT(*) as total_users FROM users"; const parameters = {}; const result = await SQLToolFactory.executeStatementWithParameters( "test_tool", "test_source", sql, parameters, [], // No parameter definitions testContext, ); expect(result.data).toBeDefined(); expect(result.parameterMetadata?.mode).toBe("none"); // Should call with original SQL and no binding parameters expect(mockSourceManager.executeQuery).toHaveBeenCalledWith( "test_source", "SELECT COUNT(*) as total_users FROM users", [], // No binding parameters expect.any(Object), ); }); }); describe("Real-world Use Cases", () => { it("should handle getUserProfile.rule with named parameters", async () => { const sql = "select * from qsys2.user_info_basic where authorization_name = :username"; const parameters = { username: "TESTUSER", }; const parameterDefinitions: SqlToolParameter[] = [ { name: "username", type: "string", description: "The user profile name to lookup", required: true, pattern: "^[A-Z0-9_]{1,10}$", // IBM i user name pattern }, ]; const result = await SQLToolFactory.executeStatementWithParameters( "getUserProfile", "ibmi-system", sql, parameters, parameterDefinitions, testContext, ); expect(result.data).toBeDefined(); expect(mockSourceManager.executeQuery).toHaveBeenCalledWith( "ibmi-system", "select * from qsys2.user_info_basic where authorization_name = ?", ["TESTUSER"], expect.any(Object), ); }); it("should handle executeCl.rule with named parameters", async () => { const sql = "call qsys2.qcmdexc(:clCommand)"; const parameters = { clCommand: "DSPLIB QSYS", }; const parameterDefinitions: SqlToolParameter[] = [ { name: "clCommand", type: "string", description: "The CL command to execute", required: true, maxLength: 1000, }, ]; const result = await SQLToolFactory.executeStatementWithParameters( "executeCl", "ibmi-system", sql, parameters, parameterDefinitions, testContext, ); expect(result.data).toBeDefined(); expect(mockSourceManager.executeQuery).toHaveBeenCalledWith( "ibmi-system", "call qsys2.qcmdexc(?)", ["DSPLIB QSYS"], expect.any(Object), ); }); it("should handle complex audit journal query with multiple parameters", async () => { const sql = ` SELECT entry_timestamp, user_name, object_name, object_type FROM qsys2.audit_journal_af WHERE entry_timestamp >= :startDate AND entry_timestamp <= :endDate AND user_name = :userName AND object_type IN (:objectTypes) ORDER BY entry_timestamp DESC LIMIT :maxResults `; const parameters = { startDate: "2024-01-01", endDate: "2024-12-31", userName: "TESTUSER", objectTypes: ["*FILE", "*PGM", "*SRVPGM"], maxResults: 100, }; const parameterDefinitions: SqlToolParameter[] = [ { name: "startDate", type: "string", required: true }, { name: "endDate", type: "string", required: true }, { name: "userName", type: "string", required: true }, { name: "objectTypes", type: "array", itemType: "string", maxLength: 10, }, { name: "maxResults", type: "integer", min: 1, max: 1000, default: 100, }, ]; const result = await SQLToolFactory.executeStatementWithParameters( "auditJournalQuery", "ibmi-system", sql, parameters, parameterDefinitions, testContext, ); expect(result.data).toBeDefined(); // Verify all parameters are properly bound const [, processedSql, bindingParams] = mockSourceManager.executeQuery.mock.calls[0]; expect(processedSql).toContain("WHERE entry_timestamp >= ?"); expect(processedSql).toContain("AND entry_timestamp <= ?"); expect(processedSql).toContain("AND user_name = ?"); expect(processedSql).toContain("AND object_type IN (?, ?, ?)"); expect(processedSql).toContain("LIMIT ?"); expect(bindingParams).toEqual([ "2024-01-01", "2024-12-31", "TESTUSER", "*FILE", "*PGM", "*SRVPGM", 100, ]); }); }); describe("Error Handling", () => { it("should handle database execution errors", async () => { mockSourceManager.executeQuery.mockRejectedValue( new Error("Database connection failed"), ); const sql = "SELECT * FROM users WHERE name = :username"; const parameters = { username: "test" }; const parameterDefinitions: SqlToolParameter[] = [ { name: "username", type: "string" }, ]; await expect( SQLToolFactory.executeStatementWithParameters( "test_tool", "test_source", sql, parameters, parameterDefinitions, testContext, ), ).rejects.toThrow("Database connection failed"); }); it("should handle SQL syntax errors", async () => { mockSourceManager.executeQuery.mockRejectedValue( new Error("SQL syntax error"), ); const sql = "SELECT * FROM users WHERE invalid syntax"; const parameters = {}; await expect( SQLToolFactory.executeStatementWithParameters( "test_tool", "test_source", sql, parameters, [], // No parameter definitions testContext, ), ).rejects.toThrow("SQL syntax error"); }); it("should handle uninitialized executor", async () => { // Create a new instance without initializing SQLToolFactory.initialize(undefined as unknown as SourceManager); await expect( SQLToolFactory.executeStatementWithParameters( "test_tool", "test_source", "SELECT 1", {}, [], // No parameter definitions testContext, ), ).rejects.toThrow("YAML SQL executor not initialized"); }); }); describe("Hybrid Parameter Mode", () => { it("should handle mix of named and positional parameters", async () => { // This would be a malformed query in real usage, but tests the hybrid mode detection const sql = "SELECT * FROM users WHERE name = :username AND age > ? AND active = :active"; const parameters = { username: "john_doe", active: true, // Missing positional parameter should be handled gracefully }; const parameterDefinitions: SqlToolParameter[] = [ { name: "username", type: "string", required: true }, { name: "active", type: "boolean", required: true }, ]; const result = await SQLToolFactory.executeStatementWithParameters( "hybrid_test", "test_source", sql, parameters, parameterDefinitions, testContext, ); expect(result.data).toBeDefined(); // Verify named parameters were converted to positional const [, processedSql, bindingParams] = mockSourceManager.executeQuery.mock.calls[0]; expect(processedSql).toContain( "WHERE name = ? AND age > ? AND active = ?", ); expect(bindingParams).toEqual(["john_doe", 1]); // Only the named params that were available }); it("should handle hybrid mode with template detection", async () => { const sql = "SELECT * FROM {{tableName}} WHERE name = :username"; const parameters = { tableName: "users", username: "john_doe", }; const parameterDefinitions: SqlToolParameter[] = [ { name: "tableName", type: "string", required: true }, { name: "username", type: "string", required: true }, ]; // Should detect template mode and reject await expect( SQLToolFactory.executeStatementWithParameters( "template_test", "test_source", sql, parameters, parameterDefinitions, testContext, ), ).rejects.toThrow("Template mode"); }); }); describe("Edge Cases and Validation", () => { it("should handle empty parameter definitions gracefully", async () => { const sql = "SELECT * FROM users"; const parameters = { someParam: "ignored" }; const result = await SQLToolFactory.executeStatementWithParameters( "no_params_test", "test_source", sql, parameters, [], // Empty parameter definitions testContext, ); expect(result.data).toBeDefined(); expect(result.parameterMetadata?.mode).toBe("none"); expect(mockSourceManager.executeQuery).toHaveBeenCalledWith( "test_source", "SELECT * FROM users", [], expect.any(Object), ); }); it("should handle special characters in parameter names", async () => { const sql = "SELECT * FROM users WHERE name = :user_name AND age = :min_age_value"; const parameters = { user_name: "test_user", min_age_value: 21, }; const parameterDefinitions: SqlToolParameter[] = [ { name: "user_name", type: "string", pattern: "^[a-zA-Z_]+$" }, { name: "min_age_value", type: "integer", min: 18, max: 100 }, ]; const result = await SQLToolFactory.executeStatementWithParameters( "special_chars_test", "test_source", sql, parameters, parameterDefinitions, testContext, ); expect(result.data).toBeDefined(); expect(mockSourceManager.executeQuery).toHaveBeenCalledWith( "test_source", "SELECT * FROM users WHERE name = ? AND age = ?", ["test_user", 21], expect.any(Object), ); }); it("should handle SQL with quotes containing parameter-like strings", async () => { const sql = "SELECT * FROM users WHERE name = :username AND description LIKE '%:not_a_param%'"; const parameters = { username: "john_doe", }; const parameterDefinitions: SqlToolParameter[] = [ { name: "username", type: "string", required: true }, ]; const result = await SQLToolFactory.executeStatementWithParameters( "quoted_strings_test", "test_source", sql, parameters, parameterDefinitions, testContext, ); expect(result.data).toBeDefined(); // Should only replace actual parameters, not quoted strings const [, processedSql, bindingParams] = mockSourceManager.executeQuery.mock.calls[0]; expect(processedSql).toBe( "SELECT * FROM users WHERE name = ? AND description LIKE '%:not_a_param%'", ); expect(bindingParams).toEqual(["john_doe"]); }); it("should handle duplicate parameter names", async () => { const sql = "SELECT * FROM users WHERE name = :username OR email = :username"; const parameters = { username: "john_doe", }; const parameterDefinitions: SqlToolParameter[] = [ { name: "username", type: "string", required: true }, ]; const result = await SQLToolFactory.executeStatementWithParameters( "duplicate_params_test", "test_source", sql, parameters, parameterDefinitions, testContext, ); expect(result.data).toBeDefined(); // Should replace both instances with the same value const [, processedSql, bindingParams] = mockSourceManager.executeQuery.mock.calls[0]; expect(processedSql).toBe( "SELECT * FROM users WHERE name = ? OR email = ?", ); expect(bindingParams).toEqual(["john_doe", "john_doe"]); }); it("should handle type conversion edge cases", async () => { const sql = "SELECT * FROM users WHERE active = :active AND count = :count AND ratio = :ratio"; const parameters = { active: "true", // String to boolean count: "42", // String to integer ratio: "3.14", // String to float }; const parameterDefinitions: SqlToolParameter[] = [ { name: "active", type: "boolean" }, { name: "count", type: "integer", min: 1 }, { name: "ratio", type: "float", min: 0.0 }, ]; const result = await SQLToolFactory.executeStatementWithParameters( "type_conversion_test", "test_source", sql, parameters, parameterDefinitions, testContext, ); expect(result.data).toBeDefined(); expect(mockSourceManager.executeQuery).toHaveBeenCalledWith( "test_source", "SELECT * FROM users WHERE active = ? AND count = ? AND ratio = ?", [1, 42, 3.14], // Converted values expect.any(Object), ); }); it("should handle complex nested array with enum validation", async () => { const sql = "SELECT * FROM users WHERE status IN (:statuses) AND roles IN (:roles)"; const parameters = { statuses: ["active", "pending"], roles: ["admin", "user", "guest"], }; const parameterDefinitions: SqlToolParameter[] = [ { name: "statuses", type: "array", itemType: "string", enum: ["active", "inactive", "pending", "suspended"], }, { name: "roles", type: "array", itemType: "string", enum: ["admin", "user", "guest", "moderator"], maxLength: 5, }, ]; const result = await SQLToolFactory.executeStatementWithParameters( "complex_array_test", "test_source", sql, parameters, parameterDefinitions, testContext, ); expect(result.data).toBeDefined(); expect(mockSourceManager.executeQuery).toHaveBeenCalledWith( "test_source", "SELECT * FROM users WHERE status IN (?, ?) AND roles IN (?, ?, ?)", ["active", "pending", "admin", "user", "guest"], expect.any(Object), ); }); it("should handle invalid enum values in arrays", async () => { const sql = "SELECT * FROM users WHERE status IN (:statuses)"; const parameters = { statuses: ["active", "invalid_status"], }; const parameterDefinitions: SqlToolParameter[] = [ { name: "statuses", type: "array", itemType: "string", enum: ["active", "inactive", "pending"], }, ]; await expect( SQLToolFactory.executeStatementWithParameters( "invalid_enum_test", "test_source", sql, parameters, parameterDefinitions, testContext, ), ).rejects.toThrow("not one of allowed values"); }); it("should handle parameter with complex default values", async () => { const sql = "SELECT * FROM users WHERE created_after = :startDate AND status = :status AND limit_count = :limit"; const parameters = { // Only provide one parameter, others should use defaults startDate: "2024-01-01", }; const parameterDefinitions: SqlToolParameter[] = [ { name: "startDate", type: "string", required: true }, { name: "status", type: "string", default: "active", enum: ["active", "inactive"], }, { name: "limit", type: "integer", default: 100, min: 1, max: 1000 }, ]; const result = await SQLToolFactory.executeStatementWithParameters( "complex_defaults_test", "test_source", sql, parameters, parameterDefinitions, testContext, ); expect(result.data).toBeDefined(); expect(mockSourceManager.executeQuery).toHaveBeenCalledWith( "test_source", "SELECT * FROM users WHERE created_after = ? AND status = ? AND limit_count = ?", ["2024-01-01", "active", 100], // Default values applied expect.any(Object), ); }); it("should handle malformed SQL syntax validation", async () => { const sql = "SELECT * FROM users WHERE name = 'unmatched quote AND id = :id"; const parameters = { id: 123 }; const parameterDefinitions: SqlToolParameter[] = [ { name: "id", type: "integer" }, ]; await expect( SQLToolFactory.executeStatementWithParameters( "malformed_sql_test", "test_source", sql, parameters, parameterDefinitions, testContext, ), ).rejects.toThrow("Unmatched single quotes"); }); it("should handle malformed parameter syntax", async () => { const sql = "SELECT * FROM users WHERE id = :123invalid"; // Parameter name can't start with number const parameters = { "123invalid": 123 }; const parameterDefinitions: SqlToolParameter[] = [ { name: "123invalid", type: "integer" }, ]; await expect( SQLToolFactory.executeStatementWithParameters( "invalid_param_syntax_test", "test_source", sql, parameters, parameterDefinitions, testContext, ), ).rejects.toThrow("Invalid named parameter syntax"); }); it("should handle parameters with null and undefined edge cases", async () => { const sql = "SELECT * FROM users WHERE name = :name AND age = :age"; const parameters = { name: null, age: undefined, }; const parameterDefinitions: SqlToolParameter[] = [ { name: "name", type: "string", required: false, default: "unknown" }, { name: "age", type: "integer", required: false, default: 0 }, ]; const result = await SQLToolFactory.executeStatementWithParameters( "null_undefined_test", "test_source", sql, parameters, parameterDefinitions, testContext, ); expect(result.data).toBeDefined(); expect(mockSourceManager.executeQuery).toHaveBeenCalledWith( "test_source", "SELECT * FROM users WHERE name = ? AND age = ?", ["unknown", 0], // Default values used for null/undefined expect.any(Object), ); }); it("should handle extremely long parameter values", async () => { const longString = "x".repeat(5000); const sql = "SELECT * FROM users WHERE description = :description"; const parameters = { description: longString, }; const parameterDefinitions: SqlToolParameter[] = [ { name: "description", type: "string", maxLength: 10000 }, ]; const result = await SQLToolFactory.executeStatementWithParameters( "long_string_test", "test_source", sql, parameters, parameterDefinitions, testContext, ); expect(result.data).toBeDefined(); expect(mockSourceManager.executeQuery).toHaveBeenCalledWith( "test_source", "SELECT * FROM users WHERE description = ?", [longString], expect.any(Object), ); }); it("should reject parameter values exceeding length limits", async () => { const longString = "x".repeat(1001); const sql = "SELECT * FROM users WHERE description = :description"; const parameters = { description: longString, }; const parameterDefinitions: SqlToolParameter[] = [ { name: "description", type: "string", maxLength: 1000 }, ]; await expect( SQLToolFactory.executeStatementWithParameters( "length_limit_test", "test_source", sql, parameters, parameterDefinitions, testContext, ), ).rejects.toThrow("must be at most 1000 characters long"); }); it("should handle complex boolean conversions", async () => { const sql = "SELECT * FROM settings WHERE flag1 = :flag1 AND flag2 = :flag2 AND flag3 = :flag3 AND flag4 = :flag4"; const parameters = { flag1: "yes", flag2: "off", flag3: 1, flag4: 0, }; const parameterDefinitions: SqlToolParameter[] = [ { name: "flag1", type: "boolean" }, { name: "flag2", type: "boolean" }, { name: "flag3", type: "boolean" }, { name: "flag4", type: "boolean" }, ]; const result = await SQLToolFactory.executeStatementWithParameters( "boolean_conversion_test", "test_source", sql, parameters, parameterDefinitions, testContext, ); expect(result.data).toBeDefined(); expect(mockSourceManager.executeQuery).toHaveBeenCalledWith( "test_source", "SELECT * FROM settings WHERE flag1 = ? AND flag2 = ? AND flag3 = ? AND flag4 = ?", [1, 0, 1, 0], // All converted to DB2-compatible integers expect.any(Object), ); }); it("should handle regex pattern validation edge cases", async () => { const sql = "SELECT * FROM users WHERE email = :email AND phone = :phone"; const parameters = { email: "test@example.com", phone: "+1-555-0123", }; const parameterDefinitions: SqlToolParameter[] = [ { name: "email", type: "string", pattern: "^[\\w.-]+@[\\w.-]+\\.[a-zA-Z]{2,}$", }, { name: "phone", type: "string", pattern: "^\\+?[1-9]\\d{1,14}$|^\\+?[1-9]-\\d{3}-\\d{4}$", }, ]; const result = await SQLToolFactory.executeStatementWithParameters( "regex_validation_test", "test_source", sql, parameters, parameterDefinitions, testContext, ); expect(result.data).toBeDefined(); expect(mockSourceManager.executeQuery).toHaveBeenCalledWith( "test_source", "SELECT * FROM users WHERE email = ? AND phone = ?", ["test@example.com", "+1-555-0123"], expect.any(Object), ); }); it("should handle invalid regex patterns gracefully", async () => { const sql = "SELECT * FROM users WHERE name = :name"; const parameters = { name: "test_user", }; const parameterDefinitions: SqlToolParameter[] = [ { name: "name", type: "string", pattern: "[invalid regex(", // Malformed regex }, ]; const result = await SQLToolFactory.executeStatementWithParameters( "invalid_regex_test", "test_source", sql, parameters, parameterDefinitions, testContext, ); // Should succeed but log a warning about invalid pattern expect(result.data).toBeDefined(); expect(mockSourceManager.executeQuery).toHaveBeenCalledWith( "test_source", "SELECT * FROM users WHERE name = ?", ["test_user"], expect.any(Object), ); }); }); describe("Performance and Optimization", () => { it("should include execution metrics in results", async () => { // Simulate slower execution mockSourceManager.executeQuery.mockImplementation(async () => { await new Promise((resolve) => setTimeout(resolve, 50)); return { success: true, data: [{ id: 1, name: "test" }], metadata: { affectedRows: 1 }, }; }); const result = await SQLToolFactory.executeStatementWithParameters( "test_tool", "test_source", "SELECT * FROM users WHERE id = :id", { id: 1 }, [{ name: "id", type: "integer" }], testContext, ); expect(result.data).toBeDefined(); expect(result.executionTime).toBeGreaterThan(0); expect(result.rowCount).toBe(1); expect(result.parameterMetadata?.mode).toBe("parameters"); expect(result.parameterMetadata?.parameterCount).toBe(1); }); it("should handle large parameter arrays efficiently", async () => { const largeArray = Array.from({ length: 1000 }, (_, i) => i + 1); const result = await SQLToolFactory.executeStatementWithParameters( "test_tool", "test_source", "SELECT * FROM users WHERE id IN (:ids)", { ids: largeArray }, [{ name: "ids", type: "array", itemType: "integer", maxLength: 1000 }], testContext, ); expect(result.data).toBeDefined(); // Array parameters are expanded to individual placeholders const expectedPlaceholders = largeArray.map(() => "?").join(", "); expect(mockSourceManager.executeQuery).toHaveBeenCalledWith( "test_source", `SELECT * FROM users WHERE id IN (${expectedPlaceholders})`, largeArray, expect.any(Object), ); }); }); });

Latest Blog Posts

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/IBM/ibmi-mcp-server'

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