Skip to content

chore: add integration tests for delete operations #86

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 2 commits into from
Apr 22, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion src/tools/mongodb/delete/deleteMany.ts
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@ export class DeleteManyTool extends MongoDBToolBase {
return {
content: [
{
text: `Deleted \`${result.deletedCount}\` documents from collection \`${collection}\``,
text: `Deleted \`${result.deletedCount}\` document(s) from collection "${collection}"`,
type: "text",
},
],
Expand Down
38 changes: 0 additions & 38 deletions src/tools/mongodb/delete/deleteOne.ts

This file was deleted.

2 changes: 1 addition & 1 deletion src/tools/mongodb/delete/dropCollection.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ export class DropCollectionTool extends MongoDBToolBase {
return {
content: [
{
text: `${result ? "Successfully dropped" : "Failed to drop"} collection \`${collection}\` from database \`${database}\``,
text: `${result ? "Successfully dropped" : "Failed to drop"} collection "${collection}" from database "${database}"`,
type: "text",
},
],
Expand Down
2 changes: 1 addition & 1 deletion src/tools/mongodb/delete/dropDatabase.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ export class DropDatabaseTool extends MongoDBToolBase {
return {
content: [
{
text: `${result.ok ? "Successfully dropped" : "Failed to drop"} database \`${database}\``,
text: `${result.ok ? "Successfully dropped" : "Failed to drop"} database "${database}"`,
type: "text",
},
],
Expand Down
4 changes: 0 additions & 4 deletions src/tools/mongodb/tools.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,12 +7,10 @@ import { CollectionSchemaTool } from "./metadata/collectionSchema.js";
import { FindTool } from "./read/find.js";
import { InsertManyTool } from "./create/insertMany.js";
import { DeleteManyTool } from "./delete/deleteMany.js";
import { DeleteOneTool } from "./delete/deleteOne.js";
import { CollectionStorageSizeTool } from "./metadata/collectionStorageSize.js";
import { CountTool } from "./read/count.js";
import { DbStatsTool } from "./metadata/dbStats.js";
import { AggregateTool } from "./read/aggregate.js";
import { UpdateOneTool } from "./update/updateOne.js";
import { UpdateManyTool } from "./update/updateMany.js";
import { RenameCollectionTool } from "./update/renameCollection.js";
import { DropDatabaseTool } from "./delete/dropDatabase.js";
Expand All @@ -30,12 +28,10 @@ export const MongoDbTools = [
FindTool,
InsertManyTool,
DeleteManyTool,
DeleteOneTool,
CollectionStorageSizeTool,
CountTool,
DbStatsTool,
AggregateTool,
UpdateOneTool,
UpdateManyTool,
RenameCollectionTool,
DropDatabaseTool,
Expand Down
65 changes: 0 additions & 65 deletions src/tools/mongodb/update/updateOne.ts

This file was deleted.

Original file line number Diff line number Diff line change
Expand Up @@ -126,7 +126,7 @@ describe("createCollection tool", () => {
expect(content).toEqual(`Collection "new-collection" created in database "${integration.randomDbName()}".`);
});

it("throw an error if connection string is not configured", async () => {
it("throws an error if connection string is not configured", async () => {
const response = await integration.mcpClient().callTool({
name: "create-collection",
arguments: { database: integration.randomDbName(), collection: "new-collection" },
Expand Down
2 changes: 1 addition & 1 deletion tests/integration/tools/mongodb/create/createIndex.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -233,7 +233,7 @@ describe("createIndex tool", () => {
);
});

it("throw an error if connection string is not configured", async () => {
it("throws an error if connection string is not configured", async () => {
const response = await integration.mcpClient().callTool({
name: "create-index",
arguments: {
Expand Down
2 changes: 1 addition & 1 deletion tests/integration/tools/mongodb/create/insertMany.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -125,7 +125,7 @@ describe("insertMany tool", () => {
expect(content).toContain('Inserted `1` document(s) into collection "coll1"');
});

it("throw an error if connection string is not configured", async () => {
it("throws an error if connection string is not configured", async () => {
const response = await integration.mcpClient().callTool({
name: "insert-many",
arguments: {
Expand Down
191 changes: 191 additions & 0 deletions tests/integration/tools/mongodb/delete/deleteMany.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,191 @@
import {
getResponseContent,
validateParameters,
dbOperationParameters,
setupIntegrationTest,
} from "../../../helpers.js";
import { McpError } from "@modelcontextprotocol/sdk/types.js";
import config from "../../../../../src/config.js";

describe("deleteMany tool", () => {
const integration = setupIntegrationTest();

it("should have correct metadata", async () => {
const { tools } = await integration.mcpClient().listTools();
const deleteMany = tools.find((tool) => tool.name === "delete-many")!;
expect(deleteMany).toBeDefined();
expect(deleteMany.description).toBe("Removes all documents that match the filter from a MongoDB collection");

validateParameters(deleteMany, [
...dbOperationParameters,
{
name: "filter",
type: "object",
description:
"The query filter, specifying the deletion criteria. Matches the syntax of the filter argument of db.collection.deleteMany()",
required: false,
},
]);
});

describe("with invalid arguments", () => {
const args = [
{},
{ collection: "bar", database: 123, filter: {} },
{ collection: [], database: "test", filter: {} },
{ collection: "bar", database: "test", filter: "my-document" },
{ collection: "bar", database: "test", filter: [{ name: "Peter" }] },
];
for (const arg of args) {
it(`throws a schema error for: ${JSON.stringify(arg)}`, async () => {
await integration.connectMcpClient();
try {
await integration.mcpClient().callTool({ name: "delete-many", arguments: arg });
expect.fail("Expected an error to be thrown");
} catch (error) {
expect(error).toBeInstanceOf(McpError);
const mcpError = error as McpError;
expect(mcpError.code).toEqual(-32602);
expect(mcpError.message).toContain("Invalid arguments for tool delete-many");
}
});
}
});

it("doesn't create the collection if it doesn't exist", async () => {
await integration.connectMcpClient();
const response = await integration.mcpClient().callTool({
name: "delete-many",
arguments: {
database: integration.randomDbName(),
collection: "coll1",
filter: {},
},
});

const content = getResponseContent(response.content);
expect(content).toContain('Deleted `0` document(s) from collection "coll1"');

const collections = await integration.mongoClient().db(integration.randomDbName()).listCollections().toArray();
expect(collections).toHaveLength(0);
});

const insertDocuments = async () => {
await integration
.mongoClient()
.db(integration.randomDbName())
.collection("coll1")
.insertMany([
{ age: 10, name: "Peter" },
{ age: 20, name: "John" },
{ age: 30, name: "Mary" },
{ age: 40, name: "Lucy" },
]);
};

const validateDocuments = async (expected: object[]) => {
const documents = await integration
.mongoClient()
.db(integration.randomDbName())
.collection("coll1")
.find()
.toArray();

expect(documents).toHaveLength(expected.length);
for (const expectedDocument of expected) {
expect(documents).toContainEqual(expect.objectContaining(expectedDocument));
}
};

it("deletes documents matching the filter", async () => {
await insertDocuments();

await integration.connectMcpClient();
const response = await integration.mcpClient().callTool({
name: "delete-many",
arguments: {
database: integration.randomDbName(),
collection: "coll1",
filter: { age: { $gt: 20 } },
},
});
const content = getResponseContent(response.content);
expect(content).toContain('Deleted `2` document(s) from collection "coll1"');

await validateDocuments([
{ age: 10, name: "Peter" },
{ age: 20, name: "John" },
]);
});

it("when filter doesn't match, deletes nothing", async () => {
await insertDocuments();
await integration.connectMcpClient();
const response = await integration.mcpClient().callTool({
name: "delete-many",
arguments: {
database: integration.randomDbName(),
collection: "coll1",
filter: { age: { $gt: 100 } },
},
});

const content = getResponseContent(response.content);
expect(content).toContain('Deleted `0` document(s) from collection "coll1"');

await validateDocuments([
{ age: 10, name: "Peter" },
{ age: 20, name: "John" },
{ age: 30, name: "Mary" },
{ age: 40, name: "Lucy" },
]);
});

it("with empty filter, deletes all documents", async () => {
await insertDocuments();
await integration.connectMcpClient();
const response = await integration.mcpClient().callTool({
name: "delete-many",
arguments: {
database: integration.randomDbName(),
collection: "coll1",
filter: {},
},
});

const content = getResponseContent(response.content);
expect(content).toContain('Deleted `4` document(s) from collection "coll1"');

await validateDocuments([]);
});

describe("when not connected", () => {
it("connects automatically if connection string is configured", async () => {
config.connectionString = integration.connectionString();

const response = await integration.mcpClient().callTool({
name: "delete-many",
arguments: {
database: integration.randomDbName(),
collection: "coll1",
filter: {},
},
});
const content = getResponseContent(response.content);
expect(content).toContain('Deleted `0` document(s) from collection "coll1"');
});

it("throws an error if connection string is not configured", async () => {
const response = await integration.mcpClient().callTool({
name: "delete-many",
arguments: {
database: integration.randomDbName(),
collection: "coll1",
filter: {},
},
});
const content = getResponseContent(response.content);
expect(content).toContain("You need to connect to a MongoDB instance before you can access its data.");
});
});
});
Loading
Loading