Skip to content

Commit eb1dbc7

Browse files
committed
chore: add integration tests for create-index
1 parent 99039bb commit eb1dbc7

File tree

2 files changed

+222
-2
lines changed

2 files changed

+222
-2
lines changed

src/tools/mongodb/create/createIndex.ts

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -10,22 +10,29 @@ export class CreateIndexTool extends MongoDBToolBase {
1010
protected argsShape = {
1111
...DbOperationArgs,
1212
keys: z.record(z.string(), z.custom<IndexDirection>()).describe("The index definition"),
13+
name: z.string().optional().describe("The name of the index"),
1314
};
1415

1516
protected operationType: OperationType = "create";
1617

17-
protected async execute({ database, collection, keys }: ToolArgs<typeof this.argsShape>): Promise<CallToolResult> {
18+
protected async execute({
19+
database,
20+
collection,
21+
keys,
22+
name,
23+
}: ToolArgs<typeof this.argsShape>): Promise<CallToolResult> {
1824
const provider = await this.ensureConnected();
1925
const indexes = await provider.createIndexes(database, collection, [
2026
{
2127
key: keys,
28+
name,
2229
},
2330
]);
2431

2532
return {
2633
content: [
2734
{
28-
text: `Created the index \`${indexes[0]}\``,
35+
text: `Created the index "${indexes[0]}" on collection "${collection}" in database "${database}"`,
2936
type: "text",
3037
},
3138
],
Lines changed: 213 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,213 @@
1+
import {
2+
getResponseContent,
3+
validateParameters,
4+
dbOperationParameters,
5+
setupIntegrationTest,
6+
} from "../../../helpers.js";
7+
import { McpError } from "@modelcontextprotocol/sdk/types.js";
8+
import { ObjectId } from "bson";
9+
import { IndexDirection } from "mongodb";
10+
11+
describe("createIndex tool", () => {
12+
const integration = setupIntegrationTest();
13+
14+
let dbName: string;
15+
beforeEach(() => {
16+
dbName = new ObjectId().toString();
17+
});
18+
19+
it("should have correct metadata", async () => {
20+
const { tools } = await integration.mcpClient().listTools();
21+
const listCollections = tools.find((tool) => tool.name === "create-index")!;
22+
expect(listCollections).toBeDefined();
23+
expect(listCollections.description).toBe("Create an index for a collection");
24+
25+
validateParameters(listCollections, [
26+
...dbOperationParameters,
27+
{
28+
name: "keys",
29+
type: "object",
30+
description: "The index definition",
31+
required: true,
32+
},
33+
{
34+
name: "name",
35+
type: "string",
36+
description: "The name of the index",
37+
required: false,
38+
},
39+
]);
40+
});
41+
42+
describe("with invalid arguments", () => {
43+
const args = [
44+
{},
45+
{ collection: "bar", database: 123, keys: { foo: 1 } },
46+
{ collection: "bar", database: "test", keys: { foo: 5 } },
47+
{ collection: [], database: "test", keys: { foo: 1 } },
48+
{ collection: "bar", database: "test", keys: { foo: 1 }, name: 123 },
49+
{ collection: "bar", database: "test", keys: "foo", name: "my-index" },
50+
];
51+
for (const arg of args) {
52+
it(`throws a schema error for: ${JSON.stringify(arg)}`, async () => {
53+
await integration.connectMcpClient();
54+
try {
55+
await integration.mcpClient().callTool({ name: "create-index", arguments: arg });
56+
expect.fail("Expected an error to be thrown");
57+
} catch (error) {
58+
expect(error).toBeInstanceOf(McpError);
59+
const mcpError = error as McpError;
60+
expect(mcpError.code).toEqual(-32602);
61+
expect(mcpError.message).toContain("Invalid arguments for tool create-index");
62+
}
63+
});
64+
}
65+
});
66+
67+
const validateIndex = async (collection: string, expected: { name: string; key: object }[]) => {
68+
const mongoClient = integration.mongoClient();
69+
const collections = await mongoClient.db(dbName).listCollections().toArray();
70+
expect(collections).toHaveLength(1);
71+
expect(collections[0].name).toEqual("coll1");
72+
const indexes = await mongoClient.db(dbName).collection(collection).indexes();
73+
expect(indexes).toHaveLength(expected.length + 1);
74+
expect(indexes[0].name).toEqual("_id_");
75+
for (const index of expected) {
76+
const foundIndex = indexes.find((i) => i.name === index.name);
77+
expect(foundIndex).toBeDefined();
78+
expect(foundIndex!.key).toEqual(index.key);
79+
}
80+
};
81+
82+
it("creates the namespace if necessary", async () => {
83+
await integration.connectMcpClient();
84+
const response = await integration.mcpClient().callTool({
85+
name: "create-index",
86+
arguments: { database: dbName, collection: "coll1", keys: { prop1: 1 }, name: "my-index" },
87+
});
88+
89+
const content = getResponseContent(response.content);
90+
expect(content).toEqual(`Created the index "my-index" on collection "coll1" in database "${dbName}"`);
91+
92+
await validateIndex("coll1", [{ name: "my-index", key: { prop1: 1 } }]);
93+
});
94+
95+
it("generates a name if not provided", async () => {
96+
await integration.connectMcpClient();
97+
const response = await integration.mcpClient().callTool({
98+
name: "create-index",
99+
arguments: { database: dbName, collection: "coll1", keys: { prop1: 1 } },
100+
});
101+
102+
const content = getResponseContent(response.content);
103+
expect(content).toEqual(`Created the index "prop1_1" on collection "coll1" in database "${dbName}"`);
104+
await validateIndex("coll1", [{ name: "prop1_1", key: { prop1: 1 } }]);
105+
});
106+
107+
it("can create multiple indexes in the same collection", async () => {
108+
await integration.connectMcpClient();
109+
let response = await integration.mcpClient().callTool({
110+
name: "create-index",
111+
arguments: { database: dbName, collection: "coll1", keys: { prop1: 1 } },
112+
});
113+
114+
expect(getResponseContent(response.content)).toEqual(
115+
`Created the index "prop1_1" on collection "coll1" in database "${dbName}"`
116+
);
117+
118+
response = await integration.mcpClient().callTool({
119+
name: "create-index",
120+
arguments: { database: dbName, collection: "coll1", keys: { prop2: -1 } },
121+
});
122+
123+
expect(getResponseContent(response.content)).toEqual(
124+
`Created the index "prop2_-1" on collection "coll1" in database "${dbName}"`
125+
);
126+
127+
await validateIndex("coll1", [
128+
{ name: "prop1_1", key: { prop1: 1 } },
129+
{ name: "prop2_-1", key: { prop2: -1 } },
130+
]);
131+
});
132+
133+
it("can create multiple indexes on the same property", async () => {
134+
await integration.connectMcpClient();
135+
let response = await integration.mcpClient().callTool({
136+
name: "create-index",
137+
arguments: { database: dbName, collection: "coll1", keys: { prop1: 1 } },
138+
});
139+
140+
expect(getResponseContent(response.content)).toEqual(
141+
`Created the index "prop1_1" on collection "coll1" in database "${dbName}"`
142+
);
143+
144+
response = await integration.mcpClient().callTool({
145+
name: "create-index",
146+
arguments: { database: dbName, collection: "coll1", keys: { prop1: -1 } },
147+
});
148+
149+
expect(getResponseContent(response.content)).toEqual(
150+
`Created the index "prop1_-1" on collection "coll1" in database "${dbName}"`
151+
);
152+
153+
await validateIndex("coll1", [
154+
{ name: "prop1_1", key: { prop1: 1 } },
155+
{ name: "prop1_-1", key: { prop1: -1 } },
156+
]);
157+
});
158+
159+
it("doesn't duplicate indexes", async () => {
160+
await integration.connectMcpClient();
161+
let response = await integration.mcpClient().callTool({
162+
name: "create-index",
163+
arguments: { database: dbName, collection: "coll1", keys: { prop1: 1 } },
164+
});
165+
166+
expect(getResponseContent(response.content)).toEqual(
167+
`Created the index "prop1_1" on collection "coll1" in database "${dbName}"`
168+
);
169+
170+
response = await integration.mcpClient().callTool({
171+
name: "create-index",
172+
arguments: { database: dbName, collection: "coll1", keys: { prop1: 1 } },
173+
});
174+
175+
expect(getResponseContent(response.content)).toEqual(
176+
`Created the index "prop1_1" on collection "coll1" in database "${dbName}"`
177+
);
178+
179+
await validateIndex("coll1", [{ name: "prop1_1", key: { prop1: 1 } }]);
180+
});
181+
182+
const testCases: { name: string; direction: IndexDirection }[] = [
183+
{ name: "descending", direction: -1 },
184+
{ name: "ascending", direction: 1 },
185+
{ name: "hashed", direction: "hashed" },
186+
{ name: "text", direction: "text" },
187+
{ name: "geoHaystack", direction: "2dsphere" },
188+
{ name: "geo2d", direction: "2d" },
189+
];
190+
191+
for (const { name, direction } of testCases) {
192+
it(`creates ${name} index`, async () => {
193+
await integration.connectMcpClient();
194+
const response = await integration.mcpClient().callTool({
195+
name: "create-index",
196+
arguments: { database: dbName, collection: "coll1", keys: { prop1: direction } },
197+
});
198+
199+
expect(getResponseContent(response.content)).toEqual(
200+
`Created the index "prop1_${direction}" on collection "coll1" in database "${dbName}"`
201+
);
202+
203+
let expectedKey: object = { prop1: direction };
204+
if (direction === "text") {
205+
expectedKey = {
206+
_fts: "text",
207+
_ftsx: 1,
208+
};
209+
}
210+
await validateIndex("coll1", [{ name: `prop1_${direction}`, key: expectedKey }]);
211+
});
212+
}
213+
});

0 commit comments

Comments
 (0)