Skip to content

Commit 09b74ec

Browse files
committed
chore: add tests for find
1 parent b8894ea commit 09b74ec

File tree

2 files changed

+187
-2
lines changed

2 files changed

+187
-2
lines changed

src/tools/mongodb/read/find.ts

+3-2
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ import { CallToolResult } from "@modelcontextprotocol/sdk/types.js";
33
import { DbOperationArgs, MongoDBToolBase } from "../mongodbTool.js";
44
import { ToolArgs, OperationType } from "../../tool.js";
55
import { SortDirection } from "mongodb";
6+
import { EJSON } from "bson";
67

78
export const FindArgs = {
89
filter: z
@@ -44,12 +45,12 @@ export class FindTool extends MongoDBToolBase {
4445

4546
const content: Array<{ text: string; type: "text" }> = [
4647
{
47-
text: `Found ${documents.length} documents in the collection \`${collection}\`:`,
48+
text: `Found ${documents.length} documents in the collection "${collection}":`,
4849
type: "text",
4950
},
5051
...documents.map((doc) => {
5152
return {
52-
text: JSON.stringify(doc),
53+
text: EJSON.stringify(doc),
5354
type: "text",
5455
} as { text: string; type: "text" };
5556
}),
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,184 @@
1+
import {
2+
getResponseContent,
3+
databaseCollectionParameters,
4+
setupIntegrationTest,
5+
validateToolMetadata,
6+
validateAutoConnectBehavior,
7+
validateThrowsForInvalidArguments,
8+
getResponseElements,
9+
} from "../../../helpers.js";
10+
11+
describe("find tool", () => {
12+
const integration = setupIntegrationTest();
13+
14+
validateToolMetadata(integration, "find", "Run a find query against a MongoDB collection", [
15+
...databaseCollectionParameters,
16+
17+
{
18+
name: "filter",
19+
description: "The query filter, matching the syntax of the query argument of db.collection.find()",
20+
type: "object",
21+
required: false,
22+
},
23+
{
24+
name: "projection",
25+
description: "The projection, matching the syntax of the projection argument of db.collection.find()",
26+
type: "object",
27+
required: false,
28+
},
29+
{
30+
name: "limit",
31+
description: "The maximum number of documents to return",
32+
type: "number",
33+
required: false,
34+
},
35+
{
36+
name: "sort",
37+
description:
38+
"A document, describing the sort order, matching the syntax of the sort argument of cursor.sort()",
39+
type: "object",
40+
required: false,
41+
},
42+
]);
43+
44+
validateThrowsForInvalidArguments(integration, "find", [
45+
{},
46+
{ database: 123, collection: "bar" },
47+
{ database: "test", collection: "bar", extra: "extra" },
48+
{ database: "test", collection: [] },
49+
{ database: "test", collection: "bar", filter: "{ $gt: { foo: 5 } }" },
50+
{ database: "test", collection: "bar", projection: "name" },
51+
{ database: "test", collection: "bar", limit: "10" },
52+
{ database: "test", collection: "bar", sort: [], limit: 10 },
53+
]);
54+
55+
it("returns 0 when database doesn't exist", async () => {
56+
await integration.connectMcpClient();
57+
const response = await integration.mcpClient().callTool({
58+
name: "find",
59+
arguments: { database: "non-existent", collection: "foos" },
60+
});
61+
const content = getResponseContent(response.content);
62+
expect(content).toEqual('Found 0 documents in the collection "foos":');
63+
});
64+
65+
it("returns 0 when collection doesn't exist", async () => {
66+
await integration.connectMcpClient();
67+
const mongoClient = integration.mongoClient();
68+
await mongoClient.db(integration.randomDbName()).collection("bar").insertOne({});
69+
const response = await integration.mcpClient().callTool({
70+
name: "find",
71+
arguments: { database: integration.randomDbName(), collection: "non-existent" },
72+
});
73+
const content = getResponseContent(response.content);
74+
expect(content).toEqual('Found 0 documents in the collection "non-existent":');
75+
});
76+
77+
describe("with existing database", () => {
78+
beforeEach(async () => {
79+
const mongoClient = integration.mongoClient();
80+
const items = Array(10)
81+
.fill(0)
82+
.map((_, index) => ({
83+
value: index,
84+
}));
85+
86+
await mongoClient.db(integration.randomDbName()).collection("foo").insertMany(items);
87+
});
88+
89+
const testCases: {
90+
name: string;
91+
filter?: any;
92+
limit?: number;
93+
projection?: any;
94+
sort?: any;
95+
expected: any[];
96+
}[] = [
97+
{
98+
name: "returns all documents when no filter is provided",
99+
expected: Array(10)
100+
.fill(0)
101+
.map((_, index) => ({ _id: expect.any(Object), value: index })),
102+
},
103+
{
104+
name: "returns documents matching the filter",
105+
filter: { value: { $gt: 5 } },
106+
expected: Array(4)
107+
.fill(0)
108+
.map((_, index) => ({ _id: expect.any(Object), value: index + 6 })),
109+
},
110+
{
111+
name: "returns documents matching the filter with projection",
112+
filter: { value: { $gt: 5 } },
113+
projection: { value: 1, _id: 0 },
114+
expected: Array(4)
115+
.fill(0)
116+
.map((_, index) => ({ value: index + 6 })),
117+
},
118+
{
119+
name: "returns documents matching the filter with limit",
120+
filter: { value: { $gt: 5 } },
121+
limit: 2,
122+
expected: [
123+
{ _id: expect.any(Object), value: 6 },
124+
{ _id: expect.any(Object), value: 7 },
125+
],
126+
},
127+
{
128+
name: "returns documents matching the filter with sort",
129+
filter: {},
130+
sort: { value: -1 },
131+
expected: Array(10)
132+
.fill(0)
133+
.map((_, index) => ({ _id: expect.any(Object), value: index }))
134+
.reverse(),
135+
},
136+
];
137+
138+
for (const { name, filter, limit, projection, sort, expected } of testCases) {
139+
it(name, async () => {
140+
await integration.connectMcpClient();
141+
const response = await integration.mcpClient().callTool({
142+
name: "find",
143+
arguments: {
144+
database: integration.randomDbName(),
145+
collection: "foo",
146+
filter,
147+
limit,
148+
projection,
149+
sort,
150+
},
151+
});
152+
const elements = getResponseElements(response.content);
153+
expect(elements).toHaveLength(expected.length + 1);
154+
expect(elements[0].text).toEqual(`Found ${expected.length} documents in the collection "foo":`);
155+
156+
for (let i = 0; i < expected.length; i++) {
157+
expect(JSON.parse(elements[i + 1].text)).toEqual(expected[i]);
158+
}
159+
});
160+
}
161+
162+
it("returns all documents when no filter is provided", async () => {
163+
await integration.connectMcpClient();
164+
const response = await integration.mcpClient().callTool({
165+
name: "find",
166+
arguments: { database: integration.randomDbName(), collection: "foo" },
167+
});
168+
const elements = getResponseElements(response.content);
169+
expect(elements).toHaveLength(11);
170+
expect(elements[0].text).toEqual('Found 10 documents in the collection "foo":');
171+
172+
for (let i = 0; i < 10; i++) {
173+
expect(JSON.parse(elements[i + 1].text).value).toEqual(i);
174+
}
175+
});
176+
});
177+
178+
validateAutoConnectBehavior(integration, "find", () => {
179+
return {
180+
args: { database: integration.randomDbName(), collection: "coll1" },
181+
expectedResponse: 'Found 0 documents in the collection "coll1":',
182+
};
183+
});
184+
});

0 commit comments

Comments
 (0)