Skip to content

Commit eaffdbe

Browse files
committed
chore: add integration tests for list-databases
1 parent 5b6c803 commit eaffdbe

File tree

10 files changed

+155
-43
lines changed

10 files changed

+155
-43
lines changed

jest.config.js

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,4 +16,5 @@ export default {
1616
],
1717
},
1818
moduleFileExtensions: ["ts", "tsx", "js", "jsx", "json", "node"],
19+
setupFilesAfterEnv: ["jest-extended/all"],
1920
};

package-lock.json

Lines changed: 23 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

package.json

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -45,6 +45,7 @@
4545
"globals": "^16.0.0",
4646
"jest": "^29.7.0",
4747
"jest-environment-node": "^29.7.0",
48+
"jest-extended": "^4.0.2",
4849
"mongodb-runner": "^5.8.2",
4950
"openapi-types": "^12.1.3",
5051
"openapi-typescript": "^7.6.1",
@@ -55,9 +56,9 @@
5556
"typescript-eslint": "^8.29.1"
5657
},
5758
"dependencies": {
59+
"@modelcontextprotocol/sdk": "^1.8.0",
5860
"@mongodb-js/devtools-connect": "^3.7.2",
5961
"@mongosh/service-provider-node-driver": "^3.6.0",
60-
"@modelcontextprotocol/sdk": "^1.8.0",
6162
"bson": "^6.10.3",
6263
"mongodb": "^6.15.0",
6364
"mongodb-log-writer": "^2.4.1",

src/tools/mongodb/createIndex.ts renamed to src/tools/mongodb/create/createIndex.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
import { z } from "zod";
22
import { CallToolResult } from "@modelcontextprotocol/sdk/types.js";
3-
import { DbOperationArgs, DbOperationType, MongoDBToolBase } from "./mongodbTool.js";
4-
import { ToolArgs } from "../tool.js";
3+
import { DbOperationArgs, DbOperationType, MongoDBToolBase } from "../mongodbTool.js";
4+
import { ToolArgs } from "../../tool.js";
55
import { IndexDirection } from "mongodb";
66

77
export class CreateIndexTool extends MongoDBToolBase {

src/tools/mongodb/connect.ts renamed to src/tools/mongodb/metadata/connect.ts

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,9 @@
11
import { z } from "zod";
22
import { CallToolResult } from "@modelcontextprotocol/sdk/types.js";
3-
import { DbOperationType, MongoDBToolBase } from "./mongodbTool.js";
4-
import { ToolArgs } from "../tool.js";
5-
import { ErrorCodes, MongoDBError } from "../../errors.js";
6-
import config from "../../config.js";
3+
import { DbOperationType, MongoDBToolBase } from "../mongodbTool.js";
4+
import { ToolArgs } from "../../tool.js";
5+
import { ErrorCodes, MongoDBError } from "../../../errors.js";
6+
import config from "../../../config.js";
77

88
export class ConnectTool extends MongoDBToolBase {
99
protected name = "connect";

src/tools/mongodb/collectionIndexes.ts renamed to src/tools/mongodb/read/collectionIndexes.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
import { CallToolResult } from "@modelcontextprotocol/sdk/types.js";
2-
import { DbOperationArgs, DbOperationType, MongoDBToolBase } from "./mongodbTool.js";
3-
import { ToolArgs } from "../tool.js";
2+
import { DbOperationArgs, DbOperationType, MongoDBToolBase } from "../mongodbTool.js";
3+
import { ToolArgs } from "../../tool.js";
44

55
export class CollectionIndexesTool extends MongoDBToolBase {
66
protected name = "collection-indexes";

src/tools/mongodb/tools.ts

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,8 @@
1-
import { ConnectTool } from "./connect.js";
1+
import { ConnectTool } from "./metadata/connect.js";
22
import { ListCollectionsTool } from "./metadata/listCollections.js";
3-
import { CollectionIndexesTool } from "./collectionIndexes.js";
3+
import { CollectionIndexesTool } from "./read/collectionIndexes.js";
44
import { ListDatabasesTool } from "./metadata/listDatabases.js";
5-
import { CreateIndexTool } from "./createIndex.js";
5+
import { CreateIndexTool } from "./create/createIndex.js";
66
import { CollectionSchemaTool } from "./metadata/collectionSchema.js";
77
import { InsertOneTool } from "./create/insertOne.js";
88
import { FindTool } from "./read/find.js";

tests/integration/helpers.ts

Lines changed: 16 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -68,15 +68,28 @@ export async function runMongoDB(): Promise<runner.MongoCluster> {
6868
}
6969
}
7070

71-
export function validateToolResponse(content: unknown): string {
71+
export function getResponseContent(content: unknown): string {
72+
return getResponseElements(content)
73+
.map((item) => item.text)
74+
.join("\n");
75+
}
76+
77+
export function getResponseElements(content: unknown): { type: string; text: string }[] {
7278
expect(Array.isArray(content)).toBe(true);
7379

74-
const response = content as Array<{ type: string; text: string }>;
80+
const response = content as { type: string; text: string }[];
7581
for (const item of response) {
7682
expect(item).toHaveProperty("type");
7783
expect(item).toHaveProperty("text");
7884
expect(item.type).toBe("text");
7985
}
8086

81-
return response.map((item) => item.text).join("\n");
87+
return response;
88+
}
89+
90+
export async function connect(client: Client, cluster: runner.MongoCluster): Promise<void> {
91+
await client.callTool({
92+
name: "connect",
93+
arguments: { connectionStringOrClusterName: cluster.connectionString },
94+
});
8295
}

tests/integration/tools/mongodb/connect.test.ts renamed to tests/integration/tools/mongodb/metadata/connect.test.ts

Lines changed: 28 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,8 @@
11
import { Client } from "@modelcontextprotocol/sdk/client/index.js";
2-
import { runMongoDB, setupIntegrationTest, validateToolResponse } from "../../helpers.js";
2+
import { runMongoDB, setupIntegrationTest, getResponseContent } from "../../../helpers.js";
33
import runner from "mongodb-runner";
44

5-
import config from "../../../../src/config.js";
5+
import config from "../../../../../src/config.js";
66

77
describe("Connect tool", () => {
88
let client: Client;
@@ -26,32 +26,32 @@ describe("Connect tool", () => {
2626
await cluster.close();
2727
});
2828

29-
describe("with default config", () => {
30-
it("should have correct metadata", async () => {
31-
const tools = await client.listTools();
32-
const connectTool = tools.tools.find((tool) => tool.name === "connect");
33-
expect(connectTool).toBeDefined();
34-
expect(connectTool!.description).toBe("Connect to a MongoDB instance");
35-
expect(connectTool!.inputSchema.type).toBe("object");
36-
expect(connectTool!.inputSchema.properties).toBeDefined();
37-
38-
const propertyNames = Object.keys(connectTool!.inputSchema.properties!);
39-
expect(propertyNames).toHaveLength(1);
40-
expect(propertyNames[0]).toBe("connectionStringOrClusterName");
41-
42-
const connectionStringOrClusterNameProp = connectTool!.inputSchema.properties![propertyNames[0]] as {
43-
type: string;
44-
description: string;
45-
};
46-
expect(connectionStringOrClusterNameProp.type).toBe("string");
47-
expect(connectionStringOrClusterNameProp.description).toContain("MongoDB connection string");
48-
expect(connectionStringOrClusterNameProp.description).toContain("cluster name");
49-
});
29+
it("should have correct metadata", async () => {
30+
const tools = await client.listTools();
31+
const connectTool = tools.tools.find((tool) => tool.name === "connect");
32+
expect(connectTool).toBeDefined();
33+
expect(connectTool!.description).toBe("Connect to a MongoDB instance");
34+
expect(connectTool!.inputSchema.type).toBe("object");
35+
expect(connectTool!.inputSchema.properties).toBeDefined();
36+
37+
const propertyNames = Object.keys(connectTool!.inputSchema.properties!);
38+
expect(propertyNames).toHaveLength(1);
39+
expect(propertyNames[0]).toBe("connectionStringOrClusterName");
40+
41+
const connectionStringOrClusterNameProp = connectTool!.inputSchema.properties![propertyNames[0]] as {
42+
type: string;
43+
description: string;
44+
};
45+
expect(connectionStringOrClusterNameProp.type).toBe("string");
46+
expect(connectionStringOrClusterNameProp.description).toContain("MongoDB connection string");
47+
expect(connectionStringOrClusterNameProp.description).toContain("cluster name");
48+
});
5049

50+
describe("with default config", () => {
5151
describe("without connection string", () => {
5252
it("prompts for connection string", async () => {
5353
const response = await client.callTool({ name: "connect", arguments: {} });
54-
const content = validateToolResponse(response.content);
54+
const content = getResponseContent(response.content);
5555
expect(content).toContain("No connection details provided");
5656
expect(content).toContain("mongodb://localhost:27017");
5757
});
@@ -63,7 +63,7 @@ describe("Connect tool", () => {
6363
name: "connect",
6464
arguments: { connectionStringOrClusterName: cluster.connectionString },
6565
});
66-
const content = validateToolResponse(response.content);
66+
const content = getResponseContent(response.content);
6767
expect(content).toContain("Successfully connected");
6868
expect(content).toContain(cluster.connectionString);
6969
});
@@ -75,7 +75,7 @@ describe("Connect tool", () => {
7575
name: "connect",
7676
arguments: { connectionStringOrClusterName: "mongodb://localhost:12345" },
7777
});
78-
const content = validateToolResponse(response.content);
78+
const content = getResponseContent(response.content);
7979
expect(content).toContain("Error running connect");
8080
});
8181
});
@@ -88,7 +88,7 @@ describe("Connect tool", () => {
8888

8989
it("uses the connection string from config", async () => {
9090
const response = await client.callTool({ name: "connect", arguments: {} });
91-
const content = validateToolResponse(response.content);
91+
const content = getResponseContent(response.content);
9292
expect(content).toContain("Successfully connected");
9393
expect(content).toContain(cluster.connectionString);
9494
});
@@ -99,7 +99,7 @@ describe("Connect tool", () => {
9999
name: "connect",
100100
arguments: { connectionStringOrClusterName: newConnectionString },
101101
});
102-
const content = validateToolResponse(response.content);
102+
const content = getResponseContent(response.content);
103103
expect(content).toContain("Successfully connected");
104104
expect(content).toContain(newConnectionString);
105105
});
Lines changed: 74 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,74 @@
1+
import { Client } from "@modelcontextprotocol/sdk/client/index.js";
2+
import { runMongoDB, setupIntegrationTest, getResponseElements, connect } from "../../../helpers.js";
3+
import runner from "mongodb-runner";
4+
import { MongoClient } from "mongodb";
5+
import { toIncludeSameMembers } from "jest-extended";
6+
7+
describe("listDatabases tool", () => {
8+
let client: Client;
9+
let serverClientTeardown: () => Promise<void>;
10+
11+
let cluster: runner.MongoCluster;
12+
13+
beforeAll(async () => {
14+
cluster = await runMongoDB();
15+
}, 60_000);
16+
17+
beforeEach(async () => {
18+
({ client, teardown: serverClientTeardown } = await setupIntegrationTest());
19+
});
20+
21+
afterEach(async () => {
22+
await serverClientTeardown?.();
23+
});
24+
25+
afterAll(async () => {
26+
await cluster.close();
27+
});
28+
29+
it("should have correct metadata", async () => {
30+
const tools = await client.listTools();
31+
const listDatabases = tools.tools.find((tool) => tool.name === "list-databases");
32+
expect(listDatabases).toBeDefined();
33+
expect(listDatabases!.description).toBe("List all databases for a MongoDB connection");
34+
expect(listDatabases!.inputSchema.type).toBe("object");
35+
expect(listDatabases!.inputSchema.properties).toBeDefined();
36+
37+
const propertyNames = Object.keys(listDatabases!.inputSchema.properties!);
38+
expect(propertyNames).toHaveLength(0);
39+
});
40+
41+
describe("with no preexisting databases", () => {
42+
it("returns only the system databases", async () => {
43+
await connect(client, cluster);
44+
const response = await client.callTool({ name: "list-databases", arguments: {} });
45+
const dbNames = getDbNames(response.content);
46+
47+
expect(dbNames).toIncludeSameMembers(["admin", "config", "local"]);
48+
});
49+
});
50+
51+
describe("with preexisting databases", () => {
52+
it("returns their names and sizes", async () => {
53+
const mongoClient = new MongoClient(cluster.connectionString);
54+
await mongoClient.db("foo").collection("bar").insertOne({ test: "test" });
55+
await mongoClient.db("baz").collection("qux").insertOne({ test: "test" });
56+
await mongoClient.close();
57+
58+
await connect(client, cluster);
59+
60+
const response = await client.callTool({ name: "list-databases", arguments: {} });
61+
const dbNames = getDbNames(response.content);
62+
expect(dbNames).toIncludeSameMembers(["admin", "config", "local", "foo", "baz"]);
63+
});
64+
});
65+
});
66+
67+
function getDbNames(content: unknown): (string | null)[] {
68+
const responseItems = getResponseElements(content);
69+
70+
return responseItems.map((item) => {
71+
const match = item.text.match(/Name: (.*), Size: \d+ bytes/);
72+
return match ? match[1] : null;
73+
});
74+
}

0 commit comments

Comments
 (0)