-
Notifications
You must be signed in to change notification settings - Fork 41
refactor: split test helpers #103
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,113 @@ | ||
import runner, { MongoCluster } from "mongodb-runner"; | ||
import path from "path"; | ||
import fs from "fs/promises"; | ||
import { MongoClient, ObjectId } from "mongodb"; | ||
import { IntegrationTest, setupIntegrationTest } from "../../helpers.js"; | ||
import { UserConfig, config } from "../../../../src/config.js"; | ||
|
||
interface MongoDBIntegrationTest { | ||
mongoClient: () => MongoClient; | ||
connectionString: () => string; | ||
connectMcpClient: () => Promise<void>; | ||
randomDbName: () => string; | ||
} | ||
|
||
export function describeWithMongoDB( | ||
name: number | string | Function | jest.FunctionLike, | ||
fn: (integration: IntegrationTest & MongoDBIntegrationTest) => void | ||
): void { | ||
describe("mongodb", () => { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. do we benefit from having this parent MongoDB describe? we could also set the variables inside the child There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. the reason why I added the main one was because need separation of beforeAll/AfterAll from main setup and MongoDB one other ordering is not correct |
||
const integration = setupIntegrationTest(); | ||
const mdbIntegration = setupMongoDBIntegrationTest(integration); | ||
describe(name, () => { | ||
fn({ ...integration, ...mdbIntegration }); | ||
}); | ||
}); | ||
} | ||
|
||
export function setupMongoDBIntegrationTest( | ||
integration: IntegrationTest, | ||
userConfig: UserConfig = config | ||
): MongoDBIntegrationTest { | ||
let mongoCluster: runner.MongoCluster | undefined; | ||
let mongoClient: MongoClient | undefined; | ||
let randomDbName: string; | ||
|
||
beforeEach(async () => { | ||
randomDbName = new ObjectId().toString(); | ||
}); | ||
|
||
afterEach(async () => { | ||
await integration.mcpServer().session.close(); | ||
config.connectionString = undefined; | ||
|
||
await mongoClient?.close(); | ||
mongoClient = undefined; | ||
}); | ||
|
||
beforeAll(async function () { | ||
// Downloading Windows executables in CI takes a long time because | ||
// they include debug symbols... | ||
const tmpDir = path.join(__dirname, "..", "..", "..", "tmp"); | ||
await fs.mkdir(tmpDir, { recursive: true }); | ||
|
||
// On Windows, we may have a situation where mongod.exe is not fully released by the OS | ||
// before we attempt to run it again, so we add a retry. | ||
let dbsDir = path.join(tmpDir, "mongodb-runner", "dbs"); | ||
for (let i = 0; i < 10; i++) { | ||
try { | ||
mongoCluster = await MongoCluster.start({ | ||
tmpDir: dbsDir, | ||
logDir: path.join(tmpDir, "mongodb-runner", "logs"), | ||
topology: "standalone", | ||
}); | ||
|
||
return; | ||
} catch (err) { | ||
if (i < 5) { | ||
// Just wait a little bit and retry | ||
console.error(`Failed to start cluster in ${dbsDir}, attempt ${i}: ${err}`); | ||
await new Promise((resolve) => setTimeout(resolve, 1000)); | ||
} else { | ||
// If we still fail after 5 seconds, try another db dir | ||
console.error( | ||
`Failed to start cluster in ${dbsDir}, attempt ${i}: ${err}. Retrying with a new db dir.` | ||
); | ||
dbsDir = path.join(tmpDir, "mongodb-runner", `dbs${i - 5}`); | ||
} | ||
} | ||
} | ||
|
||
throw new Error("Failed to start cluster after 10 attempts"); | ||
}, 120_000); | ||
|
||
afterAll(async function () { | ||
await mongoCluster?.close(); | ||
mongoCluster = undefined; | ||
}); | ||
|
||
const getConnectionString = () => { | ||
if (!mongoCluster) { | ||
throw new Error("beforeAll() hook not ran yet"); | ||
} | ||
|
||
return mongoCluster.connectionString; | ||
}; | ||
|
||
return { | ||
mongoClient: () => { | ||
if (!mongoClient) { | ||
mongoClient = new MongoClient(getConnectionString()); | ||
} | ||
return mongoClient; | ||
}, | ||
connectionString: getConnectionString, | ||
connectMcpClient: async () => { | ||
await integration.mcpClient().callTool({ | ||
name: "connect", | ||
arguments: { options: [{ connectionString: getConnectionString() }] }, | ||
}); | ||
}, | ||
randomDbName: () => randomDbName, | ||
}; | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
what if we were to mix MongoDB + Atlas flows? would that be its own describe statement?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think we would need to do something like
describeAtlasMongoDB(...)