Skip to content

Commit

Permalink
feat(MongoMemoryServer): allow setting if port generation is enabeld …
Browse files Browse the repository at this point in the history
…or not

fixes #816
  • Loading branch information
hasezoey committed Jul 15, 2024
1 parent 129af60 commit 6c4daed
Show file tree
Hide file tree
Showing 3 changed files with 168 additions and 7 deletions.
35 changes: 28 additions & 7 deletions packages/mongodb-memory-server-core/src/MongoMemoryServer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,25 @@ const log = debug('MongoMS:MongoMemoryServer');
* Type with automatic options removed
* "auth" is automatically handled and set via {@link AutomaticAuth}
*/
export type MemoryServerInstanceOpts = Omit<MongoMemoryInstanceOpts, 'auth'>;
export type MemoryServerInstanceOpts = Omit<MongoMemoryInstanceOpts, 'auth'> & ExtraInstanceOpts;

/**
* Extra Instance options specifically for {@link MongoMemoryServer}
*/
export interface ExtraInstanceOpts {
/**
* Change if port generation is enabled or not.
*
* If enabled and a port is set, that port is tried, if locked a new one will be generated.
* If disabled and a port is set, only that port is tried, if locked a error will be thrown.
* If disabled and no port is set, will act as if enabled.
*
* This setting will get overwritten by `start`'s `forceSamePort` parameter if set
*
* @default true
*/
portGeneration?: boolean;
}

