Skip to content

Commit 4c92c52

Browse files
authored
chore: add integration tests for count (#78)
1 parent 939faad commit 4c92c52

File tree

7 files changed

+133
-16
lines changed

7 files changed

+133
-16
lines changed

eslint.config.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,6 @@ export default defineConfig([
3030
// TODO: Configure tests and scripts to work with this.
3131
ignores: ["eslint.config.js", "jest.config.js", "tests/**/*.ts", "scripts/**/*.ts"],
3232
}),
33-
globalIgnores(["node_modules", "dist", "src/common/atlas/openapi.d.ts"]),
33+
globalIgnores(["node_modules", "dist", "src/common/atlas/openapi.d.ts", "coverage"]),
3434
eslintConfigPrettier,
3535
]);

src/tools/mongodb/read/count.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -30,7 +30,7 @@ export class CountTool extends MongoDBToolBase {
3030
return {
3131
content: [
3232
{
33-
text: `Found ${count} documents in the collection \`${collection}\``,
33+
text: `Found ${count} documents in the collection "${collection}"`,
3434
type: "text",
3535
},
3636
],

tests/integration/helpers.ts

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ interface ParameterInfo {
1313
name: string;
1414
type: string;
1515
description: string;
16+
required: boolean;
1617
}
1718

1819
type ToolInfo = Awaited<ReturnType<Client["listTools"]>>["tools"][number];
@@ -180,10 +181,16 @@ export function getParameters(tool: ToolInfo): ParameterInfo[] {
180181
name: key,
181182
type: typedValue.type,
182183
description: typedValue.description,
184+
required: (tool.inputSchema.required as string[])?.includes(key) ?? false,
183185
};
184186
});
185187
}
186188

189+
export const dbOperationParameters: ParameterInfo[] = [
190+
{ name: "database", type: "string", description: "Database name", required: true },
191+
{ name: "collection", type: "string", description: "Collection name", required: true },
192+
];
193+
187194
export function validateParameters(tool: ToolInfo, parameters: ParameterInfo[]): void {
188195
const toolParameters = getParameters(tool);
189196
expect(toolParameters).toHaveLength(parameters.length);

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

Lines changed: 2 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ import {
44
jestTestMCPClient,
55
getResponseContent,
66
validateParameters,
7+
dbOperationParameters,
78
} from "../../../helpers.js";
89
import { toIncludeSameMembers } from "jest-extended";
910
import { McpError } from "@modelcontextprotocol/sdk/types.js";
@@ -21,18 +22,7 @@ describe("createCollection tool", () => {
2122
"Creates a new collection in a database. If the database doesn't exist, it will be created automatically."
2223
);
2324

24-
validateParameters(listCollections, [
25-
{
26-
name: "database",
27-
description: "Database name",
28-
type: "string",
29-
},
30-
{
31-
name: "collection",
32-
description: "Collection name",
33-
type: "string",
34-
},
35-
]);
25+
validateParameters(listCollections, dbOperationParameters);
3626
});
3727

3828
describe("with invalid arguments", () => {

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

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@ describe("Connect tool", () => {
1717
name: "connectionStringOrClusterName",
1818
description: "MongoDB connection string (in the mongodb:// or mongodb+srv:// format) or cluster name",
1919
type: "string",
20+
required: false,
2021
},
2122
]);
2223
});

tests/integration/tools/mongodb/metadata/listCollections.test.ts

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,6 @@ import {
44
jestTestCluster,
55
jestTestMCPClient,
66
getResponseContent,
7-
getParameters,
87
validateParameters,
98
} from "../../../helpers.js";
109
import { toIncludeSameMembers } from "jest-extended";
@@ -20,7 +19,9 @@ describe("listCollections tool", () => {
2019
expect(listCollections).toBeDefined();
2120
expect(listCollections.description).toBe("List all collections for a given database");
2221

23-
validateParameters(listCollections, [{ name: "database", description: "Database name", type: "string" }]);
22+
validateParameters(listCollections, [
23+
{ name: "database", description: "Database name", type: "string", required: true },
24+
]);
2425
});
2526

2627
describe("with invalid arguments", () => {
Lines changed: 118 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,118 @@
1+
import {
2+
connect,
3+
jestTestCluster,
4+
jestTestMCPClient,
5+
getResponseContent,
6+
validateParameters,
7+
dbOperationParameters,
8+
} from "../../../helpers.js";
9+
import { toIncludeSameMembers } from "jest-extended";
10+
import { McpError } from "@modelcontextprotocol/sdk/types.js";
11+
import { ObjectId } from "mongodb";
12+
13+
describe("count tool", () => {
14+
const client = jestTestMCPClient();
15+
const cluster = jestTestCluster();
16+
17+
let randomDbName: string;
18+
beforeEach(() => {
19+
randomDbName = new ObjectId().toString();
20+
});
21+
22+
it("should have correct metadata", async () => {
23+
const { tools } = await client().listTools();
24+
const listCollections = tools.find((tool) => tool.name === "count")!;
25+
expect(listCollections).toBeDefined();
26+
expect(listCollections.description).toBe("Gets the number of documents in a MongoDB collection");
27+
28+
validateParameters(listCollections, [
29+
{
30+
name: "query",
31+
description:
32+
"The query filter to count documents. Matches the syntax of the filter argument of db.collection.count()",
33+
type: "object",
34+
required: false,
35+
},
36+
...dbOperationParameters,
37+
]);
38+
});
39+
40+
describe("with invalid arguments", () => {
41+
const args = [
42+
{},
43+
{ database: 123, collection: "bar" },
44+
{ foo: "bar", database: "test", collection: "bar" },
45+
{ collection: [], database: "test" },
46+
{ collection: "bar", database: "test", query: "{ $gt: { foo: 5 } }" },
47+
];
48+
for (const arg of args) {
49+
it(`throws a schema error for: ${JSON.stringify(arg)}`, async () => {
50+
await connect(client(), cluster());
51+
try {
52+
await client().callTool({ name: "count", arguments: arg });
53+
expect.fail("Expected an error to be thrown");
54+
} catch (error) {
55+
expect(error).toBeInstanceOf(McpError);
56+
const mcpError = error as McpError;
57+
expect(mcpError.code).toEqual(-32602);
58+
expect(mcpError.message).toContain("Invalid arguments for tool count");
59+
}
60+
});
61+
}
62+
});
63+
64+
it("returns 0 when database doesn't exist", async () => {
65+
await connect(client(), cluster());
66+
const response = await client().callTool({
67+
name: "count",
68+
arguments: { database: "non-existent", collection: "foos" },
69+
});
70+
const content = getResponseContent(response.content);
71+
expect(content).toEqual('Found 0 documents in the collection "foos"');
72+
});
73+
74+
it("returns 0 when collection doesn't exist", async () => {
75+
await connect(client(), cluster());
76+
const mongoClient = cluster().getClient();
77+
await mongoClient.db(randomDbName).collection("bar").insertOne({});
78+
const response = await client().callTool({
79+
name: "count",
80+
arguments: { database: randomDbName, collection: "non-existent" },
81+
});
82+
const content = getResponseContent(response.content);
83+
expect(content).toEqual('Found 0 documents in the collection "non-existent"');
84+
});
85+
86+
describe("with existing database", () => {
87+
beforeEach(async () => {
88+
const mongoClient = cluster().getClient();
89+
await mongoClient
90+
.db(randomDbName)
91+
.collection("foo")
92+
.insertMany([
93+
{ name: "Peter", age: 5 },
94+
{ name: "Parker", age: 10 },
95+
{ name: "George", age: 15 },
96+
]);
97+
});
98+
99+
const testCases = [
100+
{ filter: undefined, expectedCount: 3 },
101+
{ filter: {}, expectedCount: 3 },
102+
{ filter: { age: { $lt: 15 } }, expectedCount: 2 },
103+
{ filter: { age: { $gt: 5 }, name: { $regex: "^P" } }, expectedCount: 1 },
104+
];
105+
for (const testCase of testCases) {
106+
it(`returns ${testCase.expectedCount} documents for filter ${JSON.stringify(testCase.filter)}`, async () => {
107+
await connect(client(), cluster());
108+
const response = await client().callTool({
109+
name: "count",
110+
arguments: { database: randomDbName, collection: "foo", query: testCase.filter },
111+
});
112+
113+
const content = getResponseContent(response.content);
114+
expect(content).toEqual(`Found ${testCase.expectedCount} documents in the collection "foo"`);
115+
});
116+
}
117+
});
118+
});

0 commit comments

Comments
 (0)