Skip to content

Commit 673fca7

Browse files
authored
chore: add tests for db-stats (#99)
1 parent 55618a6 commit 673fca7

14 files changed

+158
-39
lines changed

src/tools/mongodb/metadata/dbStats.ts

+6-1
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
import { CallToolResult } from "@modelcontextprotocol/sdk/types.js";
22
import { DbOperationArgs, MongoDBToolBase } from "../mongodbTool.js";
33
import { ToolArgs, OperationType } from "../../tool.js";
4+
import { EJSON } from "bson";
45

56
export class DbStatsTool extends MongoDBToolBase {
67
protected name = "db-stats";
@@ -21,7 +22,11 @@ export class DbStatsTool extends MongoDBToolBase {
2122
return {
2223
content: [
2324
{
24-
text: `Statistics for database ${database}: ${JSON.stringify(result)}`,
25+
text: `Statistics for database ${database}`,
26+
type: "text",
27+
},
28+
{
29+
text: EJSON.stringify(result),
2530
type: "text",
2631
},
2732
],

tests/integration/helpers.ts

+17-2
Original file line numberDiff line numberDiff line change
@@ -149,12 +149,27 @@ export function getParameters(tool: ToolInfo): ParameterInfo[] {
149149
});
150150
}
151151

152-
export const dbOperationParameters: ParameterInfo[] = [
152+
export const databaseParameters: ParameterInfo[] = [
153153
{ name: "database", type: "string", description: "Database name", required: true },
154+
];
155+
156+
export const databaseCollectionParameters: ParameterInfo[] = [
157+
...databaseParameters,
154158
{ name: "collection", type: "string", description: "Collection name", required: true },
155159
];
156160

157-
export const dbOperationInvalidArgTests = [{}, { database: 123 }, { foo: "bar", database: "test" }, { database: [] }];
161+
export const databaseCollectionInvalidArgs = [
162+
{},
163+
{ database: "test" },
164+
{ collection: "foo" },
165+
{ database: 123, collection: "foo" },
166+
{ database: "test", collection: "foo", extra: "bar" },
167+
{ database: "test", collection: 123 },
168+
{ database: [], collection: "foo" },
169+
{ database: "test", collection: [] },
170+
];
171+
172+
export const databaseInvalidArgs = [{}, { database: 123 }, { database: [] }, { database: "test", extra: "bar" }];
158173

159174
export function validateToolMetadata(
160175
integration: IntegrationTest,

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

+4-4
Original file line numberDiff line numberDiff line change
@@ -2,21 +2,21 @@ import { describeWithMongoDB, validateAutoConnectBehavior } from "../mongodbHelp
22

33
import {
44
getResponseContent,
5-
dbOperationParameters,
5+
databaseCollectionParameters,
66
validateToolMetadata,
77
validateThrowsForInvalidArguments,
8-
dbOperationInvalidArgTests,
8+
databaseCollectionInvalidArgs,
99
} from "../../../helpers.js";
1010

1111
describeWithMongoDB("createCollection tool", (integration) => {
1212
validateToolMetadata(
1313
integration,
1414
"create-collection",
1515
"Creates a new collection in a database. If the database doesn't exist, it will be created automatically.",
16-
dbOperationParameters
16+
databaseCollectionParameters
1717
);
1818

19-
validateThrowsForInvalidArguments(integration, "create-collection", dbOperationInvalidArgTests);
19+
validateThrowsForInvalidArguments(integration, "create-collection", databaseCollectionInvalidArgs);
2020

2121
describe("with non-existent database", () => {
2222
it("creates a new collection", async () => {

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

+2-2
Original file line numberDiff line numberDiff line change
@@ -2,15 +2,15 @@ import { describeWithMongoDB, validateAutoConnectBehavior } from "../mongodbHelp
22

33
import {
44
getResponseContent,
5-
dbOperationParameters,
5+
databaseCollectionParameters,
66
validateToolMetadata,
77
validateThrowsForInvalidArguments,
88
} from "../../../helpers.js";
99
import { IndexDirection } from "mongodb";
1010

1111
describeWithMongoDB("createIndex tool", (integration) => {
1212
validateToolMetadata(integration, "create-index", "Create an index for a collection", [
13-
...dbOperationParameters,
13+
...databaseCollectionParameters,
1414
{
1515
name: "keys",
1616
type: "object",

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

+2-2
Original file line numberDiff line numberDiff line change
@@ -2,14 +2,14 @@ import { describeWithMongoDB, validateAutoConnectBehavior } from "../mongodbHelp
22

33
import {
44
getResponseContent,
5-
dbOperationParameters,
5+
databaseCollectionParameters,
66
validateToolMetadata,
77
validateThrowsForInvalidArguments,
88
} from "../../../helpers.js";
99

1010
describeWithMongoDB("insertMany tool", (integration) => {
1111
validateToolMetadata(integration, "insert-many", "Insert an array of documents into a MongoDB collection", [
12-
...dbOperationParameters,
12+
...databaseCollectionParameters,
1313
{
1414
name: "documents",
1515
type: "array",

tests/integration/tools/mongodb/delete/deleteMany.test.ts

+2-2
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@ import { describeWithMongoDB, validateAutoConnectBehavior } from "../mongodbHelp
22

33
import {
44
getResponseContent,
5-
dbOperationParameters,
5+
databaseCollectionParameters,
66
validateToolMetadata,
77
validateThrowsForInvalidArguments,
88
} from "../../../helpers.js";
@@ -13,7 +13,7 @@ describeWithMongoDB("deleteMany tool", (integration) => {
1313
"delete-many",
1414
"Removes all documents that match the filter from a MongoDB collection",
1515
[
16-
...dbOperationParameters,
16+
...databaseCollectionParameters,
1717
{
1818
name: "filter",
1919
type: "object",

tests/integration/tools/mongodb/delete/dropCollection.test.ts

+4-4
Original file line numberDiff line numberDiff line change
@@ -2,21 +2,21 @@ import { describeWithMongoDB, validateAutoConnectBehavior } from "../mongodbHelp
22

33
import {
44
getResponseContent,
5-
dbOperationParameters,
5+
databaseCollectionParameters,
66
validateToolMetadata,
77
validateThrowsForInvalidArguments,
8-
dbOperationInvalidArgTests,
8+
databaseCollectionInvalidArgs,
99
} from "../../../helpers.js";
1010

1111
describeWithMongoDB("dropCollection tool", (integration) => {
1212
validateToolMetadata(
1313
integration,
1414
"drop-collection",
1515
"Removes a collection or view from the database. The method also removes any indexes associated with the dropped collection.",
16-
dbOperationParameters
16+
databaseCollectionParameters
1717
);
1818

19-
validateThrowsForInvalidArguments(integration, "drop-collection", dbOperationInvalidArgTests);
19+
validateThrowsForInvalidArguments(integration, "drop-collection", databaseCollectionInvalidArgs);
2020

2121
it("can drop non-existing collection", async () => {
2222
await integration.connectMcpClient();

tests/integration/tools/mongodb/delete/dropDatabase.test.ts

+4-4
Original file line numberDiff line numberDiff line change
@@ -2,21 +2,21 @@ import { describeWithMongoDB, validateAutoConnectBehavior } from "../mongodbHelp
22

33
import {
44
getResponseContent,
5-
dbOperationParameters,
65
validateToolMetadata,
76
validateThrowsForInvalidArguments,
8-
dbOperationInvalidArgTests,
7+
databaseParameters,
8+
databaseInvalidArgs,
99
} from "../../../helpers.js";
1010

1111
describeWithMongoDB("dropDatabase tool", (integration) => {
1212
validateToolMetadata(
1313
integration,
1414
"drop-database",
1515
"Removes the specified database, deleting the associated data files",
16-
[dbOperationParameters.find((d) => d.name === "database")!]
16+
databaseParameters
1717
);
1818

19-
validateThrowsForInvalidArguments(integration, "drop-database", dbOperationInvalidArgTests);
19+
validateThrowsForInvalidArguments(integration, "drop-database", databaseInvalidArgs);
2020

2121
it("can drop non-existing database", async () => {
2222
let { databases } = await integration.mongoClient().db("").admin().listDatabases();

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

+4-4
Original file line numberDiff line numberDiff line change
@@ -3,10 +3,10 @@ import { describeWithMongoDB, validateAutoConnectBehavior } from "../mongodbHelp
33
import {
44
getResponseElements,
55
getResponseContent,
6-
dbOperationParameters,
6+
databaseCollectionParameters,
77
validateToolMetadata,
88
validateThrowsForInvalidArguments,
9-
dbOperationInvalidArgTests,
9+
databaseCollectionInvalidArgs,
1010
} from "../../../helpers.js";
1111
import { Document } from "bson";
1212
import { OptionalId } from "mongodb";
@@ -17,10 +17,10 @@ describeWithMongoDB("collectionSchema tool", (integration) => {
1717
integration,
1818
"collection-schema",
1919
"Describe the schema for a collection",
20-
dbOperationParameters
20+
databaseCollectionParameters
2121
);
2222

23-
validateThrowsForInvalidArguments(integration, "collection-schema", dbOperationInvalidArgTests);
23+
validateThrowsForInvalidArguments(integration, "collection-schema", databaseCollectionInvalidArgs);
2424

2525
describe("with non-existent database", () => {
2626
it("returns empty schema", async () => {

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

+5-5
Original file line numberDiff line numberDiff line change
@@ -2,9 +2,9 @@ import { describeWithMongoDB, validateAutoConnectBehavior } from "../mongodbHelp
22

33
import {
44
getResponseContent,
5-
dbOperationParameters,
5+
databaseCollectionParameters,
6+
databaseCollectionInvalidArgs,
67
validateToolMetadata,
7-
dbOperationInvalidArgTests,
88
validateThrowsForInvalidArguments,
99
} from "../../../helpers.js";
1010
import * as crypto from "crypto";
@@ -14,13 +14,13 @@ describeWithMongoDB("collectionStorageSize tool", (integration) => {
1414
integration,
1515
"collection-storage-size",
1616
"Gets the size of the collection",
17-
dbOperationParameters
17+
databaseCollectionParameters
1818
);
1919

20-
validateThrowsForInvalidArguments(integration, "collection-storage-size", dbOperationInvalidArgTests);
20+
validateThrowsForInvalidArguments(integration, "collection-storage-size", databaseCollectionInvalidArgs);
2121

2222
describe("with non-existent database", () => {
23-
it("returns 0 MB", async () => {
23+
it("returns an error", async () => {
2424
await integration.connectMcpClient();
2525
const response = await integration.mcpClient().callTool({
2626
name: "collection-storage-size",
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,96 @@
1+
import { ObjectId } from "bson";
2+
import {
3+
databaseParameters,
4+
validateToolMetadata,
5+
validateThrowsForInvalidArguments,
6+
databaseInvalidArgs,
7+
getResponseElements,
8+
} from "../../../helpers.js";
9+
import * as crypto from "crypto";
10+
import { describeWithMongoDB, validateAutoConnectBehavior } from "../mongodbHelpers.js";
11+
12+
describeWithMongoDB("dbStats tool", (integration) => {
13+
validateToolMetadata(
14+
integration,
15+
"db-stats",
16+
"Returns statistics that reflect the use state of a single database",
17+
databaseParameters
18+
);
19+
20+
validateThrowsForInvalidArguments(integration, "db-stats", databaseInvalidArgs);
21+
22+
describe("with non-existent database", () => {
23+
it("returns an error", async () => {
24+
await integration.connectMcpClient();
25+
const response = await integration.mcpClient().callTool({
26+
name: "db-stats",
27+
arguments: { database: integration.randomDbName() },
28+
});
29+
const elements = getResponseElements(response.content);
30+
expect(elements).toHaveLength(2);
31+
expect(elements[0].text).toBe(`Statistics for database ${integration.randomDbName()}`);
32+
33+
const stats = JSON.parse(elements[1].text);
34+
expect(stats.db).toBe(integration.randomDbName());
35+
expect(stats.collections).toBe(0);
36+
expect(stats.storageSize).toBe(0);
37+
});
38+
});
39+
40+
describe("with existing database", () => {
41+
const testCases = [
42+
{
43+
collections: {
44+
foos: 3,
45+
},
46+
name: "single collection",
47+
},
48+
{
49+
collections: {
50+
foos: 2,
51+
bars: 5,
52+
},
53+
name: "multiple collections",
54+
},
55+
];
56+
for (const test of testCases) {
57+
it(`returns correct stats for ${test.name}`, async () => {
58+
for (const [name, count] of Object.entries(test.collections)) {
59+
const objects = Array(count)
60+
.fill(0)
61+
.map(() => {
62+
return { data: crypto.randomBytes(1024), _id: new ObjectId() };
63+
});
64+
await integration.mongoClient().db(integration.randomDbName()).collection(name).insertMany(objects);
65+
}
66+
67+
await integration.connectMcpClient();
68+
const response = await integration.mcpClient().callTool({
69+
name: "db-stats",
70+
arguments: { database: integration.randomDbName() },
71+
});
72+
const elements = getResponseElements(response.content);
73+
expect(elements).toHaveLength(2);
74+
expect(elements[0].text).toBe(`Statistics for database ${integration.randomDbName()}`);
75+
76+
const stats = JSON.parse(elements[1].text);
77+
expect(stats.db).toBe(integration.randomDbName());
78+
expect(stats.collections).toBe(Object.entries(test.collections).length);
79+
expect(stats.storageSize).toBeGreaterThan(1024);
80+
expect(stats.objects).toBe(Object.values(test.collections).reduce((a, b) => a + b, 0));
81+
});
82+
}
83+
});
84+
85+
describe("when not connected", () => {
86+
validateAutoConnectBehavior(integration, "db-stats", () => {
87+
return {
88+
args: {
89+
database: integration.randomDbName(),
90+
collection: "foo",
91+
},
92+
expectedResponse: `Statistics for database ${integration.randomDbName()}`,
93+
};
94+
});
95+
});
96+
});

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

+9-5
Original file line numberDiff line numberDiff line change
@@ -5,15 +5,19 @@ import {
55
getResponseContent,
66
validateToolMetadata,
77
validateThrowsForInvalidArguments,
8-
dbOperationInvalidArgTests,
8+
databaseInvalidArgs,
9+
databaseParameters,
910
} from "../../../helpers.js";
1011

1112
describeWithMongoDB("listCollections tool", (integration) => {
12-
validateToolMetadata(integration, "list-collections", "List all collections for a given database", [
13-
{ name: "database", description: "Database name", type: "string", required: true },
14-
]);
13+
validateToolMetadata(
14+
integration,
15+
"list-collections",
16+
"List all collections for a given database",
17+
databaseParameters
18+
);
1519

16-
validateThrowsForInvalidArguments(integration, "list-collections", dbOperationInvalidArgTests);
20+
validateThrowsForInvalidArguments(integration, "list-collections", databaseInvalidArgs);
1721

1822
describe("with non-existent database", () => {
1923
it("returns no collections", async () => {

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

+1-2
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,4 @@
11
import { describeWithMongoDB, validateAutoConnectBehavior } from "../mongodbHelpers.js";
2-
32
import { getResponseElements, getParameters } from "../../../helpers.js";
43

54
describeWithMongoDB("listDatabases tool", (integration) => {
@@ -21,7 +20,7 @@ describeWithMongoDB("listDatabases tool", (integration) => {
2120
const response = await integration.mcpClient().callTool({ name: "list-databases", arguments: {} });
2221
const dbNames = getDbNames(response.content);
2322

24-
expect(defaultDatabases).toIncludeAllMembers(defaultDatabases);
23+
expect(defaultDatabases).toIncludeAllMembers(dbNames);
2524
});
2625
});
2726

tests/integration/tools/mongodb/read/count.test.ts

+2-2
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@ import { describeWithMongoDB, validateAutoConnectBehavior } from "../mongodbHelp
22

33
import {
44
getResponseContent,
5-
dbOperationParameters,
5+
databaseCollectionParameters,
66
validateToolMetadata,
77
validateThrowsForInvalidArguments,
88
} from "../../../helpers.js";
@@ -16,7 +16,7 @@ describeWithMongoDB("count tool", (integration) => {
1616
type: "object",
1717
required: false,
1818
},
19-
...dbOperationParameters,
19+
...databaseCollectionParameters,
2020
]);
2121

2222
validateThrowsForInvalidArguments(integration, "count", [

0 commit comments

Comments
 (0)