Skip to content

Commit 5c43a16

Browse files
authored
chore: add atlas tests (#83)
1 parent 795858a commit 5c43a16

20 files changed

+2739
-1961
lines changed

.github/workflows/code_health.yaml

Lines changed: 18 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,20 @@ jobs:
2020
- name: Run style check
2121
run: npm run check
2222

23+
check-generate:
24+
runs-on: ubuntu-latest
25+
steps:
26+
- uses: GitHubSecurityLab/actions-permissions/monitor@v1
27+
- uses: actions/checkout@v4
28+
- uses: actions/setup-node@v4
29+
with:
30+
node-version-file: package.json
31+
cache: "npm"
32+
- name: Install dependencies
33+
run: npm ci
34+
- name: Run style check
35+
run: npm run generate
36+
2337
run-tests:
2438
strategy:
2539
matrix:
@@ -29,12 +43,6 @@ jobs:
2943
steps:
3044
- uses: GitHubSecurityLab/actions-permissions/monitor@v1
3145
if: matrix.os != 'windows-latest'
32-
- name: Install keyring deps on Ubuntu
33-
if: matrix.os == 'ubuntu-latest'
34-
run: |
35-
sudo apt update -y
36-
sudo apt install -y gnome-keyring libdbus-1-dev
37-
3846
- uses: actions/checkout@v4
3947
- uses: actions/setup-node@v4
4048
with:
@@ -43,6 +51,10 @@ jobs:
4351
- name: Install dependencies
4452
run: npm ci
4553
- name: Run tests
54+
env:
55+
MDB_MCP_API_CLIENT_ID: ${{ secrets.TEST_ATLAS_CLIENT_ID }}
56+
MDB_MCP_API_CLIENT_SECRET: ${{ secrets.TEST_ATLAS_CLIENT_SECRET }}
57+
MDB_MCP_API_BASE_URL: ${{ vars.TEST_ATLAS_BASE_URL }}
4658
run: npm test
4759
- name: Coveralls GitHub Action
4860
uses: coverallsapp/[email protected]

README.md

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -98,12 +98,14 @@ You may experiment asking `Can you connect to my mongodb instance?`.
9898

9999
#### MongoDB Atlas Tools
100100

101-
- `atlas-list-clusters` - Lists MongoDB Atlas clusters
101+
- `atlas-list-orgs` - Lists MongoDB Atlas organizations
102102
- `atlas-list-projects` - Lists MongoDB Atlas projects
103+
- `atlas-create-project` - Creates a new MongoDB Atlas project
104+
- `atlas-list-clusters` - Lists MongoDB Atlas clusters
103105
- `atlas-inspect-cluster` - Inspect a specific MongoDB Atlas cluster
104106
- `atlas-create-free-cluster` - Create a free MongoDB Atlas cluster
105-
- `atlas-create-access-list` - Configure IP/CIDR access list for MongoDB Atlas clusters
106107
- `atlas-inspect-access-list` - Inspect IP/CIDR ranges with access to MongoDB Atlas clusters
108+
- `atlas-create-access-list` - Configure IP/CIDR access list for MongoDB Atlas clusters
107109
- `atlas-list-db-users` - List MongoDB Atlas database users
108110
- `atlas-create-db-user` - List MongoDB Atlas database users
109111

jest.config.js

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ export default {
33
preset: "ts-jest/presets/default-esm",
44
testEnvironment: "node",
55
extensionsToTreatAsEsm: [".ts"],
6+
testTimeout: 3600000, // 3600 seconds
67
moduleNameMapper: {
78
"^(\\.{1,2}/.*)\\.js$": "$1", // Map .js to real paths for ESM
89
},

package-lock.json

Lines changed: 1721 additions & 1890 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

scripts/apply.ts

Lines changed: 54 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -2,18 +2,22 @@ import fs from "fs/promises";
22
import { OpenAPIV3_1 } from "openapi-types";
33
import argv from "yargs-parser";
44

5-
function findParamFromRef(ref: string, openapi: OpenAPIV3_1.Document): OpenAPIV3_1.ParameterObject {
5+
function findObjectFromRef<T>(obj: T | OpenAPIV3_1.ReferenceObject, openapi: OpenAPIV3_1.Document): T {
6+
const ref = (obj as OpenAPIV3_1.ReferenceObject).$ref;
7+
if (ref === undefined) {
8+
return obj as T;
9+
}
610
const paramParts = ref.split("/");
711
paramParts.shift(); // Remove the first part which is always '#'
8-
let param: any = openapi; // eslint-disable-line @typescript-eslint/no-explicit-any
12+
let foundObj: any = openapi; // eslint-disable-line @typescript-eslint/no-explicit-any
913
while (true) {
1014
const part = paramParts.shift();
1115
if (!part) {
1216
break;
1317
}
14-
param = param[part];
18+
foundObj = foundObj[part];
1519
}
16-
return param;
20+
return foundObj as T;
1721
}
1822

1923
async function main() {
@@ -32,6 +36,7 @@ async function main() {
3236
operationId: string;
3337
requiredParams: boolean;
3438
tag: string;
39+
hasResponseBody: boolean;
3540
}[] = [];
3641

3742
const openapi = JSON.parse(specFile) as OpenAPIV3_1.Document;
@@ -44,13 +49,27 @@ async function main() {
4449
}
4550

4651
let requiredParams = !!operation.requestBody;
52+
let hasResponseBody = false;
53+
for (const code in operation.responses) {
54+
try {
55+
const httpCode = parseInt(code, 10);
56+
if (httpCode >= 200 && httpCode < 300) {
57+
const response = operation.responses[code];
58+
const responseObject = findObjectFromRef(response, openapi);
59+
if (responseObject.content) {
60+
for (const contentType in responseObject.content) {
61+
const content = responseObject.content[contentType];
62+
hasResponseBody = !!content.schema;
63+
}
64+
}
65+
}
66+
} catch {
67+
continue;
68+
}
69+
}
4770

4871
for (const param of operation.parameters || []) {
49-
const ref = (param as OpenAPIV3_1.ReferenceObject).$ref as string | undefined;
50-
let paramObject: OpenAPIV3_1.ParameterObject = param as OpenAPIV3_1.ParameterObject;
51-
if (ref) {
52-
paramObject = findParamFromRef(ref, openapi);
53-
}
72+
const paramObject = findObjectFromRef(param, openapi);
5473
if (paramObject.in === "path") {
5574
requiredParams = true;
5675
}
@@ -61,27 +80,45 @@ async function main() {
6180
method: method.toUpperCase(),
6281
operationId: operation.operationId || "",
6382
requiredParams,
83+
hasResponseBody,
6484
tag: operation.tags[0],
6585
});
6686
}
6787
}
6888

6989
const operationOutput = operations
7090
.map((operation) => {
71-
const { operationId, method, path, requiredParams } = operation;
91+
const { operationId, method, path, requiredParams, hasResponseBody } = operation;
7292
return `async ${operationId}(options${requiredParams ? "" : "?"}: FetchOptions<operations["${operationId}"]>) {
73-
const { data } = await this.client.${method}("${path}", options);
74-
return data;
75-
}
93+
${hasResponseBody ? `const { data } = ` : ``}await this.client.${method}("${path}", options);
94+
${
95+
hasResponseBody
96+
? `return data;
97+
`
98+
: ``
99+
}}
76100
`;
77101
})
78102
.join("\n");
79103

80104
const templateFile = (await fs.readFile(file, "utf8")) as string;
81-
const output = templateFile.replace(
82-
/\/\/ DO NOT EDIT\. This is auto-generated code\.\n.*\/\/ DO NOT EDIT\. This is auto-generated code\./g,
83-
operationOutput
84-
);
105+
const templateLines = templateFile.split("\n");
106+
let outputLines: string[] = [];
107+
let addLines = true;
108+
for (const line of templateLines) {
109+
if (line.includes("DO NOT EDIT. This is auto-generated code.")) {
110+
addLines = !addLines;
111+
outputLines.push(line);
112+
if (!addLines) {
113+
outputLines.push(operationOutput);
114+
}
115+
continue;
116+
}
117+
if (addLines) {
118+
outputLines.push(line);
119+
}
120+
}
121+
const output = outputLines.join("\n");
85122

86123
await fs.writeFile(file, output, "utf8");
87124
}

scripts/filter.ts

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,14 +22,19 @@ function filterOpenapi(openapi: OpenAPIV3_1.Document): OpenAPIV3_1.Document {
2222
"listOrganizations",
2323
"getProject",
2424
"createProject",
25+
"deleteProject",
2526
"listClusters",
2627
"getCluster",
2728
"createCluster",
29+
"deleteCluster",
2830
"listClustersForAllProjects",
2931
"createDatabaseUser",
32+
"deleteDatabaseUser",
3033
"listDatabaseUsers",
3134
"listProjectIpAccessLists",
3235
"createProjectIpAccessList",
36+
"deleteProjectIpAccessList",
37+
"listOrganizationProjects",
3338
];
3439

3540
const filteredPaths = {};

src/common/atlas/apiClient.ts

Lines changed: 34 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -117,8 +117,8 @@ export class ApiClient {
117117
}
118118

119119
// DO NOT EDIT. This is auto-generated code.
120-
async listOrganizations(options?: FetchOptions<operations["listOrganizations"]>) {
121-
const { data } = await this.client.GET("/api/atlas/v2/orgs", options);
120+
async listClustersForAllProjects(options?: FetchOptions<operations["listClustersForAllProjects"]>) {
121+
const { data } = await this.client.GET("/api/atlas/v2/clusters", options);
122122
return data;
123123
}
124124

@@ -132,16 +132,29 @@ export class ApiClient {
132132
return data;
133133
}
134134

135-
async listClustersForAllProjects(options?: FetchOptions<operations["listClustersForAllProjects"]>) {
136-
const { data } = await this.client.GET("/api/atlas/v2/clusters", options);
137-
return data;
135+
async deleteProject(options: FetchOptions<operations["deleteProject"]>) {
136+
await this.client.DELETE("/api/atlas/v2/groups/{groupId}", options);
138137
}
139138

140139
async getProject(options: FetchOptions<operations["getProject"]>) {
141140
const { data } = await this.client.GET("/api/atlas/v2/groups/{groupId}", options);
142141
return data;
143142
}
144143

144+
async listProjectIpAccessLists(options: FetchOptions<operations["listProjectIpAccessLists"]>) {
145+
const { data } = await this.client.GET("/api/atlas/v2/groups/{groupId}/accessList", options);
146+
return data;
147+
}
148+
149+
async createProjectIpAccessList(options: FetchOptions<operations["createProjectIpAccessList"]>) {
150+
const { data } = await this.client.POST("/api/atlas/v2/groups/{groupId}/accessList", options);
151+
return data;
152+
}
153+
154+
async deleteProjectIpAccessList(options: FetchOptions<operations["deleteProjectIpAccessList"]>) {
155+
await this.client.DELETE("/api/atlas/v2/groups/{groupId}/accessList/{entryValue}", options);
156+
}
157+
145158
async listClusters(options: FetchOptions<operations["listClusters"]>) {
146159
const { data } = await this.client.GET("/api/atlas/v2/groups/{groupId}/clusters", options);
147160
return data;
@@ -152,13 +165,12 @@ export class ApiClient {
152165
return data;
153166
}
154167

155-
async listProjectIpAccessLists(options: FetchOptions<operations["listProjectIpAccessLists"]>) {
156-
const { data } = await this.client.GET("/api/atlas/v2/groups/{groupId}/accessList", options);
157-
return data;
168+
async deleteCluster(options: FetchOptions<operations["deleteCluster"]>) {
169+
await this.client.DELETE("/api/atlas/v2/groups/{groupId}/clusters/{clusterName}", options);
158170
}
159171

160-
async createProjectIpAccessList(options: FetchOptions<operations["createProjectIpAccessList"]>) {
161-
const { data } = await this.client.POST("/api/atlas/v2/groups/{groupId}/accessList", options);
172+
async getCluster(options: FetchOptions<operations["getCluster"]>) {
173+
const { data } = await this.client.GET("/api/atlas/v2/groups/{groupId}/clusters/{clusterName}", options);
162174
return data;
163175
}
164176

@@ -172,9 +184,19 @@ export class ApiClient {
172184
return data;
173185
}
174186

175-
async getCluster(options: FetchOptions<operations["getCluster"]>) {
176-
const { data } = await this.client.GET("/api/atlas/v2/groups/{groupId}/clusters/{clusterName}", options);
187+
async deleteDatabaseUser(options: FetchOptions<operations["deleteDatabaseUser"]>) {
188+
await this.client.DELETE("/api/atlas/v2/groups/{groupId}/databaseUsers/{databaseName}/{username}", options);
189+
}
190+
191+
async listOrganizations(options?: FetchOptions<operations["listOrganizations"]>) {
192+
const { data } = await this.client.GET("/api/atlas/v2/orgs", options);
177193
return data;
178194
}
195+
196+
async listOrganizationProjects(options: FetchOptions<operations["listOrganizationProjects"]>) {
197+
const { data } = await this.client.GET("/api/atlas/v2/orgs/{orgId}/groups", options);
198+
return data;
199+
}
200+
179201
// DO NOT EDIT. This is auto-generated code.
180202
}

0 commit comments

Comments
 (0)