diff --git a/packages/mongodb-memory-server-core/src/MongoMemoryServer.ts b/packages/mongodb-memory-server-core/src/MongoMemoryServer.ts index 70bdd35d..8625a465 100644 --- a/packages/mongodb-memory-server-core/src/MongoMemoryServer.ts +++ b/packages/mongodb-memory-server-core/src/MongoMemoryServer.ts @@ -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; +export type MemoryServerInstanceOpts = Omit & 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 @@ -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 { + async start(forceSamePort?: boolean): Promise { this.debug('start: Called .start() method'); switch (this._state) { @@ -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 @@ -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 { + async _startUpInstance(forceSamePort?: boolean): Promise { 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; @@ -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); diff --git a/packages/mongodb-memory-server-core/src/__tests__/MongoMemoryServer.test.ts b/packages/mongodb-memory-server-core/src/__tests__/MongoMemoryServer.test.ts index f811acaf..08a27b2c 100644 --- a/packages/mongodb-memory-server-core/src/__tests__/MongoMemoryServer.test.ts +++ b/packages/mongodb-memory-server-core/src/__tests__/MongoMemoryServer.test.ts @@ -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()', () => { diff --git a/packages/mongodb-memory-server-core/src/__tests__/__snapshots__/MongoMemoryServer.test.ts.snap b/packages/mongodb-memory-server-core/src/__tests__/__snapshots__/MongoMemoryServer.test.ts.snap index 0116363f..3ebe5937 100644 --- a/packages/mongodb-memory-server-core/src/__tests__/__snapshots__/MongoMemoryServer.test.ts.snap +++ b/packages/mongodb-memory-server-core/src/__tests__/__snapshots__/MongoMemoryServer.test.ts.snap @@ -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: