Skip to content
Open
Show file tree
Hide file tree
Changes from 14 commits
Commits
Show all changes
25 commits
Select commit Hold shift + click to select a range
a317cce
Add additional options to MongoCluster
blink1073 Nov 10, 2025
b6beb60
wip add auth support
blink1073 Nov 10, 2025
bde597f
finish support for auth and ssl
blink1073 Nov 11, 2025
462f151
finish sharded support
blink1073 Nov 11, 2025
28a10e2
align more with mongodb-runner design principles
addaleax Nov 14, 2025
5e383ec
fix handling of arbiters
blink1073 Nov 14, 2025
e475696
Add RSMemberOptions
blink1073 Nov 14, 2025
625c540
fixup: use actual TagSet type
addaleax Nov 17, 2025
47de6fd
feat(mongodb-runner): add ability to use a config file
addaleax Nov 17, 2025
4c756a1
fixup: clean up replset and shard arg handling
addaleax Nov 18, 2025
23bb0d3
fixup: allow passing CLI args as env vars, add verbose debug mode dir…
addaleax Nov 18, 2025
d159b3d
fixup: be more correct/more explicit in replset hosts test
addaleax Nov 18, 2025
fea6ceb
add tests, fix behaviors
addaleax Nov 18, 2025
74b8c93
fixup: check mongos list in config db
addaleax Nov 18, 2025
008bfdf
fixup: CR, use 8.x in tests by default
addaleax Nov 18, 2025
04249ed
fixup: version assertions
addaleax Nov 18, 2025
4746fa0
fixup: cr, export interfaces, fix auth test for 8.x
addaleax Nov 18, 2025
acfe911
fixup: support TLS on internal client out of the box
addaleax Nov 24, 2025
401163c
fixup: full flexibility for individual shards
addaleax Nov 24, 2025
42cc373
chore: add documentation
addaleax Nov 24, 2025
d919fe9
fixup: bump to Node.js 22.x in CI
addaleax Nov 24, 2025
41b1b32
fixup: update tests
addaleax Nov 25, 2025
ec2d585
Merge remote-tracking branch 'origin/main' into extra-params
addaleax Nov 28, 2025
80d81fc
fixup: deduplicate yargs env() entry
addaleax Nov 28, 2025
0b91dcd
fixup: take care of shards in config file
addaleax Nov 28, 2025
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 7 additions & 1 deletion packages/mongodb-runner/src/cli.ts
Original file line number Diff line number Diff line change
Expand Up @@ -74,7 +74,10 @@ import type { MongoClientOptions } from 'mongodb';
type: 'string',
describe: 'Configure OIDC authentication on the server',
})
.config()

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Haha I can't believe I missed this option. 😅

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I verified that it passes options through that aren't defined in the cli options, so we don't need to add all of the new options to the cli.