/**
* MongoMemoryServer Stored Options
Expand Down Expand Up @@ -303,10 +321,10 @@ export class MongoMemoryServer extends EventEmitter implements ManagerAdvanced {

/**
* Start the Mongod Instance
* @param forceSamePort Force to use the Same Port, if already an "instanceInfo" exists
* @param forceSamePort Force to use the port defined in `options.instance` (disabled port generation)
* @throws if state is not "new" or "stopped"
*/
async start(forceSamePort: boolean = false): Promise<void> {
async start(forceSamePort?: boolean): Promise<void> {
this.debug('start: Called .start() method');

switch (this._state) {
Expand Down Expand Up @@ -392,6 +410,7 @@ export class MongoMemoryServer extends EventEmitter implements ManagerAdvanced {

/**
* Construct Instance Starting Options
* @param forceSamePort Force to use the port defined in `options.instance` (disabled port generation)
*/
protected async getStartOptions(
forceSamePort: boolean = false
Expand Down Expand Up @@ -483,16 +502,18 @@ export class MongoMemoryServer extends EventEmitter implements ManagerAdvanced {

/**
* Internal Function to start an instance
* @param forceSamePort Force to use the Same Port, if already an "instanceInfo" exists
* @param forceSamePort Force to use the port defined in `options.instance` (disabled port generation)
* @private
*/
async _startUpInstance(forceSamePort: boolean = false): Promise<void> {
async _startUpInstance(forceSamePort?: boolean): Promise<void> {
this.debug('_startUpInstance: Called MongoMemoryServer._startUpInstance() method');

const useSamePort = forceSamePort ?? !(this.opts.instance?.portGeneration ?? true);

if (!isNullOrUndefined(this._instanceInfo)) {
this.debug('_startUpInstance: "instanceInfo" already defined, reusing instance');

if (!forceSamePort) {
if (!useSamePort) {
const newPort = await this.getNewPort(this._instanceInfo.port);
this._instanceInfo.instance.instanceOpts.port = newPort;
this._instanceInfo.port = newPort;
Expand All @@ -503,7 +524,7 @@ export class MongoMemoryServer extends EventEmitter implements ManagerAdvanced {
return;
}

const { mongodOptions, createAuth, data } = await this.getStartOptions(forceSamePort);
const { mongodOptions, createAuth, data } = await this.getStartOptions(useSamePort);
this.debug(`_startUpInstance: Creating new MongoDB instance with options:`, mongodOptions);

const instance = await MongoInstance.create(mongodOptions);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -458,6 +458,142 @@ describe('MongoMemoryServer', () => {
'Cannot start because "instance.mongodProcess" is already defined!'
);
});

describe('instance.portGeneration', () => {
it('should use a predefined port if "opts.instance.portGeneration" is "false"', async () => {
const predefinedPort = 30001;

const mongoServer = new MongoMemoryServer({
instance: { port: predefinedPort, portGeneration: false },
});
const newPortSpy = jest
// @ts-expect-error "getNewPort" is protected
.spyOn(mongoServer, 'getNewPort')
.mockImplementation(() => fail('Expected this function to not be called'));

await mongoServer.start();

expect(newPortSpy).not.toHaveBeenCalled();
// @ts-expect-error "_instanceInfo" is protected
expect(mongoServer._instanceInfo!.port).toStrictEqual(predefinedPort);

await mongoServer.stop();
});

it('should Error if a predefined port is already in use if "opts.instance.portGeneration" is "false"', async () => {
const predefinedPort = 30002;

const newPortSpy = jest
// @ts-expect-error "getNewPort" is protected
.spyOn(MongoMemoryServer.prototype, 'getNewPort')
.mockImplementation(() => fail('Expected this function to not be called'));

jest.spyOn(console, 'warn').mockImplementationOnce(() => void 0);

const mongoServer1 = new MongoMemoryServer({
instance: { port: predefinedPort, portGeneration: false },
});

const mongoServer2 = new MongoMemoryServer({
instance: { port: predefinedPort, portGeneration: false },
});

await mongoServer1.start();

await expect(() => mongoServer2.start()).rejects.toMatchSnapshot();

expect(newPortSpy).not.toHaveBeenCalled();
// @ts-expect-error "_instanceInfo" is protected
expect(mongoServer1._instanceInfo!.port).toStrictEqual(predefinedPort);
expect(console.warn).toHaveBeenCalledTimes(1);

await mongoServer1.stop();
});

it('should generate a new port if the predefined port is already in use and "opts.instance.portGeneration" is "true"', async () => {
const predefinedPort = 30003;

const newPortSpy = jest
// @ts-expect-error "getNewPort" is protected
.spyOn(MongoMemoryServer.prototype, 'getNewPort');

const mongoServer1 = new MongoMemoryServer({
instance: { port: predefinedPort, portGeneration: false },
});

const mongoServer2 = new MongoMemoryServer({
instance: { port: predefinedPort, portGeneration: true },
});

await mongoServer1.start();

expect(newPortSpy).not.toHaveBeenCalled();

await mongoServer2.start();

expect(newPortSpy).toHaveBeenCalledTimes(1);

await mongoServer1.stop();
await mongoServer2.stop();
});

it('should overwrite "opts.instance.portGeneration" if "forceSamePort" is set ("forceSamePort true" case)', async () => {
const predefinedPort = 30004;

const newPortSpy = jest
// @ts-expect-error "getNewPort" is protected
.spyOn(MongoMemoryServer.prototype, 'getNewPort')
.mockImplementation(() => fail('Expected this function to not be called'));

jest.spyOn(console, 'warn').mockImplementationOnce(() => void 0);

const mongoServer1 = new MongoMemoryServer({
instance: { port: predefinedPort, portGeneration: false },
});

const mongoServer2 = new MongoMemoryServer({
instance: { port: predefinedPort, portGeneration: true },
});

await mongoServer1.start();

await expect(() => mongoServer2.start(true)).rejects.toMatchSnapshot();

expect(newPortSpy).not.toHaveBeenCalled();
// @ts-expect-error "_instanceInfo" is protected
expect(mongoServer1._instanceInfo!.port).toStrictEqual(predefinedPort);
expect(console.warn).toHaveBeenCalledTimes(1);

await mongoServer1.stop();
});

it('should overwrite "opts.instance.portGeneration" if "forceSamePort" is set ("forceSamePort false" case)', async () => {
const predefinedPort = 30005;

const newPortSpy = jest
// @ts-expect-error "getNewPort" is protected
.spyOn(MongoMemoryServer.prototype, 'getNewPort');

const mongoServer1 = new MongoMemoryServer({
instance: { port: predefinedPort, portGeneration: false },
});

const mongoServer2 = new MongoMemoryServer({
instance: { port: predefinedPort, portGeneration: false },
});

await mongoServer1.start();

expect(newPortSpy).not.toHaveBeenCalled();

await mongoServer2.start(false);

expect(newPortSpy).toHaveBeenCalledTimes(1);

await mongoServer1.stop();
await mongoServer2.stop();
});
});
});

describe('ensureInstance()', () => {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,10 @@ This warning is because the mentioned storage engine is explicitly used and mong
]
`;

exports[`MongoMemoryServer start() instance.portGeneration should Error if a predefined port is already in use if "opts.instance.portGeneration" is "false" 1`] = `[Error: Port "30002" already in use]`;

exports[`MongoMemoryServer start() instance.portGeneration should overwrite "opts.instance.portGeneration" if "forceSamePort" is set ("forceSamePort true" case) 1`] = `[Error: Port "30004" already in use]`;

exports[`MongoMemoryServer start() should throw an error if state is not "new" or "stopped" 1`] = `
"Incorrect State for operation: \\"starting\\", allowed States: \\"[new,stopped]\\"
This may be because of using a v6.x way of calling functions, look at the following guide if anything applies:
Expand Down

0 comments on commit 6c4daed

Please sign in to comment.