Skip to content

Commit 7a1e217

Browse files
committed
chore: add tests to connection manager
these test should validate that connecting to a cluster is fine and also the connection mechanism inference is working as expected until we support both oidc flows.
1 parent 3977aa7 commit 7a1e217

File tree

2 files changed

+154
-3
lines changed

2 files changed

+154
-3
lines changed

src/common/connectionManager.ts

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,7 @@ export interface ConnectionSettings extends ConnectOptions {
2121

2222
type ConnectionTag = "connected" | "connecting" | "disconnected" | "errored";
2323
type OIDCConnectionAuthType = "oidc-auth-flow" | "oidc-device-flow";
24-
type ConnectionStringAuthType = "scram" | "ldap" | "kerberos" | OIDCConnectionAuthType | "x.509";
24+
export type ConnectionStringAuthType = "scram" | "ldap" | "kerberos" | OIDCConnectionAuthType | "x.509";
2525

2626
export interface ConnectionState {
2727
tag: ConnectionTag;
@@ -114,7 +114,7 @@ export class ConnectionManager extends EventEmitter<ConnectionManagerEvents> {
114114
tag: "connected",
115115
connectedAtlasCluster: settings.atlas,
116116
serviceProvider,
117-
connectionStringAuthType: this.inferConnectionTypeFromSettings(settings),
117+
connectionStringAuthType: ConnectionManager.inferConnectionTypeFromSettings(settings),
118118
});
119119
} catch (error: unknown) {
120120
const errorReason = error instanceof Error ? error.message : `${error as string}`;
@@ -154,7 +154,7 @@ export class ConnectionManager extends EventEmitter<ConnectionManagerEvents> {
154154
return newState;
155155
}
156156

157-
private inferConnectionTypeFromSettings(settings: ConnectionSettings): ConnectionStringAuthType {
157+
static inferConnectionTypeFromSettings(settings: ConnectionSettings): ConnectionStringAuthType {
158158
const connString = new ConnectionString(settings.connectionString);
159159
const searchParams = connString.typedSearchParams<MongoClientOptions>();
160160

Lines changed: 151 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,151 @@
1+
import {
2+
ConnectionManager,
3+
ConnectionManagerEvents,
4+
ConnectionStateConnected,
5+
ConnectionStringAuthType,
6+
} from "../../../src/common/connectionManager.js";
7+
import { describeWithMongoDB } from "../tools/mongodb/mongodbHelpers.js";
8+
import { describe, beforeEach, expect, it, vi, afterEach } from "vitest";
9+
import { config } from "../../../src/common/config.js";
10+
11+
describeWithMongoDB("Connection Manager", (integration) => {
12+
function connectionManager() {
13+
return integration.mcpServer().session.connectionManager;
14+
}
15+
16+
afterEach(async () => {
17+
// disconnect on purpose doesn't change the state if it was failed to avoid losing
18+
// information in production.
19+
await connectionManager().disconnect();
20+
// for testing, force disconnecting AND setting the connection to closed to reset the
21+
// state of the connection manager
22+
connectionManager().changeState("connection-closed", { tag: "disconnected" });
23+
});
24+
25+
describe("when successfully connected", () => {
26+
type ConnectionManagerSpies = {
27+
"connection-requested": (event: ConnectionManagerEvents["connection-requested"][0]) => void;
28+
"connection-succeeded": (event: ConnectionManagerEvents["connection-succeeded"][0]) => void;
29+
"connection-timed-out": (event: ConnectionManagerEvents["connection-timed-out"][0]) => void;
30+
"connection-closed": (event: ConnectionManagerEvents["connection-closed"][0]) => void;
31+
"connection-errored": (event: ConnectionManagerEvents["connection-errored"][0]) => void;
32+
};
33+
34+
let connectionManagerSpies: ConnectionManagerSpies;
35+
36+
beforeEach(async () => {
37+
connectionManagerSpies = {
38+
"connection-requested": vi.fn(),
39+
"connection-succeeded": vi.fn(),
40+
"connection-timed-out": vi.fn(),
41+
"connection-closed": vi.fn(),
42+
"connection-errored": vi.fn(),
43+
};
44+
45+
for (const [event, spy] of Object.entries(connectionManagerSpies)) {
46+
connectionManager().on(event as keyof ConnectionManagerEvents, spy);
47+
}
48+
49+
await connectionManager().connect({
50+
connectionString: integration.connectionString(),
51+
...integration.mcpServer().userConfig.connectOptions,
52+
});
53+
});
54+
55+
it("should be marked explicitly as connected", () => {
56+
expect(connectionManager().currentConnectionState.tag).toEqual("connected");
57+
});
58+
59+
it("can query mongodb successfully", async () => {
60+
const connectionState = connectionManager().currentConnectionState as ConnectionStateConnected;
61+
const collections = await connectionState.serviceProvider.listCollections("admin");
62+
expect(collections).not.toBe([]);
63+
});
64+
65+
it("should notify that the connection was successful", () => {
66+
expect(connectionManagerSpies["connection-succeeded"]).toHaveBeenCalledOnce();
67+
});
68+
69+
describe("when disconnects", () => {
70+
beforeEach(async () => {
71+
await connectionManager().disconnect();
72+
});
73+
74+
it("should notify that it was disconnected before connecting", () => {
75+
expect(connectionManagerSpies["connection-closed"]).toHaveBeenCalled();
76+
});
77+
});
78+
79+
describe("when reconnects", () => {
80+
beforeEach(async () => {
81+
await connectionManager().connect({
82+
connectionString: integration.connectionString(),
83+
...integration.mcpServer().userConfig.connectOptions,
84+
});
85+
});
86+
87+
it("should notify that it was disconnected before connecting", () => {
88+
expect(connectionManagerSpies["connection-closed"]).toHaveBeenCalled();
89+
});
90+
91+
it("should notify that it was connected again", () => {
92+
expect(connectionManagerSpies["connection-succeeded"]).toHaveBeenCalled();
93+
});
94+
});
95+
96+
describe("when fails to connect to a new cluster", () => {
97+
beforeEach(async () => {
98+
try {
99+
await connectionManager().connect({
100+
connectionString: "mongodb://localhost:xxxxx",
101+
...integration.mcpServer().userConfig.connectOptions,
102+
});
103+
} catch (_error: unknown) {
104+
void _error;
105+
}
106+
});
107+
108+
it("should notify that it was disconnected before connecting", () => {
109+
expect(connectionManagerSpies["connection-closed"]).toHaveBeenCalled();
110+
});
111+
112+
it("should notify that it failed connecting", () => {
113+
expect(connectionManagerSpies["connection-errored"]).toHaveBeenCalled();
114+
});
115+
});
116+
});
117+
118+
describe("when disconnected", () => {
119+
it("should be marked explictly as disconnected", () => {
120+
expect(connectionManager().currentConnectionState.tag).toEqual("disconnected");
121+
});
122+
});
123+
});
124+
125+
describe("Connection Manager connection type inference", () => {
126+
const testCases = [
127+
{ connectionString: "mongodb://localhost:27017", connectionType: "scram" },
128+
{ connectionString: "mongodb://localhost:27017?authMechanism=MONGODB-X509", connectionType: "x.509" },
129+
{ connectionString: "mongodb://localhost:27017?authMechanism=GSSAPI", connectionType: "kerberos" },
130+
{
131+
connectionString: "mongodb://localhost:27017?authMechanism=PLAIN&authSource=$external",
132+
connectionType: "ldap",
133+
},
134+
{ connectionString: "mongodb://localhost:27017?authMechanism=PLAIN", connectionType: "scram" },
135+
{ connectionString: "mongodb://localhost:27017?authMechanism=MONGODB-OIDC", connectionType: "oidc-auth-flow" },
136+
] as {
137+
connectionString: string;
138+
connectionType: ConnectionStringAuthType;
139+
}[];
140+
141+
for (const { connectionString, connectionType } of testCases) {
142+
it(`infers ${connectionType} from ${connectionString}`, () => {
143+
const actualConnectionType = ConnectionManager.inferConnectionTypeFromSettings({
144+
connectionString,
145+
...config.connectOptions,
146+
});
147+
148+
expect(actualConnectionType).toBe(connectionType);
149+
});
150+
}
151+
});

0 commit comments

Comments
 (0)