.env('MONGODB_RUNNER')
.option('debug', { type: 'boolean', describe: 'Enable debug output' })
.option('verbose', { type: 'boolean', describe: 'Enable verbose output' })
.command('start', 'Start a MongoDB instance')
.command('stop', 'Stop a MongoDB instance')
.command('prune', 'Clean up metadata for any dead MongoDB instances')
Expand All @@ -86,9 +89,12 @@ import type { MongoClientOptions } from 'mongodb';
.demandCommand(1, 'A command needs to be provided')
.help().argv;
const [command, ...args] = argv._.map(String);
if (argv.debug) {
if (argv.debug || argv.verbose) {
createDebug.enable('mongodb-runner');
}
if (argv.verbose) {
createDebug.enable('mongodb-runner:*');
}

if (argv.oidc && process.platform !== 'linux') {
console.warn(
Expand Down
1 change: 1 addition & 0 deletions packages/mongodb-runner/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ export {
MongoCluster,
type MongoClusterEvents,
MongoClusterOptions,
MongoDBUserDoc,
} from './mongocluster';
export type { LogEntry } from './mongologreader';
export type { ConnectionString } from 'mongodb-connection-string-url';
Expand Down
178 changes: 177 additions & 1 deletion packages/mongodb-runner/src/mongocluster.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -125,7 +125,9 @@ describe('MongoCluster', function () {
const hello = await cluster.withClient(async (client) => {
return await client.db('admin').command({ hello: 1 });
});
expect(+hello.passives.length + +hello.hosts.length).to.equal(5);
expect(hello.hosts).to.have.lengthOf(1);
expect(hello.passives).to.have.lengthOf(3);
expect(hello.arbiters).to.have.lengthOf(1);
});

it('can spawn a 6.x sharded cluster', async function () {
Expand Down Expand Up @@ -343,4 +345,178 @@ describe('MongoCluster', function () {
baddoc: 1,
});
});

it('can pass custom arguments for replica set members', async function () {
cluster = await MongoCluster.start({
version: '6.x',
topology: 'replset',
tmpDir,
rsMembers: [
{ args: ['--setParameter', 'cursorTimeoutMillis=60000'] },
{ args: ['--setParameter', 'cursorTimeoutMillis=50000'] },
],
});

expect(cluster.connectionString).to.be.a('string');
expect(cluster.serverVersion).to.match(/^6\./);
const hello = await cluster.withClient(async (client) => {
return await client.db('admin').command({ hello: 1 });
});
expect(hello.hosts).to.have.lengthOf(1);
expect(hello.passives).to.have.lengthOf(1);

const servers = cluster['servers'];
expect(servers).to.have.lengthOf(2);
const values = await Promise.all(
servers.map((srv) =>
srv.withClient(async (client) => {
return await Promise.all([
client
.db('admin')
.command({ getParameter: 1, cursorTimeoutMillis: 1 }),
client.db('admin').command({ hello: 1 }),
]);
}),
),
);

expect(
values.map((v) => [v[0].cursorTimeoutMillis, v[1].isWritablePrimary]),
).to.deep.equal([
[60000, true],
[50000, false],
]);
});

it('can pass custom arguments for shards', async function () {
cluster = await MongoCluster.start({
version: '6.x',
topology: 'sharded',
tmpDir,
secondaries: 0,
shardArgs: [
['--setParameter', 'cursorTimeoutMillis=60000'],
['--setParameter', 'cursorTimeoutMillis=50000'],
],
});

expect(cluster.connectionString).to.be.a('string');
expect(cluster.serverVersion).to.match(/^6\./);

const shards = cluster['shards'];
expect(shards).to.have.lengthOf(2);
const values = await Promise.all(
shards.map((srv) =>
srv.withClient(async (client) => {
return await Promise.all([
client
.db('admin')
.command({ getParameter: 1, cursorTimeoutMillis: 1 }),
client.db('admin').command({ hello: 1 }),
]);
}),
),
);

expect(
values.map((v) => [
v[0].cursorTimeoutMillis,
v[1].setName === values[0][1].setName,
]),
).to.deep.equal([
[60000, true],
[50000, false],
]);
});

it('can pass custom arguments for mongoses', async function () {
cluster = await MongoCluster.start({
version: '6.x',
topology: 'sharded',
tmpDir,
secondaries: 0,
mongosArgs: [
['--setParameter', 'cursorTimeoutMillis=60000'],
['--setParameter', 'cursorTimeoutMillis=50000'],
],
});

expect(cluster.connectionString).to.be.a('string');
expect(cluster.serverVersion).to.match(/^6\./);

const mongoses = cluster['servers'];
expect(mongoses).to.have.lengthOf(2);
const values = await Promise.all(
mongoses.map((srv) =>
srv.withClient(async (client) => {
return await Promise.all([
client
.db('admin')
.command({ getParameter: 1, cursorTimeoutMillis: 1 }),
client.db('admin').command({ hello: 1 }),
]);
}),
),
);

const processIdForMongos = (v: any) =>
v[1].topologyVersion.processId.toHexString();
expect(
values.map((v) => [
v[0].cursorTimeoutMillis,
v[1].msg,
processIdForMongos(v) === processIdForMongos(values[0]),
]),
).to.deep.equal([
[60000, 'isdbgrid', true],
[50000, 'isdbgrid', false],
]);

const mongosList = await cluster.withClient(
async (client) =>
await client.db('config').collection('mongos').find().toArray(),
);
expect(mongosList).to.have.lengthOf(2);
});

it('can add authentication options and verify them after serialization', async function () {
cluster = await MongoCluster.start({
version: '6.x',
topology: 'sharded',
tmpDir,
secondaries: 1,
shards: 1,
users: [
{
username: 'testuser',
password: 'testpass',
roles: [{ role: 'readWriteAnyDatabase', db: 'admin' }],
},
],
mongosArgs: [[], []],
});
expect(cluster.connectionString).to.be.a('string');
expect(cluster.serverVersion).to.match(/^6\./);
expect(cluster.connectionString).to.include('testuser:testpass@');
await cluster.withClient(async (client) => {
const result = await client
.db('test')
.collection('test')
.insertOne({ foo: 42 });
expect(result.insertedId).to.exist;
});

cluster = await MongoCluster.deserialize(cluster.serialize());
expect(cluster.connectionString).to.include('testuser:testpass@');
const [doc, status] = await cluster.withClient(async (client) => {
return Promise.all([
client.db('test').collection('test').findOne({ foo: 42 }),
client.db('admin').command({ connectionStatus: 1 }),
] as const);
});
expect(doc?.foo).to.equal(42);
expect(status.authInfo.authenticatedUsers).to.deep.equal([
{ user: 'testuser', db: 'admin' },
]);
});
});
Loading
Loading