• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

mongodb-js / mongodb-mcp-server / 16373544917

18 Jul 2025 02:50PM UTC coverage: 81.82% (+4.2%) from 77.592%
16373544917

push

github

web-flow
chore(tests): accuracy tests for MongoDB tools exposed by MCP server MCP-39 (#341)

Co-authored-by: Nikola Irinchev <irinchev@me.com>

486 of 629 branches covered (77.27%)

Branch coverage included in aggregate %.

21 of 21 new or added lines in 8 files covered. (100.0%)

3 existing lines in 1 file now uncovered.

2885 of 3491 relevant lines covered (82.64%)

56.41 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

73.9
/src/common/atlas/apiClient.ts
1
import createClient, { Client, Middleware } from "openapi-fetch";
2✔
2
import type { FetchOptions } from "openapi-fetch";
3
import { AccessToken, ClientCredentials } from "simple-oauth2";
2✔
4
import { ApiClientError } from "./apiClientError.js";
2✔
5
import { paths, operations } from "./openapi.js";
6
import { CommonProperties, TelemetryEvent } from "../../telemetry/types.js";
7
import { packageInfo } from "../packageInfo.js";
2✔
8
import logger, { LogId } from "../logger.js";
2✔
9

10
const ATLAS_API_VERSION = "2025-03-12";
2✔
11

12
export interface ApiClientCredentials {
13
    clientId: string;
14
    clientSecret: string;
15
}
16

17
export interface ApiClientOptions {
18
    credentials?: ApiClientCredentials;
19
    baseUrl: string;
20
    userAgent?: string;
21
}
22

23
export class ApiClient {
2✔
24
    private options: {
25
        baseUrl: string;
26
        userAgent: string;
27
        credentials?: {
28
            clientId: string;
29
            clientSecret: string;
30
        };
31
    };
32
    private client: Client<paths>;
33
    private oauth2Client?: ClientCredentials;
34
    private accessToken?: AccessToken;
35

36
    private getAccessToken = async () => {
2✔
37
        if (this.oauth2Client && (!this.accessToken || this.accessToken.expired())) {
107✔
38
            this.accessToken = await this.oauth2Client.getToken({});
6✔
39
        }
6✔
40
        return this.accessToken?.token.access_token as string | undefined;
107✔
41
    };
107✔
42

43
    private authMiddleware: Middleware = {
2✔
44
        onRequest: async ({ request, schemaPath }) => {
2✔
45
            if (schemaPath.startsWith("/api/private/unauth") || schemaPath.startsWith("/api/oauth")) {
94!
46
                return undefined;
×
47
            }
×
48

49
            try {
94✔
50
                const accessToken = await this.getAccessToken();
94✔
51
                if (accessToken) {
94✔
52
                    request.headers.set("Authorization", `Bearer ${accessToken}`);
94✔
53
                }
94✔
54
                return request;
94✔
55
            } catch {
94!
56
                // ignore not availble tokens, API will return 401
57
            }
×
58
        },
94✔
59
    };
2✔
60

61
    constructor(options: ApiClientOptions) {
2✔
62
        this.options = {
86✔
63
            ...options,
86✔
64
            userAgent:
86✔
65
                options.userAgent ||
86✔
66
                `AtlasMCP/${packageInfo.version} (${process.platform}; ${process.arch}; ${process.env.HOSTNAME || "unknown"})`,
70✔
67
        };
86✔
68

69
        this.client = createClient<paths>({
86✔
70
            baseUrl: this.options.baseUrl,
86✔
71
            headers: {
86✔
72
                "User-Agent": this.options.userAgent,
86✔
73
                Accept: `application/vnd.atlas.${ATLAS_API_VERSION}+json`,
86✔
74
            },
86✔
75
        });
86✔
76
        if (this.options.credentials?.clientId && this.options.credentials?.clientSecret) {
86✔
77
            this.oauth2Client = new ClientCredentials({
51✔
78
                client: {
51✔
79
                    id: this.options.credentials.clientId,
51✔
80
                    secret: this.options.credentials.clientSecret,
51✔
81
                },
51✔
82
                auth: {
51✔
83
                    tokenHost: this.options.baseUrl,
51✔
84
                    tokenPath: "/api/oauth/token",
51✔
85
                    revokePath: "/api/oauth/revoke",
51✔
86
                },
51✔
87
                http: {
51✔
88
                    headers: {
51✔
89
                        "User-Agent": this.options.userAgent,
51✔
90
                    },
51✔
91
                },
51✔
92
            });
51✔
93
            this.client.use(this.authMiddleware);
51✔
94
        }
51✔
95
    }
86✔
96

97
    public hasCredentials(): boolean {
2✔
98
        return !!this.oauth2Client;
6✔
99
    }
6✔
100

101
    public async validateAccessToken(): Promise<void> {
2✔
102
        await this.getAccessToken();
×
103
    }
×
104

105
    public async close(): Promise<void> {
2✔
106
        if (this.accessToken) {
62!
107
            try {
6✔
108
                await this.accessToken.revoke("access_token");
6✔
109
            } catch (error: unknown) {
6!
UNCOV
110
                const err = error instanceof Error ? error : new Error(String(error));
×
UNCOV
111
                logger.error(LogId.atlasApiRevokeFailure, "apiClient", `Failed to revoke access token: ${err.message}`);
×
UNCOV
112
            }
×
113
            this.accessToken = undefined;
6✔
114
        }
6✔
115
    }
62✔
116

117
    public async getIpInfo(): Promise<{
2✔
118
        currentIpv4Address: string;
119
    }> {
13✔
120
        const accessToken = await this.getAccessToken();
13✔
121

122
        const endpoint = "api/private/ipinfo";
13✔
123
        const url = new URL(endpoint, this.options.baseUrl);
13✔
124
        const response = await fetch(url, {
13✔
125
            method: "GET",
13✔
126
            headers: {
13✔
127
                Accept: "application/json",
13✔
128
                Authorization: `Bearer ${accessToken}`,
13✔
129
                "User-Agent": this.options.userAgent,
13✔
130
            },
13✔
131
        });
13✔
132

133
        if (!response.ok) {
13!
134
            throw await ApiClientError.fromResponse(response);
×
135
        }
×
136

137
        return (await response.json()) as Promise<{
13✔
138
            currentIpv4Address: string;
139
        }>;
140
    }
13✔
141

142
    public async sendEvents(events: TelemetryEvent<CommonProperties>[]): Promise<void> {
2✔
143
        if (!this.options.credentials) {
10!
144
            await this.sendUnauthEvents(events);
×
145
            return;
×
146
        }
×
147

148
        try {
10✔
149
            await this.sendAuthEvents(events);
10✔
150
        } catch (error) {
10✔
151
            if (error instanceof ApiClientError) {
8✔
152
                if (error.response.status !== 401) {
4!
153
                    throw error;
×
154
                }
×
155
            }
4✔
156

157
            // send unauth events if any of the following are true:
158
            // 1: the token is not valid (not ApiClientError)
159
            // 2: if the api responded with 401 (ApiClientError with status 401)
160
            await this.sendUnauthEvents(events);
8✔
161
        }
6✔
162
    }
10✔
163

164
    private async sendAuthEvents(events: TelemetryEvent<CommonProperties>[]): Promise<void> {
2✔
165
        const accessToken = await this.getAccessToken();
10✔
166
        if (!accessToken) {
10✔
167
            throw new Error("No access token available");
2✔
168
        }
2✔
169
        const authUrl = new URL("api/private/v1.0/telemetry/events", this.options.baseUrl);
6✔
170
        const response = await fetch(authUrl, {
6✔
171
            method: "POST",
6✔
172
            headers: {
6✔
173
                Accept: "application/json",
6✔
174
                "Content-Type": "application/json",
6✔
175
                "User-Agent": this.options.userAgent,
6✔
176
                Authorization: `Bearer ${accessToken}`,
6✔
177
            },
6✔
178
            body: JSON.stringify(events),
6✔
179
        });
6✔
180

181
        if (!response.ok) {
10✔
182
            throw await ApiClientError.fromResponse(response);
4✔
183
        }
4✔
184
    }
10✔
185

186
    private async sendUnauthEvents(events: TelemetryEvent<CommonProperties>[]): Promise<void> {
2✔
187
        const headers: Record<string, string> = {
8✔
188
            Accept: "application/json",
8✔
189
            "Content-Type": "application/json",
8✔
190
            "User-Agent": this.options.userAgent,
8✔
191
        };
8✔
192

193
        const unauthUrl = new URL("api/private/unauth/telemetry/events", this.options.baseUrl);
8✔
194
        const response = await fetch(unauthUrl, {
8✔
195
            method: "POST",
8✔
196
            headers,
8✔
197
            body: JSON.stringify(events),
8✔
198
        });
8✔
199

200
        if (!response.ok) {
8✔
201
            throw await ApiClientError.fromResponse(response);
2✔
202
        }
2✔
203
    }
8✔
204

205
    // DO NOT EDIT. This is auto-generated code.
206
    async listClustersForAllProjects(options?: FetchOptions<operations["listClustersForAllProjects"]>) {
2✔
207
        const { data, error, response } = await this.client.GET("/api/atlas/v2/clusters", options);
×
208
        if (error) {
×
209
            throw ApiClientError.fromError(response, error);
×
210
        }
×
211
        return data;
×
212
    }
×
213

214
    async listProjects(options?: FetchOptions<operations["listProjects"]>) {
2✔
215
        const { data, error, response } = await this.client.GET("/api/atlas/v2/groups", options);
6✔
216
        if (error) {
6✔
217
            throw ApiClientError.fromError(response, error);
2✔
218
        }
2✔
219
        return data;
4✔
220
    }
6✔
221

222
    async createProject(options: FetchOptions<operations["createProject"]>) {
2✔
223
        const { data, error, response } = await this.client.POST("/api/atlas/v2/groups", options);
5✔
224
        if (error) {
5!
225
            throw ApiClientError.fromError(response, error);
×
226
        }
×
227
        return data;
5✔
228
    }
5✔
229

230
    async deleteProject(options: FetchOptions<operations["deleteProject"]>) {
2✔
231
        const { error, response } = await this.client.DELETE("/api/atlas/v2/groups/{groupId}", options);
5✔
232
        if (error) {
5!
233
            throw ApiClientError.fromError(response, error);
×
234
        }
×
235
    }
5✔
236

237
    async getProject(options: FetchOptions<operations["getProject"]>) {
2✔
238
        const { data, error, response } = await this.client.GET("/api/atlas/v2/groups/{groupId}", options);
1✔
239
        if (error) {
1!
240
            throw ApiClientError.fromError(response, error);
×
241
        }
×
242
        return data;
1✔
243
    }
1✔
244

245
    async listProjectIpAccessLists(options: FetchOptions<operations["listProjectIpAccessLists"]>) {
2✔
246
        const { data, error, response } = await this.client.GET("/api/atlas/v2/groups/{groupId}/accessList", options);
4✔
247
        if (error) {
4!
248
            throw ApiClientError.fromError(response, error);
×
249
        }
×
250
        return data;
4✔
251
    }
4✔
252

253
    async createProjectIpAccessList(options: FetchOptions<operations["createProjectIpAccessList"]>) {
2✔
254
        const { data, error, response } = await this.client.POST("/api/atlas/v2/groups/{groupId}/accessList", options);
10✔
255
        if (error) {
10!
256
            throw ApiClientError.fromError(response, error);
×
257
        }
×
258
        return data;
10✔
259
    }
10✔
260

261
    async deleteProjectIpAccessList(options: FetchOptions<operations["deleteProjectIpAccessList"]>) {
2✔
262
        const { error, response } = await this.client.DELETE(
5✔
263
            "/api/atlas/v2/groups/{groupId}/accessList/{entryValue}",
5✔
264
            options
5✔
265
        );
5✔
266
        if (error) {
5!
267
            throw ApiClientError.fromError(response, error);
×
268
        }
×
269
    }
5✔
270

271
    async listAlerts(options: FetchOptions<operations["listAlerts"]>) {
2✔
272
        const { data, error, response } = await this.client.GET("/api/atlas/v2/groups/{groupId}/alerts", options);
1✔
273
        if (error) {
1!
274
            throw ApiClientError.fromError(response, error);
×
275
        }
×
276
        return data;
1✔
277
    }
1✔
278

279
    async listClusters(options: FetchOptions<operations["listClusters"]>) {
2✔
280
        const { data, error, response } = await this.client.GET("/api/atlas/v2/groups/{groupId}/clusters", options);
1✔
281
        if (error) {
1!
282
            throw ApiClientError.fromError(response, error);
×
283
        }
×
284
        return data;
1✔
285
    }
1✔
286

287
    async createCluster(options: FetchOptions<operations["createCluster"]>) {
2✔
288
        const { data, error, response } = await this.client.POST("/api/atlas/v2/groups/{groupId}/clusters", options);
1✔
289
        if (error) {
1!
290
            throw ApiClientError.fromError(response, error);
×
291
        }
×
292
        return data;
1✔
293
    }
1✔
294

295
    async deleteCluster(options: FetchOptions<operations["deleteCluster"]>) {
2✔
296
        const { error, response } = await this.client.DELETE(
1✔
297
            "/api/atlas/v2/groups/{groupId}/clusters/{clusterName}",
1✔
298
            options
1✔
299
        );
1✔
300
        if (error) {
1!
301
            throw ApiClientError.fromError(response, error);
×
302
        }
×
303
    }
1✔
304

305
    async getCluster(options: FetchOptions<operations["getCluster"]>) {
2✔
306
        const { data, error, response } = await this.client.GET(
38✔
307
            "/api/atlas/v2/groups/{groupId}/clusters/{clusterName}",
38✔
308
            options
38✔
309
        );
38✔
310

311
        if (error) {
38✔
312
            throw ApiClientError.fromError(response, error);
1✔
313
        }
1✔
314
        return data;
37✔
315
    }
38✔
316

317
    async listDatabaseUsers(options: FetchOptions<operations["listDatabaseUsers"]>) {
2✔
318
        const { data, error, response } = await this.client.GET(
1✔
319
            "/api/atlas/v2/groups/{groupId}/databaseUsers",
1✔
320
            options
1✔
321
        );
1✔
322
        if (error) {
1!
323
            throw ApiClientError.fromError(response, error);
×
324
        }
×
325
        return data;
1✔
326
    }
1✔
327

328
    async createDatabaseUser(options: FetchOptions<operations["createDatabaseUser"]>) {
2✔
329
        const { data, error, response } = await this.client.POST(
5✔
330
            "/api/atlas/v2/groups/{groupId}/databaseUsers",
5✔
331
            options
5✔
332
        );
5✔
333
        if (error) {
5!
334
            throw ApiClientError.fromError(response, error);
×
335
        }
×
336
        return data;
5✔
337
    }
5✔
338

339
    async deleteDatabaseUser(options: FetchOptions<operations["deleteDatabaseUser"]>) {
2✔
340
        const { error, response } = await this.client.DELETE(
7✔
341
            "/api/atlas/v2/groups/{groupId}/databaseUsers/{databaseName}/{username}",
7✔
342
            options
7✔
343
        );
7✔
344
        if (error) {
7✔
345
            throw ApiClientError.fromError(response, error);
2✔
346
        }
2✔
347
    }
7✔
348

349
    async listFlexClusters(options: FetchOptions<operations["listFlexClusters"]>) {
2✔
350
        const { data, error, response } = await this.client.GET("/api/atlas/v2/groups/{groupId}/flexClusters", options);
×
351
        if (error) {
×
352
            throw ApiClientError.fromError(response, error);
×
353
        }
×
354
        return data;
×
355
    }
×
356

357
    async createFlexCluster(options: FetchOptions<operations["createFlexCluster"]>) {
2✔
358
        const { data, error, response } = await this.client.POST(
×
359
            "/api/atlas/v2/groups/{groupId}/flexClusters",
×
360
            options
×
361
        );
×
362
        if (error) {
×
363
            throw ApiClientError.fromError(response, error);
×
364
        }
×
365
        return data;
×
366
    }
×
367

368
    async deleteFlexCluster(options: FetchOptions<operations["deleteFlexCluster"]>) {
2✔
369
        const { error, response } = await this.client.DELETE(
×
370
            "/api/atlas/v2/groups/{groupId}/flexClusters/{name}",
×
371
            options
×
372
        );
×
373
        if (error) {
×
374
            throw ApiClientError.fromError(response, error);
×
375
        }
×
376
    }
×
377

378
    async getFlexCluster(options: FetchOptions<operations["getFlexCluster"]>) {
2✔
379
        const { data, error, response } = await this.client.GET(
×
380
            "/api/atlas/v2/groups/{groupId}/flexClusters/{name}",
×
381
            options
×
382
        );
×
383
        if (error) {
×
384
            throw ApiClientError.fromError(response, error);
×
385
        }
×
386
        return data;
×
387
    }
×
388

389
    async listOrganizations(options?: FetchOptions<operations["listOrganizations"]>) {
2✔
390
        const { data, error, response } = await this.client.GET("/api/atlas/v2/orgs", options);
7✔
391
        if (error) {
7!
392
            throw ApiClientError.fromError(response, error);
×
393
        }
×
394
        return data;
7✔
395
    }
7✔
396

397
    async listOrganizationProjects(options: FetchOptions<operations["listOrganizationProjects"]>) {
2✔
398
        const { data, error, response } = await this.client.GET("/api/atlas/v2/orgs/{orgId}/groups", options);
×
399
        if (error) {
×
400
            throw ApiClientError.fromError(response, error);
×
401
        }
×
402
        return data;
×
403
    }
×
404

405
    // DO NOT EDIT. This is auto-generated code.
406
}
2✔
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2025 Coveralls, Inc