From 3eba90688346bd29c81da1fff30a46bb16848d2a Mon Sep 17 00:00:00 2001 From: Himanshu Singh Date: Tue, 14 Oct 2025 12:23:01 +0200 Subject: [PATCH 1/6] chore: change interface of describeWithMongoDB The number of parameters were increasing as I wanted to add elicitInput as another argument. This commit compacts the rest of the config parameters in an object and modifies the usage to call the method correctly. Additionally, in places where we were just doing the defaults, this commit modifies those calls to not pass anything and rely on default. --- .../common/connectionManager.oidc.test.ts | 29 +++-- tests/integration/indexCheck.test.ts | 35 +++--- .../resources/exportedData.test.ts | 7 +- tests/integration/server.test.ts | 16 +-- .../tools/mongodb/connect/connect.test.ts | 102 +++++++++--------- .../tools/mongodb/mongodbHelpers.ts | 16 ++- .../tools/mongodb/read/aggregate.test.ts | 22 +++- .../tools/mongodb/read/export.test.ts | 7 +- .../tools/mongodb/read/find.test.ts | 22 +++- .../mongodb/search/listSearchIndexes.test.ts | 9 +- 10 files changed, 165 insertions(+), 100 deletions(-) diff --git a/tests/integration/common/connectionManager.oidc.test.ts b/tests/integration/common/connectionManager.oidc.test.ts index 9f30cf32e..670af9ba1 100644 --- a/tests/integration/common/connectionManager.oidc.test.ts +++ b/tests/integration/common/connectionManager.oidc.test.ts @@ -3,7 +3,12 @@ import { describe, beforeEach, afterAll, it, expect, vi } from "vitest"; import semver from "semver"; import process from "process"; import type { MongoDBIntegrationTestCase } from "../tools/mongodb/mongodbHelpers.js"; -import { describeWithMongoDB, isCommunityServer, getServerVersion } from "../tools/mongodb/mongodbHelpers.js"; +import { + describeWithMongoDB, + isCommunityServer, + getServerVersion, + defaultTestSuiteConfig, +} from "../tools/mongodb/mongodbHelpers.js"; import { defaultTestConfig, responseAsText, timeout, waitUntil } from "../helpers.js"; import type { ConnectionStateConnected, ConnectionStateConnecting } from "../../../src/common/connectionManager.js"; import type { UserConfig } from "../../../src/common/config.js"; @@ -137,14 +142,20 @@ describe.skipIf(process.platform !== "linux")("ConnectionManager OIDC Tests", as addCb?.(oidcIt); }, - () => oidcConfig, - () => ({ - ...setupDriverConfig({ - config: oidcConfig, - defaults: {}, - }), - }), - { runner: true, downloadOptions: { enterprise: true, version: mongodbVersion }, serverArgs } + { + ...defaultTestSuiteConfig, + getUserConfig: () => oidcConfig, + getDriverOptions: () => + setupDriverConfig({ + config: oidcConfig, + defaults: {}, + }), + downloadOptions: { + runner: true, + downloadOptions: { enterprise: true, version: mongodbVersion }, + serverArgs, + }, + } ); } diff --git a/tests/integration/indexCheck.test.ts b/tests/integration/indexCheck.test.ts index 438cd86fe..d2c7c106b 100644 --- a/tests/integration/indexCheck.test.ts +++ b/tests/integration/indexCheck.test.ts @@ -1,5 +1,5 @@ import { defaultTestConfig, getResponseContent } from "./helpers.js"; -import { describeWithMongoDB } from "./tools/mongodb/mongodbHelpers.js"; +import { defaultTestSuiteConfig, describeWithMongoDB } from "./tools/mongodb/mongodbHelpers.js"; import { beforeEach, describe, expect, it } from "vitest"; describe("IndexCheck integration tests", () => { @@ -313,10 +313,13 @@ describe("IndexCheck integration tests", () => { }); }); }, - () => ({ - ...defaultTestConfig, - indexCheck: true, // Enable indexCheck - }) + { + ...defaultTestSuiteConfig, + getUserConfig: () => ({ + ...defaultTestConfig, + indexCheck: true, // Enable indexCheck + }), + } ); }); @@ -424,10 +427,13 @@ describe("IndexCheck integration tests", () => { expect(content).not.toContain("Index check failed"); }); }, - () => ({ - ...defaultTestConfig, - indexCheck: false, // Disable indexCheck - }) + { + ...defaultTestSuiteConfig, + getUserConfig: () => ({ + ...defaultTestConfig, + indexCheck: false, // Disable indexCheck + }), + } ); }); @@ -456,10 +462,13 @@ describe("IndexCheck integration tests", () => { expect(response.isError).toBeFalsy(); }); }, - () => ({ - ...defaultTestConfig, - // indexCheck not specified, should default to false - }) + { + ...defaultTestSuiteConfig, + getUserConfig: () => ({ + ...defaultTestConfig, + // indexCheck not specified, should default to false + }), + } ); }); }); diff --git a/tests/integration/resources/exportedData.test.ts b/tests/integration/resources/exportedData.test.ts index 6e361bf03..24f014450 100644 --- a/tests/integration/resources/exportedData.test.ts +++ b/tests/integration/resources/exportedData.test.ts @@ -4,7 +4,7 @@ import { EJSON, Long, ObjectId } from "bson"; import { describe, expect, it, beforeEach, afterAll } from "vitest"; import type { CallToolResult } from "@modelcontextprotocol/sdk/types.js"; import { defaultTestConfig, getDataFromUntrustedContent, resourceChangedNotification, timeout } from "../helpers.js"; -import { describeWithMongoDB } from "../tools/mongodb/mongodbHelpers.js"; +import { defaultTestSuiteConfig, describeWithMongoDB } from "../tools/mongodb/mongodbHelpers.js"; import { contentWithResourceURILink } from "../tools/mongodb/read/export.test.js"; import type { UserConfig } from "../../../src/lib.js"; @@ -173,5 +173,8 @@ describeWithMongoDB( }); }); }, - () => userConfig + { + ...defaultTestSuiteConfig, + getUserConfig: () => userConfig, + } ); diff --git a/tests/integration/server.test.ts b/tests/integration/server.test.ts index ef98075a7..539305f0d 100644 --- a/tests/integration/server.test.ts +++ b/tests/integration/server.test.ts @@ -1,5 +1,5 @@ import { defaultDriverOptions, defaultTestConfig, expectDefined, setupIntegrationTest } from "./helpers.js"; -import { describeWithMongoDB } from "./tools/mongodb/mongodbHelpers.js"; +import { defaultTestSuiteConfig, describeWithMongoDB } from "./tools/mongodb/mongodbHelpers.js"; import { describe, expect, it } from "vitest"; describe("Server integration test", () => { @@ -15,12 +15,14 @@ describe("Server integration test", () => { expect(atlasTools.length).toBeLessThanOrEqual(0); }); }, - () => ({ - ...defaultTestConfig, - apiClientId: undefined, - apiClientSecret: undefined, - }), - () => defaultDriverOptions + { + ...defaultTestSuiteConfig, + getUserConfig: () => ({ + ...defaultTestConfig, + apiClientId: undefined, + apiClientSecret: undefined, + }), + } ); describe("with atlas", () => { diff --git a/tests/integration/tools/mongodb/connect/connect.test.ts b/tests/integration/tools/mongodb/connect/connect.test.ts index 46526fe5b..4dbd2cc5c 100644 --- a/tests/integration/tools/mongodb/connect/connect.test.ts +++ b/tests/integration/tools/mongodb/connect/connect.test.ts @@ -1,4 +1,4 @@ -import { describeWithMongoDB } from "../mongodbHelpers.js"; +import { defaultTestSuiteConfig, describeWithMongoDB } from "../mongodbHelpers.js"; import { defaultDriverOptions, getResponseContent, @@ -6,7 +6,6 @@ import { validateThrowsForInvalidArguments, validateToolMetadata, } from "../../../helpers.js"; -import { config } from "../../../../../src/common/config.js"; import { defaultTestConfig, setupIntegrationTest } from "../../../helpers.js"; import { beforeEach, describe, expect, it } from "vitest"; @@ -82,66 +81,65 @@ describeWithMongoDB( }); }); }, - (mdbIntegration) => ({ - ...config, - connectionString: mdbIntegration.connectionString(), - }) + { + ...defaultTestSuiteConfig, + getUserConfig: (mdbIntegration) => ({ + ...defaultTestConfig, + connectionString: mdbIntegration.connectionString(), + }), + } ); -describeWithMongoDB( - "Connect tool", - (integration) => { - validateToolMetadata( - integration, - "connect", - "Connect to a MongoDB instance. The config resource captures if the server is already connected to a MongoDB cluster. If the user has configured a connection string or has previously called the connect tool, a connection is already established and there's no need to call this tool unless the user has explicitly requested to switch to a new MongoDB cluster.", - [ - { - name: "connectionString", - description: "MongoDB connection string (in the mongodb:// or mongodb+srv:// format)", - type: "string", - required: true, - }, - ] - ); +describeWithMongoDB("Connect tool", (integration) => { + validateToolMetadata( + integration, + "connect", + "Connect to a MongoDB instance. The config resource captures if the server is already connected to a MongoDB cluster. If the user has configured a connection string or has previously called the connect tool, a connection is already established and there's no need to call this tool unless the user has explicitly requested to switch to a new MongoDB cluster.", + [ + { + name: "connectionString", + description: "MongoDB connection string (in the mongodb:// or mongodb+srv:// format)", + type: "string", + required: true, + }, + ] + ); - validateThrowsForInvalidArguments(integration, "connect", [{}, { connectionString: 123 }]); + validateThrowsForInvalidArguments(integration, "connect", [{}, { connectionString: 123 }]); - it("doesn't have the switch-connection tool registered", async () => { - const { tools } = await integration.mcpClient().listTools(); - const tool = tools.find((tool) => tool.name === "switch-connection"); - expect(tool).toBeUndefined(); - }); + it("doesn't have the switch-connection tool registered", async () => { + const { tools } = await integration.mcpClient().listTools(); + const tool = tools.find((tool) => tool.name === "switch-connection"); + expect(tool).toBeUndefined(); + }); - describe("with connection string", () => { - it("connects to the database", async () => { - const response = await integration.mcpClient().callTool({ - name: "connect", - arguments: { - connectionString: integration.connectionString(), - }, - }); - const content = getResponseContent(response.content); - expect(content).toContain("Successfully connected"); + describe("with connection string", () => { + it("connects to the database", async () => { + const response = await integration.mcpClient().callTool({ + name: "connect", + arguments: { + connectionString: integration.connectionString(), + }, }); + const content = getResponseContent(response.content); + expect(content).toContain("Successfully connected"); }); + }); - describe("with invalid connection string", () => { - it("returns error message", async () => { - const response = await integration.mcpClient().callTool({ - name: "connect", - arguments: { connectionString: "mangodb://localhost:12345" }, - }); - const content = getResponseContent(response.content); - expect(content).toContain("The configured connection string is not valid."); - - // Should not suggest using the config connection string (because we don't have one) - expect(content).not.toContain("Your config lists a different connection string"); + describe("with invalid connection string", () => { + it("returns error message", async () => { + const response = await integration.mcpClient().callTool({ + name: "connect", + arguments: { connectionString: "mangodb://localhost:12345" }, }); + const content = getResponseContent(response.content); + expect(content).toContain("The configured connection string is not valid."); + + // Should not suggest using the config connection string (because we don't have one) + expect(content).not.toContain("Your config lists a different connection string"); }); - }, - () => config -); + }); +}); describe("Connect tool when disabled", () => { const integration = setupIntegrationTest( diff --git a/tests/integration/tools/mongodb/mongodbHelpers.ts b/tests/integration/tools/mongodb/mongodbHelpers.ts index e3a332ae8..d777c9f68 100644 --- a/tests/integration/tools/mongodb/mongodbHelpers.ts +++ b/tests/integration/tools/mongodb/mongodbHelpers.ts @@ -66,12 +66,22 @@ export type MongoDBIntegrationTestCase = IntegrationTest & export type MongoSearchConfiguration = { search: true; image?: string }; +export type TestSuiteConfig = { + getUserConfig: (mdbIntegration: MongoDBIntegrationTest) => UserConfig; + getDriverOptions: (mdbIntegration: MongoDBIntegrationTest) => DriverOptions; + downloadOptions: MongoClusterConfiguration; +}; + +export const defaultTestSuiteConfig: TestSuiteConfig = { + getUserConfig: () => defaultTestConfig, + getDriverOptions: () => defaultDriverOptions, + downloadOptions: DEFAULT_MONGODB_PROCESS_OPTIONS, +}; + export function describeWithMongoDB( name: string, fn: (integration: MongoDBIntegrationTestCase) => void, - getUserConfig: (mdbIntegration: MongoDBIntegrationTest) => UserConfig = () => defaultTestConfig, - getDriverOptions: (mdbIntegration: MongoDBIntegrationTest) => DriverOptions = () => defaultDriverOptions, - downloadOptions: MongoClusterConfiguration = DEFAULT_MONGODB_PROCESS_OPTIONS + { getUserConfig, getDriverOptions, downloadOptions }: TestSuiteConfig = defaultTestSuiteConfig ): void { describe.skipIf(!MongoDBClusterProcess.isConfigurationSupportedInCurrentEnv(downloadOptions))(name, () => { const mdbIntegration = setupMongoDBIntegrationTest(downloadOptions); diff --git a/tests/integration/tools/mongodb/read/aggregate.test.ts b/tests/integration/tools/mongodb/read/aggregate.test.ts index 3f0a99a58..df8afa4df 100644 --- a/tests/integration/tools/mongodb/read/aggregate.test.ts +++ b/tests/integration/tools/mongodb/read/aggregate.test.ts @@ -6,7 +6,12 @@ import { defaultTestConfig, } from "../../../helpers.js"; import { beforeEach, describe, expect, it, vi, afterEach } from "vitest"; -import { describeWithMongoDB, getDocsFromUntrustedContent, validateAutoConnectBehavior } from "../mongodbHelpers.js"; +import { + defaultTestSuiteConfig, + describeWithMongoDB, + getDocsFromUntrustedContent, + validateAutoConnectBehavior, +} from "../mongodbHelpers.js"; import * as constants from "../../../../../src/helpers/constants.js"; import { freshInsertDocuments } from "./find.test.js"; @@ -282,7 +287,10 @@ describeWithMongoDB( ); }); }, - () => ({ ...defaultTestConfig, maxDocumentsPerQuery: 20 }) + { + ...defaultTestSuiteConfig, + getUserConfig: () => ({ ...defaultTestConfig, maxDocumentsPerQuery: 20 }), + } ); describeWithMongoDB( @@ -339,7 +347,10 @@ describeWithMongoDB( ); }); }, - () => ({ ...defaultTestConfig, maxBytesPerQuery: 200 }) + { + ...defaultTestSuiteConfig, + getUserConfig: () => ({ ...defaultTestConfig, maxBytesPerQuery: 200 }), + } ); describeWithMongoDB( @@ -369,5 +380,8 @@ describeWithMongoDB( expect(content).toContain(`Returning 990 documents.`); }); }, - () => ({ ...defaultTestConfig, maxDocumentsPerQuery: -1, maxBytesPerQuery: -1 }) + { + ...defaultTestSuiteConfig, + getUserConfig: () => ({ ...defaultTestConfig, maxDocumentsPerQuery: -1, maxBytesPerQuery: -1 }), + } ); diff --git a/tests/integration/tools/mongodb/read/export.test.ts b/tests/integration/tools/mongodb/read/export.test.ts index b20ca7229..5b18d9e8b 100644 --- a/tests/integration/tools/mongodb/read/export.test.ts +++ b/tests/integration/tools/mongodb/read/export.test.ts @@ -10,7 +10,7 @@ import { validateThrowsForInvalidArguments, validateToolMetadata, } from "../../../helpers.js"; -import { describeWithMongoDB } from "../mongodbHelpers.js"; +import { defaultTestSuiteConfig, describeWithMongoDB } from "../mongodbHelpers.js"; import type { UserConfig } from "../../../../../src/lib.js"; const userConfig: UserConfig = { @@ -458,5 +458,8 @@ describeWithMongoDB( }); }); }, - () => userConfig + { + ...defaultTestSuiteConfig, + getUserConfig: () => userConfig, + } ); diff --git a/tests/integration/tools/mongodb/read/find.test.ts b/tests/integration/tools/mongodb/read/find.test.ts index 3619e423c..8283c89ff 100644 --- a/tests/integration/tools/mongodb/read/find.test.ts +++ b/tests/integration/tools/mongodb/read/find.test.ts @@ -9,7 +9,12 @@ import { defaultTestConfig, } from "../../../helpers.js"; import * as constants from "../../../../../src/helpers/constants.js"; -import { describeWithMongoDB, getDocsFromUntrustedContent, validateAutoConnectBehavior } from "../mongodbHelpers.js"; +import { + defaultTestSuiteConfig, + describeWithMongoDB, + getDocsFromUntrustedContent, + validateAutoConnectBehavior, +} from "../mongodbHelpers.js"; export async function freshInsertDocuments({ collection, @@ -341,7 +346,10 @@ describeWithMongoDB( ); }); }, - () => ({ ...defaultTestConfig, maxDocumentsPerQuery: 10 }) + { + ...defaultTestSuiteConfig, + getUserConfig: () => ({ ...defaultTestConfig, maxDocumentsPerQuery: 10 }), + } ); describeWithMongoDB( @@ -391,7 +399,10 @@ describeWithMongoDB( ); }); }, - () => ({ ...defaultTestConfig, maxBytesPerQuery: 100 }) + { + ...defaultTestSuiteConfig, + getUserConfig: () => ({ ...defaultTestConfig, maxBytesPerQuery: 100 }), + } ); describeWithMongoDB( @@ -441,5 +452,8 @@ describeWithMongoDB( ); }); }, - () => ({ ...defaultTestConfig, maxDocumentsPerQuery: -1, maxBytesPerQuery: -1 }) + { + ...defaultTestSuiteConfig, + getUserConfig: () => ({ ...defaultTestConfig, maxDocumentsPerQuery: -1, maxBytesPerQuery: -1 }), + } ); diff --git a/tests/integration/tools/mongodb/search/listSearchIndexes.test.ts b/tests/integration/tools/mongodb/search/listSearchIndexes.test.ts index 97571c0a9..9b25623de 100644 --- a/tests/integration/tools/mongodb/search/listSearchIndexes.test.ts +++ b/tests/integration/tools/mongodb/search/listSearchIndexes.test.ts @@ -1,4 +1,4 @@ -import { describeWithMongoDB, getSingleDocFromUntrustedContent } from "../mongodbHelpers.js"; +import { defaultTestSuiteConfig, describeWithMongoDB, getSingleDocFromUntrustedContent } from "../mongodbHelpers.js"; import { describe, it, expect, beforeEach } from "vitest"; import { getResponseContent, @@ -117,9 +117,10 @@ describeWithMongoDB( ); }); }, - undefined, // default user config - undefined, // default driver config - { search: true } // use a search cluster + { + ...defaultTestSuiteConfig, + downloadOptions: { search: true }, + } ); async function waitUntilSearchIsReady(provider: NodeDriverServiceProvider, abortSignal: AbortSignal): Promise { From 4ae0952905200cae160a48d35a44904288893362 Mon Sep 17 00:00:00 2001 From: Himanshu Singh Date: Tue, 14 Oct 2025 13:09:14 +0200 Subject: [PATCH 2/6] chore: add elicitation options in test suite Additionally modifies elicitation tests to use describeWithMongoDB --- tests/integration/elicitation.test.ts | 510 +++++++++--------- .../tools/mongodb/mongodbHelpers.ts | 15 +- 2 files changed, 275 insertions(+), 250 deletions(-) diff --git a/tests/integration/elicitation.test.ts b/tests/integration/elicitation.test.ts index 0626fd51a..a5850f686 100644 --- a/tests/integration/elicitation.test.ts +++ b/tests/integration/elicitation.test.ts @@ -1,46 +1,195 @@ /* eslint-disable @typescript-eslint/no-unsafe-assignment */ -import { describe, it, expect } from "vitest"; -import { defaultDriverOptions, type UserConfig } from "../../src/common/config.js"; -import { defaultTestConfig, setupIntegrationTest } from "./helpers.js"; +import { describe, it, expect, afterEach } from "vitest"; +import { type UserConfig } from "../../src/common/config.js"; +import { defaultTestConfig } from "./helpers.js"; import { Elicitation } from "../../src/elicitation.js"; import { createMockElicitInput } from "../utils/elicitationMocks.js"; +import { defaultTestSuiteConfig, describeWithMongoDB } from "./tools/mongodb/mongodbHelpers.js"; + +function createTestConfig(config: Partial = {}): UserConfig { + return { + ...defaultTestConfig, + telemetry: "disabled", + // Add fake API credentials so Atlas tools get registered + apiClientId: "test-client-id", + apiClientSecret: "test-client-secret", + ...config, + }; +} describe("Elicitation Integration Tests", () => { - function createTestConfig(config: Partial = {}): UserConfig { - return { - ...defaultTestConfig, - telemetry: "disabled", - // Add fake API credentials so Atlas tools get registered - apiClientId: "test-client-id", - apiClientSecret: "test-client-secret", - ...config, - }; - } - - describe("with elicitation support", () => { - const mockElicitInput = createMockElicitInput(); - const integration = setupIntegrationTest( - () => createTestConfig(), - () => defaultDriverOptions, - { elicitInput: mockElicitInput } - ); - - describe("tools requiring confirmation by default", () => { - it("should request confirmation for drop-database tool and proceed when confirmed", async () => { - mockElicitInput.confirmYes(); + const mockElicitInput = createMockElicitInput(); + afterEach(() => { + mockElicitInput.clear(); + }); + + describeWithMongoDB( + "with elicitation support", + (integration) => { + describe("tools requiring confirmation by default", () => { + it("should request confirmation for drop-database tool and proceed when confirmed", async () => { + mockElicitInput.confirmYes(); + + const result = await integration.mcpClient().callTool({ + name: "drop-database", + arguments: { database: "test-db" }, + }); + + expect(mockElicitInput.mock).toHaveBeenCalledTimes(1); + expect(mockElicitInput.mock).toHaveBeenCalledWith({ + message: expect.stringContaining("You are about to drop the `test-db` database"), + requestedSchema: Elicitation.CONFIRMATION_SCHEMA, + }); + + // Should attempt to execute (will fail due to no connection, but confirms flow worked) + expect(result.isError).toBe(true); + expect(result.content).toEqual( + expect.arrayContaining([ + expect.objectContaining({ + type: "text", + text: expect.stringContaining("You need to connect to a MongoDB instance"), + }), + ]) + ); + }); + + it("should not proceed when user declines confirmation", async () => { + mockElicitInput.confirmNo(); + + const result = await integration.mcpClient().callTool({ + name: "drop-database", + arguments: { database: "test-db" }, + }); + + expect(mockElicitInput.mock).toHaveBeenCalledTimes(1); + expect(result.isError).toBeFalsy(); + expect(result.content).toEqual([ + { + type: "text", + text: "User did not confirm the execution of the `drop-database` tool so the operation was not performed.", + }, + ]); + }); + + it("should request confirmation for drop-collection tool", async () => { + mockElicitInput.confirmYes(); + + await integration.mcpClient().callTool({ + name: "drop-collection", + arguments: { database: "test-db", collection: "test-collection" }, + }); + + expect(mockElicitInput.mock).toHaveBeenCalledTimes(1); + expect(mockElicitInput.mock).toHaveBeenCalledWith({ + message: expect.stringContaining("You are about to drop the `test-collection` collection"), + requestedSchema: expect.objectContaining(Elicitation.CONFIRMATION_SCHEMA), + }); + }); + + it("should request confirmation for delete-many tool", async () => { + mockElicitInput.confirmYes(); + + await integration.mcpClient().callTool({ + name: "delete-many", + arguments: { + database: "test-db", + collection: "test-collection", + filter: { status: "inactive" }, + }, + }); + + expect(mockElicitInput.mock).toHaveBeenCalledTimes(1); + expect(mockElicitInput.mock).toHaveBeenCalledWith({ + message: expect.stringContaining("You are about to delete documents"), + requestedSchema: expect.objectContaining(Elicitation.CONFIRMATION_SCHEMA), + }); + }); + it("should request confirmation for create-db-user tool", async () => { + mockElicitInput.confirmYes(); + + await integration.mcpClient().callTool({ + name: "atlas-create-db-user", + arguments: { + projectId: "507f1f77bcf86cd799439011", // Valid 24-char hex string + username: "test-user", + roles: [{ roleName: "read", databaseName: "test-db" }], + }, + }); + + expect(mockElicitInput.mock).toHaveBeenCalledTimes(1); + expect(mockElicitInput.mock).toHaveBeenCalledWith({ + message: expect.stringContaining("You are about to create a database user"), + requestedSchema: expect.objectContaining(Elicitation.CONFIRMATION_SCHEMA), + }); + }); + + it("should request confirmation for create-access-list tool", async () => { + mockElicitInput.confirmYes(); + + await integration.mcpClient().callTool({ + name: "atlas-create-access-list", + arguments: { + projectId: "507f1f77bcf86cd799439011", // Valid 24-char hex string + ipAddresses: ["192.168.1.1"], + }, + }); + + expect(mockElicitInput.mock).toHaveBeenCalledTimes(1); + expect(mockElicitInput.mock).toHaveBeenCalledWith({ + message: expect.stringContaining( + "You are about to add the following entries to the access list" + ), + requestedSchema: expect.objectContaining(Elicitation.CONFIRMATION_SCHEMA), + }); + }); + }); + + describe("tools not requiring confirmation by default", () => { + it("should not request confirmation for read operations", async () => { + const result = await integration.mcpClient().callTool({ + name: "list-databases", + arguments: {}, + }); + + expect(mockElicitInput.mock).not.toHaveBeenCalled(); + // Should fail with connection error since we're not connected + expect(result.isError).toBe(true); + }); + + it("should not request confirmation for find operations", async () => { + const result = await integration.mcpClient().callTool({ + name: "find", + arguments: { + database: "test-db", + collection: "test-collection", + }, + }); + + expect(mockElicitInput.mock).not.toHaveBeenCalled(); + // Should fail with connection error since we're not connected + expect(result.isError).toBe(true); + }); + }); + }, + { + ...defaultTestSuiteConfig, + getUserConfig: () => createTestConfig(), + getMockElicitationInput: () => mockElicitInput, + } + ); + + describeWithMongoDB( + "without elicitation support", + (integration) => { + it("should proceed without confirmation for default confirmation-required tools when client lacks elicitation support", async () => { const result = await integration.mcpClient().callTool({ name: "drop-database", arguments: { database: "test-db" }, }); - expect(mockElicitInput.mock).toHaveBeenCalledTimes(1); - expect(mockElicitInput.mock).toHaveBeenCalledWith({ - message: expect.stringContaining("You are about to drop the `test-db` database"), - requestedSchema: Elicitation.CONFIRMATION_SCHEMA, - }); - - // Should attempt to execute (will fail due to no connection, but confirms flow worked) + // Note: No mock assertions needed since elicitation is disabled + // Should fail with connection error since we're not connected, but confirms flow bypassed confirmation expect(result.isError).toBe(true); expect(result.content).toEqual( expect.arrayContaining([ @@ -51,265 +200,130 @@ describe("Elicitation Integration Tests", () => { ]) ); }); - - it("should not proceed when user declines confirmation", async () => { - mockElicitInput.confirmNo(); - - const result = await integration.mcpClient().callTool({ - name: "drop-database", - arguments: { database: "test-db" }, - }); - - expect(mockElicitInput.mock).toHaveBeenCalledTimes(1); - expect(result.isError).toBeFalsy(); - expect(result.content).toEqual([ - { - type: "text", - text: "User did not confirm the execution of the `drop-database` tool so the operation was not performed.", - }, - ]); - }); - - it("should request confirmation for drop-collection tool", async () => { + }, + { + ...defaultTestSuiteConfig, + getUserConfig: () => createTestConfig(), + getClientCapabilities: () => ({}), + } + ); + + describeWithMongoDB( + "custom confirmation configuration", + (integration) => { + it("should confirm with a generic message with custom configurations for other tools", async () => { mockElicitInput.confirmYes(); await integration.mcpClient().callTool({ - name: "drop-collection", - arguments: { database: "test-db", collection: "test-collection" }, + name: "list-databases", + arguments: {}, }); expect(mockElicitInput.mock).toHaveBeenCalledTimes(1); expect(mockElicitInput.mock).toHaveBeenCalledWith({ - message: expect.stringContaining("You are about to drop the `test-collection` collection"), + message: expect.stringMatching( + /You are about to execute the `list-databases` tool which requires additional confirmation. Would you like to proceed\?/ + ), requestedSchema: expect.objectContaining(Elicitation.CONFIRMATION_SCHEMA), }); }); - it("should request confirmation for delete-many tool", async () => { - mockElicitInput.confirmYes(); - - await integration.mcpClient().callTool({ - name: "delete-many", - arguments: { - database: "test-db", - collection: "test-collection", - filter: { status: "inactive" }, - }, + it("should not request confirmation when tool is removed from default confirmationRequiredTools", async () => { + const result = await integration.mcpClient().callTool({ + name: "drop-database", + arguments: { database: "test-db" }, }); - expect(mockElicitInput.mock).toHaveBeenCalledTimes(1); - expect(mockElicitInput.mock).toHaveBeenCalledWith({ - message: expect.stringContaining("You are about to delete documents"), - requestedSchema: expect.objectContaining(Elicitation.CONFIRMATION_SCHEMA), - }); + expect(mockElicitInput.mock).not.toHaveBeenCalled(); + // Should fail with connection error since we're not connected + expect(result.isError).toBe(true); }); - - it("should request confirmation for create-db-user tool", async () => { + }, + { + ...defaultTestSuiteConfig, + getUserConfig: () => createTestConfig({ confirmationRequiredTools: ["list-databases"] }), + getMockElicitationInput: () => mockElicitInput, + } + ); + + describeWithMongoDB( + "confirmation message content validation", + (integration) => { + it("should include specific details in create-db-user confirmation", async () => { mockElicitInput.confirmYes(); await integration.mcpClient().callTool({ name: "atlas-create-db-user", arguments: { projectId: "507f1f77bcf86cd799439011", // Valid 24-char hex string - username: "test-user", - roles: [{ roleName: "read", databaseName: "test-db" }], + username: "myuser", + password: "mypassword", + roles: [ + { roleName: "readWrite", databaseName: "mydb" }, + { roleName: "read", databaseName: "logs", collectionName: "events" }, + ], + clusters: ["cluster1", "cluster2"], }, }); - expect(mockElicitInput.mock).toHaveBeenCalledTimes(1); expect(mockElicitInput.mock).toHaveBeenCalledWith({ - message: expect.stringContaining("You are about to create a database user"), + message: expect.stringMatching(/project.*507f1f77bcf86cd799439011/), requestedSchema: expect.objectContaining(Elicitation.CONFIRMATION_SCHEMA), }); }); - it("should request confirmation for create-access-list tool", async () => { + it("should include filter details in delete-many confirmation", async () => { mockElicitInput.confirmYes(); await integration.mcpClient().callTool({ - name: "atlas-create-access-list", + name: "delete-many", arguments: { - projectId: "507f1f77bcf86cd799439011", // Valid 24-char hex string - ipAddresses: ["192.168.1.1"], + database: "mydb", + collection: "users", + filter: { status: "inactive", lastLogin: { $lt: "2023-01-01" } }, }, }); - expect(mockElicitInput.mock).toHaveBeenCalledTimes(1); expect(mockElicitInput.mock).toHaveBeenCalledWith({ - message: expect.stringContaining("You are about to add the following entries to the access list"), + message: expect.stringMatching(/mydb.*database/), requestedSchema: expect.objectContaining(Elicitation.CONFIRMATION_SCHEMA), }); }); - }); - - describe("tools not requiring confirmation by default", () => { - it("should not request confirmation for read operations", async () => { - const result = await integration.mcpClient().callTool({ - name: "list-databases", - arguments: {}, - }); - - expect(mockElicitInput.mock).not.toHaveBeenCalled(); - // Should fail with connection error since we're not connected - expect(result.isError).toBe(true); - }); + }, + { + ...defaultTestSuiteConfig, + getUserConfig: () => createTestConfig(), + getMockElicitationInput: () => mockElicitInput, + } + ); + + describeWithMongoDB( + "error handling in confirmation flow", + (integration) => { + it("should handle confirmation errors gracefully", async () => { + mockElicitInput.rejectWith(new Error("Confirmation service unavailable")); - it("should not request confirmation for find operations", async () => { const result = await integration.mcpClient().callTool({ - name: "find", - arguments: { - database: "test-db", - collection: "test-collection", - }, + name: "drop-database", + arguments: { database: "test-db" }, }); - expect(mockElicitInput.mock).not.toHaveBeenCalled(); - // Should fail with connection error since we're not connected + expect(mockElicitInput.mock).toHaveBeenCalledTimes(1); expect(result.isError).toBe(true); + expect(result.content).toEqual( + expect.arrayContaining([ + expect.objectContaining({ + type: "text", + text: expect.stringContaining("Error running drop-database"), + }), + ]) + ); }); - }); - }); - - describe("without elicitation support", () => { - const integration = setupIntegrationTest( - () => createTestConfig(), - () => defaultDriverOptions, - { getClientCapabilities: () => ({}) } - ); - - it("should proceed without confirmation for default confirmation-required tools when client lacks elicitation support", async () => { - const result = await integration.mcpClient().callTool({ - name: "drop-database", - arguments: { database: "test-db" }, - }); - - // Note: No mock assertions needed since elicitation is disabled - // Should fail with connection error since we're not connected, but confirms flow bypassed confirmation - expect(result.isError).toBe(true); - expect(result.content).toEqual( - expect.arrayContaining([ - expect.objectContaining({ - type: "text", - text: expect.stringContaining("You need to connect to a MongoDB instance"), - }), - ]) - ); - }); - }); - - describe("custom confirmation configuration", () => { - const mockElicitInput = createMockElicitInput(); - const integration = setupIntegrationTest( - () => createTestConfig({ confirmationRequiredTools: ["list-databases"] }), - () => defaultDriverOptions, - { elicitInput: mockElicitInput } - ); - - it("should confirm with a generic message with custom configurations for other tools", async () => { - mockElicitInput.confirmYes(); - - await integration.mcpClient().callTool({ - name: "list-databases", - arguments: {}, - }); - - expect(mockElicitInput.mock).toHaveBeenCalledTimes(1); - expect(mockElicitInput.mock).toHaveBeenCalledWith({ - message: expect.stringMatching( - /You are about to execute the `list-databases` tool which requires additional confirmation. Would you like to proceed\?/ - ), - requestedSchema: expect.objectContaining(Elicitation.CONFIRMATION_SCHEMA), - }); - }); - - it("should not request confirmation when tool is removed from default confirmationRequiredTools", async () => { - const result = await integration.mcpClient().callTool({ - name: "drop-database", - arguments: { database: "test-db" }, - }); - - expect(mockElicitInput.mock).not.toHaveBeenCalled(); - // Should fail with connection error since we're not connected - expect(result.isError).toBe(true); - }); - }); - - describe("confirmation message content validation", () => { - const mockElicitInput = createMockElicitInput(); - const integration = setupIntegrationTest( - () => createTestConfig(), - () => defaultDriverOptions, - { elicitInput: mockElicitInput } - ); - - it("should include specific details in create-db-user confirmation", async () => { - mockElicitInput.confirmYes(); - - await integration.mcpClient().callTool({ - name: "atlas-create-db-user", - arguments: { - projectId: "507f1f77bcf86cd799439011", // Valid 24-char hex string - username: "myuser", - password: "mypassword", - roles: [ - { roleName: "readWrite", databaseName: "mydb" }, - { roleName: "read", databaseName: "logs", collectionName: "events" }, - ], - clusters: ["cluster1", "cluster2"], - }, - }); - - expect(mockElicitInput.mock).toHaveBeenCalledWith({ - message: expect.stringMatching(/project.*507f1f77bcf86cd799439011/), - requestedSchema: expect.objectContaining(Elicitation.CONFIRMATION_SCHEMA), - }); - }); - - it("should include filter details in delete-many confirmation", async () => { - mockElicitInput.confirmYes(); - - await integration.mcpClient().callTool({ - name: "delete-many", - arguments: { - database: "mydb", - collection: "users", - filter: { status: "inactive", lastLogin: { $lt: "2023-01-01" } }, - }, - }); - - expect(mockElicitInput.mock).toHaveBeenCalledWith({ - message: expect.stringMatching(/mydb.*database/), - requestedSchema: expect.objectContaining(Elicitation.CONFIRMATION_SCHEMA), - }); - }); - }); - - describe("error handling in confirmation flow", () => { - const mockElicitInput = createMockElicitInput(); - const integration = setupIntegrationTest( - () => createTestConfig(), - () => defaultDriverOptions, - { elicitInput: mockElicitInput } - ); - - it("should handle confirmation errors gracefully", async () => { - mockElicitInput.rejectWith(new Error("Confirmation service unavailable")); - - const result = await integration.mcpClient().callTool({ - name: "drop-database", - arguments: { database: "test-db" }, - }); - - expect(mockElicitInput.mock).toHaveBeenCalledTimes(1); - expect(result.isError).toBe(true); - expect(result.content).toEqual( - expect.arrayContaining([ - expect.objectContaining({ - type: "text", - text: expect.stringContaining("Error running drop-database"), - }), - ]) - ); - }); - }); + }, + { + ...defaultTestSuiteConfig, + getUserConfig: () => createTestConfig(), + getMockElicitationInput: () => mockElicitInput, + } + ); }); diff --git a/tests/integration/tools/mongodb/mongodbHelpers.ts b/tests/integration/tools/mongodb/mongodbHelpers.ts index d777c9f68..42361435a 100644 --- a/tests/integration/tools/mongodb/mongodbHelpers.ts +++ b/tests/integration/tools/mongodb/mongodbHelpers.ts @@ -16,6 +16,7 @@ import { afterAll, afterEach, beforeAll, beforeEach, describe, expect, it } from import { EJSON } from "bson"; import { MongoDBClusterProcess } from "./mongodbClusterProcess.js"; import type { MongoClusterConfiguration } from "./mongodbClusterProcess.js"; +import type { createMockElicitInput, MockClientCapabilities } from "../../../utils/elicitationMocks.js"; const __dirname = path.dirname(fileURLToPath(import.meta.url)); @@ -70,6 +71,8 @@ export type TestSuiteConfig = { getUserConfig: (mdbIntegration: MongoDBIntegrationTest) => UserConfig; getDriverOptions: (mdbIntegration: MongoDBIntegrationTest) => DriverOptions; downloadOptions: MongoClusterConfiguration; + getMockElicitationInput?: () => ReturnType; + getClientCapabilities?: () => MockClientCapabilities; }; export const defaultTestSuiteConfig: TestSuiteConfig = { @@ -81,17 +84,25 @@ export const defaultTestSuiteConfig: TestSuiteConfig = { export function describeWithMongoDB( name: string, fn: (integration: MongoDBIntegrationTestCase) => void, - { getUserConfig, getDriverOptions, downloadOptions }: TestSuiteConfig = defaultTestSuiteConfig + { + getUserConfig, + getDriverOptions, + downloadOptions, + getMockElicitationInput, + getClientCapabilities, + }: TestSuiteConfig = defaultTestSuiteConfig ): void { describe.skipIf(!MongoDBClusterProcess.isConfigurationSupportedInCurrentEnv(downloadOptions))(name, () => { const mdbIntegration = setupMongoDBIntegrationTest(downloadOptions); + const mockElicitInput = getMockElicitationInput?.(); const integration = setupIntegrationTest( () => ({ ...getUserConfig(mdbIntegration), }), () => ({ ...getDriverOptions(mdbIntegration), - }) + }), + { elicitInput: mockElicitInput, getClientCapabilities } ); fn({ From 702942840ac2938b8e69a63dc5256867cda80470 Mon Sep 17 00:00:00 2001 From: Himanshu Singh Date: Tue, 14 Oct 2025 13:15:51 +0200 Subject: [PATCH 3/6] chore: adapt connect tool to use describeWithMongoDB --- .../tools/mongodb/connect/connect.test.ts | 49 ++++++++++--------- 1 file changed, 25 insertions(+), 24 deletions(-) diff --git a/tests/integration/tools/mongodb/connect/connect.test.ts b/tests/integration/tools/mongodb/connect/connect.test.ts index 4dbd2cc5c..a1cde830c 100644 --- a/tests/integration/tools/mongodb/connect/connect.test.ts +++ b/tests/integration/tools/mongodb/connect/connect.test.ts @@ -1,12 +1,11 @@ import { defaultTestSuiteConfig, describeWithMongoDB } from "../mongodbHelpers.js"; import { - defaultDriverOptions, getResponseContent, getResponseElements, validateThrowsForInvalidArguments, validateToolMetadata, } from "../../../helpers.js"; -import { defaultTestConfig, setupIntegrationTest } from "../../../helpers.js"; +import { defaultTestConfig } from "../../../helpers.js"; import { beforeEach, describe, expect, it } from "vitest"; describeWithMongoDB( @@ -141,28 +140,30 @@ describeWithMongoDB("Connect tool", (integration) => { }); }); -describe("Connect tool when disabled", () => { - const integration = setupIntegrationTest( - () => ({ +describeWithMongoDB( + "Connect tool when disabled", + (integration) => { + it("is not suggested when querying MongoDB disconnected", async () => { + const response = await integration.mcpClient().callTool({ + name: "find", + arguments: { database: "some-db", collection: "some-collection" }, + }); + + const elements = getResponseElements(response); + expect(elements).toHaveLength(2); + expect(elements[0]?.text).toContain( + "You need to connect to a MongoDB instance before you can access its data." + ); + expect(elements[1]?.text).toContain( + "There are no tools available to connect. Please update the configuration to include a connection string and restart the server." + ); + }); + }, + { + ...defaultTestSuiteConfig, + getUserConfig: () => ({ ...defaultTestConfig, disabledTools: ["connect"], }), - () => defaultDriverOptions - ); - - it("is not suggested when querying MongoDB disconnected", async () => { - const response = await integration.mcpClient().callTool({ - name: "find", - arguments: { database: "some-db", collection: "some-collection" }, - }); - - const elements = getResponseElements(response); - expect(elements).toHaveLength(2); - expect(elements[0]?.text).toContain( - "You need to connect to a MongoDB instance before you can access its data." - ); - expect(elements[1]?.text).toContain( - "There are no tools available to connect. Please update the configuration to include a connection string and restart the server." - ); - }); -}); + } +); From c9dd27979327b8f99920feacada31bbf72452c39 Mon Sep 17 00:00:00 2001 From: Himanshu Singh Date: Tue, 14 Oct 2025 13:20:36 +0200 Subject: [PATCH 4/6] chore: adapt server.test.ts to use describeWithMongoDB --- tests/integration/server.test.ts | 112 ++++++++++++++++--------------- 1 file changed, 58 insertions(+), 54 deletions(-) diff --git a/tests/integration/server.test.ts b/tests/integration/server.test.ts index 539305f0d..315de7e26 100644 --- a/tests/integration/server.test.ts +++ b/tests/integration/server.test.ts @@ -1,4 +1,4 @@ -import { defaultDriverOptions, defaultTestConfig, expectDefined, setupIntegrationTest } from "./helpers.js"; +import { defaultTestConfig, expectDefined } from "./helpers.js"; import { defaultTestSuiteConfig, describeWithMongoDB } from "./tools/mongodb/mongodbHelpers.js"; import { describe, expect, it } from "vitest"; @@ -25,73 +25,77 @@ describe("Server integration test", () => { } ); - describe("with atlas", () => { - const integration = setupIntegrationTest( - () => ({ + describeWithMongoDB( + "with atlas", + (integration) => { + describe("list capabilities", () => { + it("should return positive number of tools and have some atlas tools", async () => { + const tools = await integration.mcpClient().listTools(); + expectDefined(tools); + expect(tools.tools.length).toBeGreaterThan(0); + + const atlasTools = tools.tools.filter((tool) => tool.name.startsWith("atlas-")); + expect(atlasTools.length).toBeGreaterThan(0); + }); + + it("should return no prompts", async () => { + await expect(() => integration.mcpClient().listPrompts()).rejects.toMatchObject({ + message: "MCP error -32601: Method not found", + }); + }); + + it("should return capabilities", () => { + const capabilities = integration.mcpClient().getServerCapabilities(); + expectDefined(capabilities); + expectDefined(capabilities?.logging); + expectDefined(capabilities?.completions); + expectDefined(capabilities?.tools); + expectDefined(capabilities?.resources); + expect(capabilities.experimental).toBeUndefined(); + expect(capabilities.prompts).toBeUndefined(); + }); + }); + }, + { + ...defaultTestSuiteConfig, + getUserConfig: () => ({ ...defaultTestConfig, apiClientId: "test", apiClientSecret: "test", }), - () => defaultDriverOptions - ); + } + ); - describe("list capabilities", () => { - it("should return positive number of tools and have some atlas tools", async () => { + describeWithMongoDB( + "with read-only mode", + (integration) => { + it("should only register read and metadata operation tools when read-only mode is enabled", async () => { const tools = await integration.mcpClient().listTools(); expectDefined(tools); expect(tools.tools.length).toBeGreaterThan(0); - const atlasTools = tools.tools.filter((tool) => tool.name.startsWith("atlas-")); - expect(atlasTools.length).toBeGreaterThan(0); - }); - - it("should return no prompts", async () => { - await expect(() => integration.mcpClient().listPrompts()).rejects.toMatchObject({ - message: "MCP error -32601: Method not found", - }); - }); + // Check that we have some tools available (the read and metadata ones) + expect(tools.tools.some((tool) => tool.name === "find")).toBe(true); + expect(tools.tools.some((tool) => tool.name === "collection-schema")).toBe(true); + expect(tools.tools.some((tool) => tool.name === "list-databases")).toBe(true); + expect(tools.tools.some((tool) => tool.name === "atlas-list-orgs")).toBe(true); + expect(tools.tools.some((tool) => tool.name === "atlas-list-projects")).toBe(true); - it("should return capabilities", () => { - const capabilities = integration.mcpClient().getServerCapabilities(); - expectDefined(capabilities); - expectDefined(capabilities?.logging); - expectDefined(capabilities?.completions); - expectDefined(capabilities?.tools); - expectDefined(capabilities?.resources); - expect(capabilities.experimental).toBeUndefined(); - expect(capabilities.prompts).toBeUndefined(); + // Check that non-read tools are NOT available + expect(tools.tools.some((tool) => tool.name === "insert-one")).toBe(false); + expect(tools.tools.some((tool) => tool.name === "update-many")).toBe(false); + expect(tools.tools.some((tool) => tool.name === "delete-one")).toBe(false); + expect(tools.tools.some((tool) => tool.name === "drop-collection")).toBe(false); }); - }); - }); - - describe("with read-only mode", () => { - const integration = setupIntegrationTest( - () => ({ + }, + { + ...defaultTestSuiteConfig, + getUserConfig: () => ({ ...defaultTestConfig, readOnly: true, apiClientId: "test", apiClientSecret: "test", }), - () => defaultDriverOptions - ); - - it("should only register read and metadata operation tools when read-only mode is enabled", async () => { - const tools = await integration.mcpClient().listTools(); - expectDefined(tools); - expect(tools.tools.length).toBeGreaterThan(0); - - // Check that we have some tools available (the read and metadata ones) - expect(tools.tools.some((tool) => tool.name === "find")).toBe(true); - expect(tools.tools.some((tool) => tool.name === "collection-schema")).toBe(true); - expect(tools.tools.some((tool) => tool.name === "list-databases")).toBe(true); - expect(tools.tools.some((tool) => tool.name === "atlas-list-orgs")).toBe(true); - expect(tools.tools.some((tool) => tool.name === "atlas-list-projects")).toBe(true); - - // Check that non-read tools are NOT available - expect(tools.tools.some((tool) => tool.name === "insert-one")).toBe(false); - expect(tools.tools.some((tool) => tool.name === "update-many")).toBe(false); - expect(tools.tools.some((tool) => tool.name === "delete-one")).toBe(false); - expect(tools.tools.some((tool) => tool.name === "drop-collection")).toBe(false); - }); - }); + } + ); }); From 475ea0900a2f5e1e141ef45927302e1d1e9af87d Mon Sep 17 00:00:00 2001 From: Himanshu Singh Date: Tue, 14 Oct 2025 13:20:50 +0200 Subject: [PATCH 5/6] chore: adapt dropIndex.test.ts to use describeWithMongoDB --- .../tools/mongodb/delete/dropIndex.test.ts | 125 +++++++++--------- 1 file changed, 62 insertions(+), 63 deletions(-) diff --git a/tests/integration/tools/mongodb/delete/dropIndex.test.ts b/tests/integration/tools/mongodb/delete/dropIndex.test.ts index a1aac591e..4d495b7fb 100644 --- a/tests/integration/tools/mongodb/delete/dropIndex.test.ts +++ b/tests/integration/tools/mongodb/delete/dropIndex.test.ts @@ -3,15 +3,12 @@ import type { Collection } from "mongodb"; import { databaseCollectionInvalidArgs, databaseCollectionParameters, - defaultDriverOptions, - defaultTestConfig, getDataFromUntrustedContent, getResponseContent, - setupIntegrationTest, validateThrowsForInvalidArguments, validateToolMetadata, } from "../../../helpers.js"; -import { describeWithMongoDB, setupMongoDBIntegrationTest } from "../mongodbHelpers.js"; +import { defaultTestSuiteConfig, describeWithMongoDB } from "../mongodbHelpers.js"; import { createMockElicitInput } from "../../../../utils/elicitationMocks.js"; import { Elicitation } from "../../../../../src/elicitation.js"; @@ -113,69 +110,71 @@ describeWithMongoDB("drop-index tool", (integration) => { }); }); -describe("drop-index tool - when invoked via an elicitation enabled client", () => { - const mockElicitInput = createMockElicitInput(); - const mdbIntegration = setupMongoDBIntegrationTest(); - const integration = setupIntegrationTest( - () => defaultTestConfig, - () => defaultDriverOptions, - { elicitInput: mockElicitInput } - ); - let moviesCollection: Collection; - let indexName: string; +const mockElicitInput = createMockElicitInput(); - beforeEach(async () => { - moviesCollection = mdbIntegration.mongoClient().db("mflix").collection("movies"); - await moviesCollection.insertMany([ - { name: "Movie1", year: 1994 }, - { name: "Movie2", year: 2001 }, - ]); - indexName = await moviesCollection.createIndex({ year: 1 }); - await integration.mcpClient().callTool({ - name: "connect", - arguments: { - connectionString: mdbIntegration.connectionString(), - }, - }); - }); - - afterEach(async () => { - await moviesCollection.drop(); - }); +describeWithMongoDB( + "drop-index tool - when invoked via an elicitation enabled client", + (integration) => { + let moviesCollection: Collection; + let indexName: string; - it("should ask for confirmation before proceeding with tool call", async () => { - expect(await moviesCollection.listIndexes().toArray()).toHaveLength(2); - mockElicitInput.confirmYes(); - await integration.mcpClient().callTool({ - name: "drop-index", - arguments: { database: "mflix", collection: "movies", indexName }, + beforeEach(async () => { + moviesCollection = integration.mongoClient().db("mflix").collection("movies"); + await moviesCollection.insertMany([ + { name: "Movie1", year: 1994 }, + { name: "Movie2", year: 2001 }, + ]); + indexName = await moviesCollection.createIndex({ year: 1 }); + await integration.mcpClient().callTool({ + name: "connect", + arguments: { + connectionString: integration.connectionString(), + }, + }); }); - expect(mockElicitInput.mock).toHaveBeenCalledTimes(1); - expect(mockElicitInput.mock).toHaveBeenCalledWith({ - // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment - message: expect.stringContaining( - "You are about to drop the `year_1` index from the `mflix.movies` namespace" - ), - requestedSchema: Elicitation.CONFIRMATION_SCHEMA, + + afterEach(async () => { + await moviesCollection.drop(); }); - expect(await moviesCollection.listIndexes().toArray()).toHaveLength(1); - }); - it("should not drop the index if the confirmation was not provided", async () => { - expect(await moviesCollection.listIndexes().toArray()).toHaveLength(2); - mockElicitInput.confirmNo(); - await integration.mcpClient().callTool({ - name: "drop-index", - arguments: { database: "mflix", collection: "movies", indexName }, + it("should ask for confirmation before proceeding with tool call", async () => { + expect(await moviesCollection.listIndexes().toArray()).toHaveLength(2); + mockElicitInput.confirmYes(); + await integration.mcpClient().callTool({ + name: "drop-index", + arguments: { database: "mflix", collection: "movies", indexName }, + }); + expect(mockElicitInput.mock).toHaveBeenCalledTimes(1); + expect(mockElicitInput.mock).toHaveBeenCalledWith({ + // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment + message: expect.stringContaining( + "You are about to drop the `year_1` index from the `mflix.movies` namespace" + ), + requestedSchema: Elicitation.CONFIRMATION_SCHEMA, + }); + expect(await moviesCollection.listIndexes().toArray()).toHaveLength(1); }); - expect(mockElicitInput.mock).toHaveBeenCalledTimes(1); - expect(mockElicitInput.mock).toHaveBeenCalledWith({ - // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment - message: expect.stringContaining( - "You are about to drop the `year_1` index from the `mflix.movies` namespace" - ), - requestedSchema: Elicitation.CONFIRMATION_SCHEMA, + + it("should not drop the index if the confirmation was not provided", async () => { + expect(await moviesCollection.listIndexes().toArray()).toHaveLength(2); + mockElicitInput.confirmNo(); + await integration.mcpClient().callTool({ + name: "drop-index", + arguments: { database: "mflix", collection: "movies", indexName }, + }); + expect(mockElicitInput.mock).toHaveBeenCalledTimes(1); + expect(mockElicitInput.mock).toHaveBeenCalledWith({ + // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment + message: expect.stringContaining( + "You are about to drop the `year_1` index from the `mflix.movies` namespace" + ), + requestedSchema: Elicitation.CONFIRMATION_SCHEMA, + }); + expect(await moviesCollection.listIndexes().toArray()).toHaveLength(2); }); - expect(await moviesCollection.listIndexes().toArray()).toHaveLength(2); - }); -}); + }, + { + ...defaultTestSuiteConfig, + getMockElicitationInput: () => mockElicitInput, + } +); From e6348ebd6b8ffa31b6d96633043a2daf3cb18a23 Mon Sep 17 00:00:00 2001 From: Himanshu Singh Date: Tue, 14 Oct 2025 19:32:08 +0200 Subject: [PATCH 6/6] chore: accept partial test suite config on the interface --- .../common/connectionManager.oidc.test.ts | 8 +------- tests/integration/elicitation.test.ts | 7 +------ tests/integration/indexCheck.test.ts | 5 +---- tests/integration/resources/exportedData.test.ts | 3 +-- tests/integration/server.test.ts | 5 +---- .../tools/mongodb/connect/connect.test.ts | 4 +--- .../tools/mongodb/delete/dropIndex.test.ts | 3 +-- tests/integration/tools/mongodb/mongodbHelpers.ts | 14 ++++++-------- .../tools/mongodb/read/aggregate.test.ts | 10 +--------- .../integration/tools/mongodb/read/export.test.ts | 3 +-- tests/integration/tools/mongodb/read/find.test.ts | 10 +--------- .../tools/mongodb/search/listSearchIndexes.test.ts | 3 +-- 12 files changed, 17 insertions(+), 58 deletions(-) diff --git a/tests/integration/common/connectionManager.oidc.test.ts b/tests/integration/common/connectionManager.oidc.test.ts index 670af9ba1..3d949bc88 100644 --- a/tests/integration/common/connectionManager.oidc.test.ts +++ b/tests/integration/common/connectionManager.oidc.test.ts @@ -3,12 +3,7 @@ import { describe, beforeEach, afterAll, it, expect, vi } from "vitest"; import semver from "semver"; import process from "process"; import type { MongoDBIntegrationTestCase } from "../tools/mongodb/mongodbHelpers.js"; -import { - describeWithMongoDB, - isCommunityServer, - getServerVersion, - defaultTestSuiteConfig, -} from "../tools/mongodb/mongodbHelpers.js"; +import { describeWithMongoDB, isCommunityServer, getServerVersion } from "../tools/mongodb/mongodbHelpers.js"; import { defaultTestConfig, responseAsText, timeout, waitUntil } from "../helpers.js"; import type { ConnectionStateConnected, ConnectionStateConnecting } from "../../../src/common/connectionManager.js"; import type { UserConfig } from "../../../src/common/config.js"; @@ -143,7 +138,6 @@ describe.skipIf(process.platform !== "linux")("ConnectionManager OIDC Tests", as addCb?.(oidcIt); }, { - ...defaultTestSuiteConfig, getUserConfig: () => oidcConfig, getDriverOptions: () => setupDriverConfig({ diff --git a/tests/integration/elicitation.test.ts b/tests/integration/elicitation.test.ts index a5850f686..d4664882b 100644 --- a/tests/integration/elicitation.test.ts +++ b/tests/integration/elicitation.test.ts @@ -4,7 +4,7 @@ import { type UserConfig } from "../../src/common/config.js"; import { defaultTestConfig } from "./helpers.js"; import { Elicitation } from "../../src/elicitation.js"; import { createMockElicitInput } from "../utils/elicitationMocks.js"; -import { defaultTestSuiteConfig, describeWithMongoDB } from "./tools/mongodb/mongodbHelpers.js"; +import { describeWithMongoDB } from "./tools/mongodb/mongodbHelpers.js"; function createTestConfig(config: Partial = {}): UserConfig { return { @@ -173,7 +173,6 @@ describe("Elicitation Integration Tests", () => { }); }, { - ...defaultTestSuiteConfig, getUserConfig: () => createTestConfig(), getMockElicitationInput: () => mockElicitInput, } @@ -202,7 +201,6 @@ describe("Elicitation Integration Tests", () => { }); }, { - ...defaultTestSuiteConfig, getUserConfig: () => createTestConfig(), getClientCapabilities: () => ({}), } @@ -240,7 +238,6 @@ describe("Elicitation Integration Tests", () => { }); }, { - ...defaultTestSuiteConfig, getUserConfig: () => createTestConfig({ confirmationRequiredTools: ["list-databases"] }), getMockElicitationInput: () => mockElicitInput, } @@ -291,7 +288,6 @@ describe("Elicitation Integration Tests", () => { }); }, { - ...defaultTestSuiteConfig, getUserConfig: () => createTestConfig(), getMockElicitationInput: () => mockElicitInput, } @@ -321,7 +317,6 @@ describe("Elicitation Integration Tests", () => { }); }, { - ...defaultTestSuiteConfig, getUserConfig: () => createTestConfig(), getMockElicitationInput: () => mockElicitInput, } diff --git a/tests/integration/indexCheck.test.ts b/tests/integration/indexCheck.test.ts index d2c7c106b..b99209201 100644 --- a/tests/integration/indexCheck.test.ts +++ b/tests/integration/indexCheck.test.ts @@ -1,5 +1,5 @@ import { defaultTestConfig, getResponseContent } from "./helpers.js"; -import { defaultTestSuiteConfig, describeWithMongoDB } from "./tools/mongodb/mongodbHelpers.js"; +import { describeWithMongoDB } from "./tools/mongodb/mongodbHelpers.js"; import { beforeEach, describe, expect, it } from "vitest"; describe("IndexCheck integration tests", () => { @@ -314,7 +314,6 @@ describe("IndexCheck integration tests", () => { }); }, { - ...defaultTestSuiteConfig, getUserConfig: () => ({ ...defaultTestConfig, indexCheck: true, // Enable indexCheck @@ -428,7 +427,6 @@ describe("IndexCheck integration tests", () => { }); }, { - ...defaultTestSuiteConfig, getUserConfig: () => ({ ...defaultTestConfig, indexCheck: false, // Disable indexCheck @@ -463,7 +461,6 @@ describe("IndexCheck integration tests", () => { }); }, { - ...defaultTestSuiteConfig, getUserConfig: () => ({ ...defaultTestConfig, // indexCheck not specified, should default to false diff --git a/tests/integration/resources/exportedData.test.ts b/tests/integration/resources/exportedData.test.ts index 24f014450..5903fb23d 100644 --- a/tests/integration/resources/exportedData.test.ts +++ b/tests/integration/resources/exportedData.test.ts @@ -4,7 +4,7 @@ import { EJSON, Long, ObjectId } from "bson"; import { describe, expect, it, beforeEach, afterAll } from "vitest"; import type { CallToolResult } from "@modelcontextprotocol/sdk/types.js"; import { defaultTestConfig, getDataFromUntrustedContent, resourceChangedNotification, timeout } from "../helpers.js"; -import { defaultTestSuiteConfig, describeWithMongoDB } from "../tools/mongodb/mongodbHelpers.js"; +import { describeWithMongoDB } from "../tools/mongodb/mongodbHelpers.js"; import { contentWithResourceURILink } from "../tools/mongodb/read/export.test.js"; import type { UserConfig } from "../../../src/lib.js"; @@ -174,7 +174,6 @@ describeWithMongoDB( }); }, { - ...defaultTestSuiteConfig, getUserConfig: () => userConfig, } ); diff --git a/tests/integration/server.test.ts b/tests/integration/server.test.ts index 315de7e26..090df4a53 100644 --- a/tests/integration/server.test.ts +++ b/tests/integration/server.test.ts @@ -1,5 +1,5 @@ import { defaultTestConfig, expectDefined } from "./helpers.js"; -import { defaultTestSuiteConfig, describeWithMongoDB } from "./tools/mongodb/mongodbHelpers.js"; +import { describeWithMongoDB } from "./tools/mongodb/mongodbHelpers.js"; import { describe, expect, it } from "vitest"; describe("Server integration test", () => { @@ -16,7 +16,6 @@ describe("Server integration test", () => { }); }, { - ...defaultTestSuiteConfig, getUserConfig: () => ({ ...defaultTestConfig, apiClientId: undefined, @@ -57,7 +56,6 @@ describe("Server integration test", () => { }); }, { - ...defaultTestSuiteConfig, getUserConfig: () => ({ ...defaultTestConfig, apiClientId: "test", @@ -89,7 +87,6 @@ describe("Server integration test", () => { }); }, { - ...defaultTestSuiteConfig, getUserConfig: () => ({ ...defaultTestConfig, readOnly: true, diff --git a/tests/integration/tools/mongodb/connect/connect.test.ts b/tests/integration/tools/mongodb/connect/connect.test.ts index a1cde830c..132e0fd90 100644 --- a/tests/integration/tools/mongodb/connect/connect.test.ts +++ b/tests/integration/tools/mongodb/connect/connect.test.ts @@ -1,4 +1,4 @@ -import { defaultTestSuiteConfig, describeWithMongoDB } from "../mongodbHelpers.js"; +import { describeWithMongoDB } from "../mongodbHelpers.js"; import { getResponseContent, getResponseElements, @@ -81,7 +81,6 @@ describeWithMongoDB( }); }, { - ...defaultTestSuiteConfig, getUserConfig: (mdbIntegration) => ({ ...defaultTestConfig, connectionString: mdbIntegration.connectionString(), @@ -160,7 +159,6 @@ describeWithMongoDB( }); }, { - ...defaultTestSuiteConfig, getUserConfig: () => ({ ...defaultTestConfig, disabledTools: ["connect"], diff --git a/tests/integration/tools/mongodb/delete/dropIndex.test.ts b/tests/integration/tools/mongodb/delete/dropIndex.test.ts index 4d495b7fb..46360b81b 100644 --- a/tests/integration/tools/mongodb/delete/dropIndex.test.ts +++ b/tests/integration/tools/mongodb/delete/dropIndex.test.ts @@ -8,7 +8,7 @@ import { validateThrowsForInvalidArguments, validateToolMetadata, } from "../../../helpers.js"; -import { defaultTestSuiteConfig, describeWithMongoDB } from "../mongodbHelpers.js"; +import { describeWithMongoDB } from "../mongodbHelpers.js"; import { createMockElicitInput } from "../../../../utils/elicitationMocks.js"; import { Elicitation } from "../../../../../src/elicitation.js"; @@ -174,7 +174,6 @@ describeWithMongoDB( }); }, { - ...defaultTestSuiteConfig, getMockElicitationInput: () => mockElicitInput, } ); diff --git a/tests/integration/tools/mongodb/mongodbHelpers.ts b/tests/integration/tools/mongodb/mongodbHelpers.ts index 42361435a..7c6da4874 100644 --- a/tests/integration/tools/mongodb/mongodbHelpers.ts +++ b/tests/integration/tools/mongodb/mongodbHelpers.ts @@ -75,7 +75,7 @@ export type TestSuiteConfig = { getClientCapabilities?: () => MockClientCapabilities; }; -export const defaultTestSuiteConfig: TestSuiteConfig = { +const defaultTestSuiteConfig: TestSuiteConfig = { getUserConfig: () => defaultTestConfig, getDriverOptions: () => defaultDriverOptions, downloadOptions: DEFAULT_MONGODB_PROCESS_OPTIONS, @@ -84,14 +84,12 @@ export const defaultTestSuiteConfig: TestSuiteConfig = { export function describeWithMongoDB( name: string, fn: (integration: MongoDBIntegrationTestCase) => void, - { - getUserConfig, - getDriverOptions, - downloadOptions, - getMockElicitationInput, - getClientCapabilities, - }: TestSuiteConfig = defaultTestSuiteConfig + partialTestSuiteConfig?: Partial ): void { + const { getUserConfig, getDriverOptions, downloadOptions, getMockElicitationInput, getClientCapabilities } = { + ...defaultTestSuiteConfig, + ...partialTestSuiteConfig, + }; describe.skipIf(!MongoDBClusterProcess.isConfigurationSupportedInCurrentEnv(downloadOptions))(name, () => { const mdbIntegration = setupMongoDBIntegrationTest(downloadOptions); const mockElicitInput = getMockElicitationInput?.(); diff --git a/tests/integration/tools/mongodb/read/aggregate.test.ts b/tests/integration/tools/mongodb/read/aggregate.test.ts index df8afa4df..d585d5786 100644 --- a/tests/integration/tools/mongodb/read/aggregate.test.ts +++ b/tests/integration/tools/mongodb/read/aggregate.test.ts @@ -6,12 +6,7 @@ import { defaultTestConfig, } from "../../../helpers.js"; import { beforeEach, describe, expect, it, vi, afterEach } from "vitest"; -import { - defaultTestSuiteConfig, - describeWithMongoDB, - getDocsFromUntrustedContent, - validateAutoConnectBehavior, -} from "../mongodbHelpers.js"; +import { describeWithMongoDB, getDocsFromUntrustedContent, validateAutoConnectBehavior } from "../mongodbHelpers.js"; import * as constants from "../../../../../src/helpers/constants.js"; import { freshInsertDocuments } from "./find.test.js"; @@ -288,7 +283,6 @@ describeWithMongoDB( }); }, { - ...defaultTestSuiteConfig, getUserConfig: () => ({ ...defaultTestConfig, maxDocumentsPerQuery: 20 }), } ); @@ -348,7 +342,6 @@ describeWithMongoDB( }); }, { - ...defaultTestSuiteConfig, getUserConfig: () => ({ ...defaultTestConfig, maxBytesPerQuery: 200 }), } ); @@ -381,7 +374,6 @@ describeWithMongoDB( }); }, { - ...defaultTestSuiteConfig, getUserConfig: () => ({ ...defaultTestConfig, maxDocumentsPerQuery: -1, maxBytesPerQuery: -1 }), } ); diff --git a/tests/integration/tools/mongodb/read/export.test.ts b/tests/integration/tools/mongodb/read/export.test.ts index 5b18d9e8b..2c0310b6e 100644 --- a/tests/integration/tools/mongodb/read/export.test.ts +++ b/tests/integration/tools/mongodb/read/export.test.ts @@ -10,7 +10,7 @@ import { validateThrowsForInvalidArguments, validateToolMetadata, } from "../../../helpers.js"; -import { defaultTestSuiteConfig, describeWithMongoDB } from "../mongodbHelpers.js"; +import { describeWithMongoDB } from "../mongodbHelpers.js"; import type { UserConfig } from "../../../../../src/lib.js"; const userConfig: UserConfig = { @@ -459,7 +459,6 @@ describeWithMongoDB( }); }, { - ...defaultTestSuiteConfig, getUserConfig: () => userConfig, } ); diff --git a/tests/integration/tools/mongodb/read/find.test.ts b/tests/integration/tools/mongodb/read/find.test.ts index 8283c89ff..c466650fa 100644 --- a/tests/integration/tools/mongodb/read/find.test.ts +++ b/tests/integration/tools/mongodb/read/find.test.ts @@ -9,12 +9,7 @@ import { defaultTestConfig, } from "../../../helpers.js"; import * as constants from "../../../../../src/helpers/constants.js"; -import { - defaultTestSuiteConfig, - describeWithMongoDB, - getDocsFromUntrustedContent, - validateAutoConnectBehavior, -} from "../mongodbHelpers.js"; +import { describeWithMongoDB, getDocsFromUntrustedContent, validateAutoConnectBehavior } from "../mongodbHelpers.js"; export async function freshInsertDocuments({ collection, @@ -347,7 +342,6 @@ describeWithMongoDB( }); }, { - ...defaultTestSuiteConfig, getUserConfig: () => ({ ...defaultTestConfig, maxDocumentsPerQuery: 10 }), } ); @@ -400,7 +394,6 @@ describeWithMongoDB( }); }, { - ...defaultTestSuiteConfig, getUserConfig: () => ({ ...defaultTestConfig, maxBytesPerQuery: 100 }), } ); @@ -453,7 +446,6 @@ describeWithMongoDB( }); }, { - ...defaultTestSuiteConfig, getUserConfig: () => ({ ...defaultTestConfig, maxDocumentsPerQuery: -1, maxBytesPerQuery: -1 }), } ); diff --git a/tests/integration/tools/mongodb/search/listSearchIndexes.test.ts b/tests/integration/tools/mongodb/search/listSearchIndexes.test.ts index 9b25623de..fa69fa721 100644 --- a/tests/integration/tools/mongodb/search/listSearchIndexes.test.ts +++ b/tests/integration/tools/mongodb/search/listSearchIndexes.test.ts @@ -1,4 +1,4 @@ -import { defaultTestSuiteConfig, describeWithMongoDB, getSingleDocFromUntrustedContent } from "../mongodbHelpers.js"; +import { describeWithMongoDB, getSingleDocFromUntrustedContent } from "../mongodbHelpers.js"; import { describe, it, expect, beforeEach } from "vitest"; import { getResponseContent, @@ -118,7 +118,6 @@ describeWithMongoDB( }); }, { - ...defaultTestSuiteConfig, downloadOptions: { search: true }, } );