Skip to content

Commit 8da49a1

Browse files
authored
chore: add integration tests for delete operations (#86)
1 parent d7021bd commit 8da49a1

15 files changed

+432
-116
lines changed

src/tools/mongodb/delete/deleteMany.ts

+1-1
Original file line numberDiff line numberDiff line change
@@ -29,7 +29,7 @@ export class DeleteManyTool extends MongoDBToolBase {
2929
return {
3030
content: [
3131
{
32-
text: `Deleted \`${result.deletedCount}\` documents from collection \`${collection}\``,
32+
text: `Deleted \`${result.deletedCount}\` document(s) from collection "${collection}"`,
3333
type: "text",
3434
},
3535
],

src/tools/mongodb/delete/deleteOne.ts

-38
This file was deleted.

src/tools/mongodb/delete/dropCollection.ts

+1-1
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@ export class DropCollectionTool extends MongoDBToolBase {
1818
return {
1919
content: [
2020
{
21-
text: `${result ? "Successfully dropped" : "Failed to drop"} collection \`${collection}\` from database \`${database}\``,
21+
text: `${result ? "Successfully dropped" : "Failed to drop"} collection "${collection}" from database "${database}"`,
2222
type: "text",
2323
},
2424
],

src/tools/mongodb/delete/dropDatabase.ts

+1-1
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@ export class DropDatabaseTool extends MongoDBToolBase {
1717
return {
1818
content: [
1919
{
20-
text: `${result.ok ? "Successfully dropped" : "Failed to drop"} database \`${database}\``,
20+
text: `${result.ok ? "Successfully dropped" : "Failed to drop"} database "${database}"`,
2121
type: "text",
2222
},
2323
],

src/tools/mongodb/tools.ts

-4
Original file line numberDiff line numberDiff line change
@@ -7,12 +7,10 @@ import { CollectionSchemaTool } from "./metadata/collectionSchema.js";
77
import { FindTool } from "./read/find.js";
88
import { InsertManyTool } from "./create/insertMany.js";
99
import { DeleteManyTool } from "./delete/deleteMany.js";
10-
import { DeleteOneTool } from "./delete/deleteOne.js";
1110
import { CollectionStorageSizeTool } from "./metadata/collectionStorageSize.js";
1211
import { CountTool } from "./read/count.js";
1312
import { DbStatsTool } from "./metadata/dbStats.js";
1413
import { AggregateTool } from "./read/aggregate.js";
15-
import { UpdateOneTool } from "./update/updateOne.js";
1614
import { UpdateManyTool } from "./update/updateMany.js";
1715
import { RenameCollectionTool } from "./update/renameCollection.js";
1816
import { DropDatabaseTool } from "./delete/dropDatabase.js";
@@ -30,12 +28,10 @@ export const MongoDbTools = [
3028
FindTool,
3129
InsertManyTool,
3230
DeleteManyTool,
33-
DeleteOneTool,
3431
CollectionStorageSizeTool,
3532
CountTool,
3633
DbStatsTool,
3734
AggregateTool,
38-
UpdateOneTool,
3935
UpdateManyTool,
4036
RenameCollectionTool,
4137
DropDatabaseTool,

src/tools/mongodb/update/updateOne.ts

-65
This file was deleted.

tests/integration/tools/mongodb/create/createCollection.test.ts

+1-1
Original file line numberDiff line numberDiff line change
@@ -126,7 +126,7 @@ describe("createCollection tool", () => {
126126
expect(content).toEqual(`Collection "new-collection" created in database "${integration.randomDbName()}".`);
127127
});
128128

129-
it("throw an error if connection string is not configured", async () => {
129+
it("throws an error if connection string is not configured", async () => {
130130
const response = await integration.mcpClient().callTool({
131131
name: "create-collection",
132132
arguments: { database: integration.randomDbName(), collection: "new-collection" },

tests/integration/tools/mongodb/create/createIndex.test.ts

+1-1
Original file line numberDiff line numberDiff line change
@@ -233,7 +233,7 @@ describe("createIndex tool", () => {
233233
);
234234
});
235235

236-
it("throw an error if connection string is not configured", async () => {
236+
it("throws an error if connection string is not configured", async () => {
237237
const response = await integration.mcpClient().callTool({
238238
name: "create-index",
239239
arguments: {

tests/integration/tools/mongodb/create/insertMany.test.ts

+1-1
Original file line numberDiff line numberDiff line change
@@ -125,7 +125,7 @@ describe("insertMany tool", () => {
125125
expect(content).toContain('Inserted `1` document(s) into collection "coll1"');
126126
});
127127

128-
it("throw an error if connection string is not configured", async () => {
128+
it("throws an error if connection string is not configured", async () => {
129129
const response = await integration.mcpClient().callTool({
130130
name: "insert-many",
131131
arguments: {
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,191 @@
1+
import {
2+
getResponseContent,
3+
validateParameters,
4+
dbOperationParameters,
5+
setupIntegrationTest,
6+
} from "../../../helpers.js";
7+
import { McpError } from "@modelcontextprotocol/sdk/types.js";
8+
import config from "../../../../../src/config.js";
9+
10+
describe("deleteMany tool", () => {
11+
const integration = setupIntegrationTest();
12+
13+
it("should have correct metadata", async () => {
14+
const { tools } = await integration.mcpClient().listTools();
15+
const deleteMany = tools.find((tool) => tool.name === "delete-many")!;
16+
expect(deleteMany).toBeDefined();
17+
expect(deleteMany.description).toBe("Removes all documents that match the filter from a MongoDB collection");
18+
19+
validateParameters(deleteMany, [
20+
...dbOperationParameters,
21+
{
22+
name: "filter",
23+
type: "object",
24+
description:
25+
"The query filter, specifying the deletion criteria. Matches the syntax of the filter argument of db.collection.deleteMany()",
26+
required: false,
27+
},
28+
]);
29+
});
30+
31+
describe("with invalid arguments", () => {
32+
const args = [
33+
{},
34+
{ collection: "bar", database: 123, filter: {} },
35+
{ collection: [], database: "test", filter: {} },
36+
{ collection: "bar", database: "test", filter: "my-document" },
37+
{ collection: "bar", database: "test", filter: [{ name: "Peter" }] },
38+
];
39+
for (const arg of args) {
40+
it(`throws a schema error for: ${JSON.stringify(arg)}`, async () => {
41+
await integration.connectMcpClient();
42+
try {
43+
await integration.mcpClient().callTool({ name: "delete-many", arguments: arg });
44+
expect.fail("Expected an error to be thrown");
45+
} catch (error) {
46+
expect(error).toBeInstanceOf(McpError);
47+
const mcpError = error as McpError;
48+
expect(mcpError.code).toEqual(-32602);
49+
expect(mcpError.message).toContain("Invalid arguments for tool delete-many");
50+
}
51+
});
52+
}
53+
});
54+
55+
it("doesn't create the collection if it doesn't exist", async () => {
56+
await integration.connectMcpClient();
57+
const response = await integration.mcpClient().callTool({
58+
name: "delete-many",
59+
arguments: {
60+
database: integration.randomDbName(),
61+
collection: "coll1",
62+
filter: {},
63+
},
64+
});
65+
66+
const content = getResponseContent(response.content);
67+
expect(content).toContain('Deleted `0` document(s) from collection "coll1"');
68+
69+
const collections = await integration.mongoClient().db(integration.randomDbName()).listCollections().toArray();
70+
expect(collections).toHaveLength(0);
71+
});
72+
73+
const insertDocuments = async () => {
74+
await integration
75+
.mongoClient()
76+
.db(integration.randomDbName())
77+
.collection("coll1")
78+
.insertMany([
79+
{ age: 10, name: "Peter" },
80+
{ age: 20, name: "John" },
81+
{ age: 30, name: "Mary" },
82+
{ age: 40, name: "Lucy" },
83+
]);
84+
};
85+
86+
const validateDocuments = async (expected: object[]) => {
87+
const documents = await integration
88+
.mongoClient()
89+
.db(integration.randomDbName())
90+
.collection("coll1")
91+
.find()
92+
.toArray();
93+
94+
expect(documents).toHaveLength(expected.length);
95+
for (const expectedDocument of expected) {
96+
expect(documents).toContainEqual(expect.objectContaining(expectedDocument));
97+
}
98+
};
99+
100+
it("deletes documents matching the filter", async () => {
101+
await insertDocuments();
102+
103+
await integration.connectMcpClient();
104+
const response = await integration.mcpClient().callTool({
105+
name: "delete-many",
106+
arguments: {
107+
database: integration.randomDbName(),
108+
collection: "coll1",
109+
filter: { age: { $gt: 20 } },
110+
},
111+
});
112+
const content = getResponseContent(response.content);
113+
expect(content).toContain('Deleted `2` document(s) from collection "coll1"');
114+
115+
await validateDocuments([
116+
{ age: 10, name: "Peter" },
117+
{ age: 20, name: "John" },
118+
]);
119+
});
120+
121+
it("when filter doesn't match, deletes nothing", async () => {
122+
await insertDocuments();
123+
await integration.connectMcpClient();
124+
const response = await integration.mcpClient().callTool({
125+
name: "delete-many",
126+
arguments: {
127+
database: integration.randomDbName(),
128+
collection: "coll1",
129+
filter: { age: { $gt: 100 } },
130+
},
131+
});
132+
133+
const content = getResponseContent(response.content);
134+
expect(content).toContain('Deleted `0` document(s) from collection "coll1"');
135+
136+
await validateDocuments([
137+
{ age: 10, name: "Peter" },
138+
{ age: 20, name: "John" },
139+
{ age: 30, name: "Mary" },
140+
{ age: 40, name: "Lucy" },
141+
]);
142+
});
143+
144+
it("with empty filter, deletes all documents", async () => {
145+
await insertDocuments();
146+
await integration.connectMcpClient();
147+
const response = await integration.mcpClient().callTool({
148+
name: "delete-many",
149+
arguments: {
150+
database: integration.randomDbName(),
151+
collection: "coll1",
152+
filter: {},
153+
},
154+
});
155+
156+
const content = getResponseContent(response.content);
157+
expect(content).toContain('Deleted `4` document(s) from collection "coll1"');
158+
159+
await validateDocuments([]);
160+
});
161+
162+
describe("when not connected", () => {
163+
it("connects automatically if connection string is configured", async () => {
164+
config.connectionString = integration.connectionString();
165+
166+
const response = await integration.mcpClient().callTool({
167+
name: "delete-many",
168+
arguments: {
169+
database: integration.randomDbName(),
170+
collection: "coll1",
171+
filter: {},
172+
},
173+
});
174+
const content = getResponseContent(response.content);
175+
expect(content).toContain('Deleted `0` document(s) from collection "coll1"');
176+
});
177+
178+
it("throws an error if connection string is not configured", async () => {
179+
const response = await integration.mcpClient().callTool({
180+
name: "delete-many",
181+
arguments: {
182+
database: integration.randomDbName(),
183+
collection: "coll1",
184+
filter: {},
185+
},
186+
});
187+
const content = getResponseContent(response.content);
188+
expect(content).toContain("You need to connect to a MongoDB instance before you can access its data.");
189+
});
190+
});
191+
});

0 commit comments

Comments
 (0)