Skip to content

Commit ed5ec2f

Browse files
committed
fix
1 parent 1f9c47d commit ed5ec2f

File tree

4 files changed

+163
-14
lines changed

4 files changed

+163
-14
lines changed

tests/integration/tools/atlas/atlasHelpers.ts

Lines changed: 95 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ import { setupIntegrationTest, defaultTestConfig, defaultDriverOptions } from ".
66
import type { SuiteCollector } from "vitest";
77
import { afterAll, beforeAll, describe } from "vitest";
88
import type { Session } from "../../../../src/common/session.js";
9+
import { get } from "ts-levenshtein";
910

1011
export type IntegrationTestFunction = (integration: IntegrationTest) => void;
1112

@@ -33,8 +34,16 @@ interface ProjectTestArgs {
3334
getIpAddress: () => string;
3435
}
3536

37+
interface ClusterTestArgs {
38+
getProjectId: () => string;
39+
getIpAddress: () => string;
40+
getClusterName: () => string;
41+
}
42+
3643
type ProjectTestFunction = (args: ProjectTestArgs) => void;
3744

45+
type ClusterTestFunction = (args: ClusterTestArgs) => void;
46+
3847
export function withCredentials(integration: IntegrationTest, fn: IntegrationTestFunction): SuiteCollector<object> {
3948
const describeFn =
4049
!process.env.MDB_MCP_API_CLIENT_ID?.length || !process.env.MDB_MCP_API_CLIENT_SECRET?.length
@@ -71,25 +80,25 @@ export function withProject(integration: IntegrationTest, fn: ProjectTestFunctio
7180
}
7281
});
7382

