Skip to content

Commit 937b933

Browse files
authored
refactor: split test helpers (#103)
1 parent 5ce31ec commit 937b933

15 files changed

+150
-92
lines changed

tests/integration/helpers.ts

Lines changed: 0 additions & 56 deletions
Original file line numberDiff line numberDiff line change
@@ -23,16 +23,9 @@ type ToolInfo = Awaited<ReturnType<Client["listTools"]>>["tools"][number];
2323
export interface IntegrationTest {
2424
mcpClient: () => Client;
2525
mcpServer: () => Server;
26-
mongoClient: () => MongoClient;
27-
connectionString: () => string;
28-
connectMcpClient: () => Promise<void>;
29-
randomDbName: () => string;
3026
}
3127

3228
export function setupIntegrationTest(userConfig: UserConfig = config): IntegrationTest {
33-
let mongoCluster: runner.MongoCluster | undefined;
34-
let mongoClient: MongoClient | undefined;
35-
3629
let mcpClient: Client | undefined;
3730
let mcpServer: Server | undefined;
3831

@@ -89,55 +82,6 @@ export function setupIntegrationTest(userConfig: UserConfig = config): Integrati
8982
mcpServer = undefined;
9083
});
9184

92-
afterEach(async () => {
93-
await mcpServer?.session.close();
94-
config.connectionString = undefined;
95-
96-
await mongoClient?.close();
97-
mongoClient = undefined;
98-
});
99-
100-
beforeAll(async function () {
101-
// Downloading Windows executables in CI takes a long time because
102-
// they include debug symbols...
103-
const tmpDir = path.join(__dirname, "..", "tmp");
104-
await fs.mkdir(tmpDir, { recursive: true });
105-
106-
// On Windows, we may have a situation where mongod.exe is not fully released by the OS
107-
// before we attempt to run it again, so we add a retry.
108-
let dbsDir = path.join(tmpDir, "mongodb-runner", "dbs");
109-
for (let i = 0; i < 10; i++) {
110-
try {
111-
mongoCluster = await MongoCluster.start({
112-
tmpDir: dbsDir,
113-
logDir: path.join(tmpDir, "mongodb-runner", "logs"),
114-
topology: "standalone",
115-
});
116-
117-
return;
118-
} catch (err) {
119-
if (i < 5) {
120-
// Just wait a little bit and retry
121-
console.error(`Failed to start cluster in ${dbsDir}, attempt ${i}: ${err}`);
122-
await new Promise((resolve) => setTimeout(resolve, 1000));
123-
} else {
124-
// If we still fail after 5 seconds, try another db dir
125-
console.error(
126-
`Failed to start cluster in ${dbsDir}, attempt ${i}: ${err}. Retrying with a new db dir.`
127-
);
128-
dbsDir = path.join(tmpDir, "mongodb-runner", `dbs${i - 5}`);
129-
}
130-
}
131-
}
132-
133-
throw new Error("Failed to start cluster after 10 attempts");
134-
}, 120_000);
135-
136-
afterAll(async function () {
137-
await mongoCluster?.close();
138-
mongoCluster = undefined;
139-
});
140-
14185
const getMcpClient = () => {
14286
if (!mcpClient) {
14387
throw new Error("beforeEach() hook not ran yet");

tests/integration/tools/atlas/atlasHelpers.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@ export function sleep(ms: number) {
99
return new Promise((resolve) => setTimeout(resolve, ms));
1010
}
1111

12-
export function describeAtlas(name: number | string | Function | jest.FunctionLike, fn: IntegrationTestFunction) {
12+
export function describeWithAtlas(name: number | string | Function | jest.FunctionLike, fn: IntegrationTestFunction) {
1313
const testDefinition = () => {
1414
const integration = setupIntegrationTest();
1515
describe(name, () => {

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

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,5 @@
1+
import { describeMongoDB } from "../mongodbHelpers.js";
2+
13
import {
24
getResponseContent,
35
dbOperationParameters,
@@ -8,9 +10,7 @@ import {
810
dbOperationInvalidArgTests,
911
} from "../../../helpers.js";
1012

11-
describe("createCollection tool", () => {
12-
const integration = setupIntegrationTest();
13-
13+
describeMongoDB("createCollection tool", (integration) => {
1414
validateToolMetadata(
1515
integration,
1616
"create-collection",

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

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,5 @@
1+
import { describeMongoDB } from "../mongodbHelpers.js";
2+
13
import {
24
getResponseContent,
35
dbOperationParameters,
@@ -8,9 +10,7 @@ import {
810
} from "../../../helpers.js";
911
import { IndexDirection } from "mongodb";
1012

11-
describe("createIndex tool", () => {
12-
const integration = setupIntegrationTest();
13-
13+
describeMongoDB("createIndex tool", (integration) => {
1414
validateToolMetadata(integration, "create-index", "Create an index for a collection", [
1515
...dbOperationParameters,
1616
{

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

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,5 @@
1+
import { describeMongoDB } from "../mongodbHelpers.js";
2+
13
import {
24
getResponseContent,
35
dbOperationParameters,
@@ -7,9 +9,7 @@ import {
79
validateThrowsForInvalidArguments,
810
} from "../../../helpers.js";
911

10-
describe("insertMany tool", () => {
11-
const integration = setupIntegrationTest();
12-
12+
describeMongoDB("insertMany tool", (integration) => {
1313
validateToolMetadata(integration, "insert-many", "Insert an array of documents into a MongoDB collection", [
1414
...dbOperationParameters,
1515
{

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

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,5 @@
1+
import { describeMongoDB } from "../mongodbHelpers.js";
2+
13
import {
24
getResponseContent,
35
dbOperationParameters,
@@ -7,9 +9,7 @@ import {
79
validateThrowsForInvalidArguments,
810
} from "../../../helpers.js";
911

10-
describe("deleteMany tool", () => {
11-
const integration = setupIntegrationTest();
12-
12+
describeMongoDB("deleteMany tool", (integration) => {
1313
validateToolMetadata(
1414
integration,
1515
"delete-many",

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

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,5 @@
1+
import { describeMongoDB } from "../mongodbHelpers.js";
2+
13
import {
24
getResponseContent,
35
dbOperationParameters,
@@ -8,9 +10,7 @@ import {
810
dbOperationInvalidArgTests,
911
} from "../../../helpers.js";
1012

11-
describe("dropCollection tool", () => {
12-
const integration = setupIntegrationTest();
13-
13+
describeMongoDB("dropCollection tool", (integration) => {
1414
validateToolMetadata(
1515
integration,
1616
"drop-collection",

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

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,5 @@
1+
import { describeMongoDB } from "../mongodbHelpers.js";
2+
13
import {
24
getResponseContent,
35
dbOperationParameters,
@@ -8,9 +10,7 @@ import {
810
dbOperationInvalidArgTests,
911
} from "../../../helpers.js";
1012

11-
describe("dropDatabase tool", () => {
12-
const integration = setupIntegrationTest();
13-
13+
describeMongoDB("dropDatabase tool", (integration) => {
1414
validateToolMetadata(
1515
integration,
1616
"drop-database",

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

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,5 @@
1+
import { describeMongoDB } from "../mongodbHelpers.js";
2+
13
import {
24
getResponseElements,
35
getResponseContent,
@@ -12,9 +14,7 @@ import { Document } from "bson";
1214
import { OptionalId } from "mongodb";
1315
import { SimplifiedSchema } from "mongodb-schema";
1416

15-
describe("collectionSchema tool", () => {
16-
const integration = setupIntegrationTest();
17-
17+
describeMongoDB("collectionSchema tool", (integration) => {
1818
validateToolMetadata(
1919
integration,
2020
"collection-schema",

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

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,5 @@
1+
import { describeMongoDB } from "../mongodbHelpers.js";
2+
13
import {
24
getResponseContent,
35
setupIntegrationTest,
@@ -9,9 +11,7 @@ import {
911
} from "../../../helpers.js";
1012
import * as crypto from "crypto";
1113

12-
describe("collectionStorageSize tool", () => {
13-
const integration = setupIntegrationTest();
14-
14+
describeMongoDB("collectionStorageSize tool", (integration) => {
1515
validateToolMetadata(
1616
integration,
1717
"collection-storage-size",

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

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,10 @@
1+
import { describeMongoDB } from "../mongodbHelpers.js";
2+
13
import { getResponseContent, setupIntegrationTest, validateToolMetadata } from "../../../helpers.js";
24

35
import { config } from "../../../../../src/config.js";
46

5-
describe("Connect tool", () => {
6-
const integration = setupIntegrationTest();
7-
7+
describeMongoDB("Connect tool", (integration) => {
88
validateToolMetadata(integration, "connect", "Connect to a MongoDB instance", [
99
{
1010
name: "options",

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

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,5 @@
1+
import { describeMongoDB } from "../mongodbHelpers.js";
2+
13
import {
24
getResponseElements,
35
getResponseContent,
@@ -8,9 +10,7 @@ import {
810
dbOperationInvalidArgTests,
911
} from "../../../helpers.js";
1012

11-
describe("listCollections tool", () => {
12-
const integration = setupIntegrationTest();
13-
13+
describeMongoDB("listCollections tool", (integration) => {
1414
validateToolMetadata(integration, "list-collections", "List all collections for a given database", [
1515
{ name: "database", description: "Database name", type: "string", required: true },
1616
]);

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

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,5 @@
1+
import { describeMongoDB } from "../mongodbHelpers.js";
2+
13
import {
24
getResponseElements,
35
getParameters,
@@ -6,8 +8,7 @@ import {
68
} from "../../../helpers.js";
79
import { toIncludeSameMembers } from "jest-extended";
810

9-
describe("listDatabases tool", () => {
10-
const integration = setupIntegrationTest();
11+
describeMongoDB("listDatabases tool", (integration) => {
1112
const defaultDatabases = ["admin", "config", "local"];
1213

1314
it("should have correct metadata", async () => {
Lines changed: 113 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,113 @@
1+
import runner, { MongoCluster } from "mongodb-runner";
2+
import path from "path";
3+
import fs from "fs/promises";
4+
import { MongoClient, ObjectId } from "mongodb";
5+
import { IntegrationTest, setupIntegrationTest } from "../../helpers.js";
6+
import { UserConfig, config } from "../../../../src/config.js";
7+
8+
interface MongoDBIntegrationTest {
9+
mongoClient: () => MongoClient;
10+
connectionString: () => string;
11+
connectMcpClient: () => Promise<void>;
12+
randomDbName: () => string;
13+
}
14+
15+
export function describeWithMongoDB(
16+
name: number | string | Function | jest.FunctionLike,
17+
fn: (integration: IntegrationTest & MongoDBIntegrationTest) => void
18+
): void {
19+
describe("mongodb", () => {
20+
const integration = setupIntegrationTest();
21+
const mdbIntegration = setupMongoDBIntegrationTest(integration);
22+
describe(name, () => {
23+
fn({ ...integration, ...mdbIntegration });
24+
});
25+
});
26+
}
27+
28+
export function setupMongoDBIntegrationTest(
29+
integration: IntegrationTest,
30+
userConfig: UserConfig = config
31+
): MongoDBIntegrationTest {
32+
let mongoCluster: runner.MongoCluster | undefined;
33+
let mongoClient: MongoClient | undefined;
34+
let randomDbName: string;
35+
36+
beforeEach(async () => {
37+
randomDbName = new ObjectId().toString();
38+
});
39+
40+
afterEach(async () => {
41+
await integration.mcpServer().session.close();
42+
config.connectionString = undefined;
43+
44+
await mongoClient?.close();
45+
mongoClient = undefined;
46+
});
47+
48+
beforeAll(async function () {
49+
// Downloading Windows executables in CI takes a long time because
50+
// they include debug symbols...
51+
const tmpDir = path.join(__dirname, "..", "..", "..", "tmp");
52+
await fs.mkdir(tmpDir, { recursive: true });
53+
54+
// On Windows, we may have a situation where mongod.exe is not fully released by the OS
55+
// before we attempt to run it again, so we add a retry.
56+
let dbsDir = path.join(tmpDir, "mongodb-runner", "dbs");
57+
for (let i = 0; i < 10; i++) {
58+
try {
59+
mongoCluster = await MongoCluster.start({
60+
tmpDir: dbsDir,
61+
logDir: path.join(tmpDir, "mongodb-runner", "logs"),
62+
topology: "standalone",
63+
});
64+
65+
return;
66+
} catch (err) {
67+
if (i < 5) {
68+
// Just wait a little bit and retry
69+
console.error(`Failed to start cluster in ${dbsDir}, attempt ${i}: ${err}`);
70+
await new Promise((resolve) => setTimeout(resolve, 1000));
71+
} else {
72+
// If we still fail after 5 seconds, try another db dir
73+
console.error(
74+
`Failed to start cluster in ${dbsDir}, attempt ${i}: ${err}. Retrying with a new db dir.`
75+
);
76+
dbsDir = path.join(tmpDir, "mongodb-runner", `dbs${i - 5}`);
77+
}
78+
}
79+
}
80+
81+
throw new Error("Failed to start cluster after 10 attempts");
82+
}, 120_000);
83+
84+
afterAll(async function () {
85+
await mongoCluster?.close();
86+
mongoCluster = undefined;
87+
});
88+
89+
const getConnectionString = () => {
90+
if (!mongoCluster) {
91+
throw new Error("beforeAll() hook not ran yet");
92+
}
93+
94+
return mongoCluster.connectionString;
95+
};
96+
97+
return {
98+
mongoClient: () => {
99+
if (!mongoClient) {
100+
mongoClient = new MongoClient(getConnectionString());
101+
}
102+
return mongoClient;
103+
},
104+
connectionString: getConnectionString,
105+
connectMcpClient: async () => {
106+
await integration.mcpClient().callTool({
107+
name: "connect",
108+
arguments: { options: [{ connectionString: getConnectionString() }] },
109+
});
110+
},
111+
randomDbName: () => randomDbName,
112+
};
113+
}

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

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,5 @@
1+
import { describeMongoDB } from "../mongodbHelpers.js";
2+
13
import {
24
getResponseContent,
35
dbOperationParameters,
@@ -7,9 +9,7 @@ import {
79
validateThrowsForInvalidArguments,
810
} from "../../../helpers.js";
911

10-
describe("count tool", () => {
11-
const integration = setupIntegrationTest();
12-
12+
describeMongoDB("count tool", (integration) => {
1313
validateToolMetadata(integration, "count", "Gets the number of documents in a MongoDB collection", [
1414
{
1515
name: "query",

0 commit comments

Comments
 (0)