74-
afterAll(() => {
83+
afterAll(async () => {
7584
if (!projectId) {
7685
return;
7786
}
7887

7988
const apiClient = integration.mcpServer().session.apiClient;
8089

81-
// send the delete request and ignore errors
82-
apiClient
83-
.deleteProject({
90+
try {
91+
await apiClient.deleteProject({
8492
params: {
8593
path: {
8694
groupId: projectId,
8795
},
8896
},
89-
})
90-
.catch((error) => {
91-
console.log("Failed to delete project:", error);
9297
});
98+
} catch (error) {
99+
// send the delete request and ignore errors
100+
console.log("Failed to delete project:", error);
101+
}
93102
});
94103

95104
const args = {
@@ -101,10 +110,12 @@ export function withProject(integration: IntegrationTest, fn: ProjectTestFunctio
101110
});
102111
}
103112

104-
export const randomId = new ObjectId().toString();
113+
export function randomId(): string {
114+
return new ObjectId().toString();
115+
}
105116

106117
async function createProject(apiClient: ApiClient): Promise<Group & Required<Pick<Group, "id">>> {
107-
const projectName: string = `testProj-` + randomId;
118+
const projectName: string = `testProj-` + randomId();
108119

109120
const orgs = await apiClient.listOrganizations();
110121
if (!orgs?.results?.length || !orgs.results[0]?.id) {
@@ -229,3 +240,78 @@ export async function waitCluster(
229240
`Cluster wait timeout: ${clusterName} did not meet condition within ${maxPollingIterations} iterations`
230241
);
231242
}
243+
244+
export function withCluster(integration: IntegrationTest, fn: ClusterTestFunction): SuiteCollector<object> {
245+
return withProject(integration, ({ getProjectId, getIpAddress }) => {
246+
describe("with cluster", () => {
247+
const clusterName: string = `test-cluster-${randomId()}`;
248+
249+
beforeAll(async () => {
250+
const apiClient = integration.mcpServer().session.apiClient;
251+
252+
const projectId = getProjectId();
253+
254+
const input = {
255+
groupId: projectId,
256+
name: clusterName,
257+
clusterType: "REPLICASET",
258+
replicationSpecs: [
259+
{
260+
zoneName: "Zone 1",
261+
regionConfigs: [
262+
{
263+
providerName: "TENANT",
264+
backingProviderName: "AWS",
265+
regionName: "US_EAST_1",
266+
electableSpecs: {
267+
instanceSize: "M0",
268+
},
269+
},
270+
],
271+
},
272+
],
273+
terminationProtectionEnabled: false,
274+
} as unknown as ClusterDescription20240805;
275+
276+
await apiClient.createCluster({
277+
params: {
278+
path: {
279+
groupId: projectId,
280+
},
281+
},
282+
body: input,
283+
});
284+
285+
await waitCluster(integration.mcpServer().session, projectId, clusterName, (cluster) => {
286+
return cluster.stateName === "IDLE";
287+
});
288+
});
289+
290+
afterAll(async () => {
291+
const apiClient = integration.mcpServer().session.apiClient;
292+
293+
try {
294+
// send the delete request and ignore errors
295+
await apiClient.deleteCluster({
296+
params: {
297+
path: {
298+
groupId: getProjectId(),
299+
clusterName,
300+
},
301+
},
302+
});
303+
} catch (error) {
304+
console.log("Failed to delete cluster:", error);
305+
}
306+
});
307+
308+
const args = {
309+
getProjectId: (): string => getProjectId(),
310+
getIpAddress: (): string => getIpAddress(),
311+
getClusterName: (): string => clusterName,
312+
};
313+
314+
fn(args);
315+
});
316+
});
317+
}

tests/integration/tools/atlas/clusters.test.ts

Lines changed: 66 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,15 @@
1+
import { before } from "node:test";
12
import type { Session } from "../../../../src/common/session.js";
23
import { expectDefined, getResponseContent } from "../../helpers.js";
3-
import { describeWithAtlas, withProject, randomId, deleteCluster, waitCluster, sleep } from "./atlasHelpers.js";
4-
import { afterAll, beforeAll, describe, expect, it } from "vitest";
4+
import { describeWithAtlas, withProject, withCluster, randomId, deleteCluster, waitCluster, sleep } from "./atlasHelpers.js";
5+
import { afterAll, beforeAll, describe, expect, it, vitest } from "vitest";
6+
import cluster from "cluster";
7+
import { connect } from "http2";
8+
import { connected } from "process";
59

610
describeWithAtlas("clusters", (integration) => {
711
withProject(integration, ({ getProjectId, getIpAddress }) => {
8-
const clusterName = "ClusterTest-" + randomId;
12+
const clusterName = "ClusterTest-" + randomId();
913

1014
afterAll(async () => {
1115
const projectId = getProjectId();
@@ -142,6 +146,11 @@ describeWithAtlas("clusters", (integration) => {
142146
});
143147

144148
it("connects to cluster", async () => {
149+
const createDatabaseUserSpy = vitest.spyOn(
150+
integration.mcpServer().session.apiClient,
151+
"createDatabaseUser"
152+
);
153+
145154
const projectId = getProjectId();
146155
const connectionType = "standard";
147156
let connected = false;
@@ -158,6 +167,8 @@ describeWithAtlas("clusters", (integration) => {
158167
if (content.includes(`Connected to cluster "${clusterName}"`)) {
159168
connected = true;
160169

170+
expect(createDatabaseUserSpy).toHaveBeenCalledTimes(1);
171+
161172
// assert that some of the element s have the message
162173
expect(content).toContain(
163174
"Note: A temporary user has been created to enable secure connection to the cluster. For more information, see https://dochub.mongodb.org/core/mongodb-mcp-server-tools-considerations"
@@ -172,6 +183,58 @@ describeWithAtlas("clusters", (integration) => {
172183
expect(connected).toBe(true);
173184
});
174185

186+
describe("when connected", () => {
187+
withCluster(
188+
integration,
189+
({ getProjectId: getSecondaryProjectId, getClusterName: getSecondaryClusterName }) => {
190+
beforeAll(async () => {
191+
let connected = false;
192+
for (let i = 0; i < 10; i++) {
193+
const response = await integration.mcpClient().callTool({
194+
name: "atlas-connect-cluster",
195+
arguments: {
196+
projectId: getSecondaryProjectId(),
197+
clusterName: getSecondaryClusterName(),
198+
connectionType: "standard",
199+
},
200+
});
201+
202+
const content = getResponseContent(response.content);
203+
204+
if (content.includes(`Connected to cluster "${getSecondaryClusterName()}"`)) {
205+
connected = true;
206+
break;
207+
}
208+
209+
await sleep(500);
210+
}
211+
212+
if (!connected) {
213+
throw new Error("Could not connect to cluster before tests");
214+
}
215+
});
216+
217+
it("disconnects before connecting to another cluster", async () => {
218+
const deleteDatabaseUserSpy = vitest.spyOn(
219+
integration.mcpServer().session.apiClient,
220+
"deleteDatabaseUser"
221+
);
222+
223+
await integration.mcpClient().callTool({
224+
name: "atlas-connect-cluster",
225+
arguments: {
226+
projectId: getProjectId(),
227+
clusterName: clusterName,
228+
connectionType: "standard",
229+
},
230+
});
231+
232+
expect(deleteDatabaseUserSpy).toHaveBeenCalledTimes(1);
233+
});
234+
}
235+
);
236+
});
237+
175238
describe("when not connected", () => {
176239
it("prompts for atlas-connect-cluster when querying mongodb", async () => {
177240
const response = await integration.mcpClient().callTool({

tests/integration/tools/atlas/dbUsers.test.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ describeWithAtlas("db users", (integration) => {
88
withProject(integration, ({ getProjectId }) => {
99
let userName: string;
1010
beforeEach(() => {
11-
userName = "testuser-" + randomId;
11+
userName = "testuser-" + randomId();
1212
});
1313

1414
const createUserWithMCP = async (password?: string): Promise<unknown> => {

tests/integration/tools/atlas/performanceAdvisor.test.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@ import type { BaseEvent, ToolEvent } from "../../../../src/telemetry/types.js";
1818

1919
describeWithAtlas("performanceAdvisor", (integration) => {
2020
withProject(integration, ({ getProjectId }) => {
21-
const clusterName = "ClusterTest-" + randomId;
21+
const clusterName = "ClusterTest-" + randomId();
2222

2323
afterAll(async () => {
2424
const projectId = getProjectId();

0 commit comments

Comments
 (0)