-
-
Notifications
You must be signed in to change notification settings - Fork 184
/
MongoMemoryServer.js
201 lines (172 loc) · 5.56 KB
/
MongoMemoryServer.js
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
/* @flow */
import type { ChildProcess } from 'child_process';
import uuid from 'uuid/v4';
import tmp from 'tmp';
import getport from 'get-port';
import Debug from 'debug';
import MongoInstance from './util/MongoInstance';
tmp.setGracefulCleanup();
export type MongoMemoryServerOptsT = {
instance: {
port?: ?number,
dbPath?: string,
dbName?: string,
storageEngine?: string,
debug?: boolean | Function,
},
binary: {
version?: string,
downloadDir?: string,
platform?: string,
arch?: string,
ssl?: boolean,
debug?: boolean | Function,
},
debug?: boolean,
spawn: any,
autoStart?: boolean,
};
export type MongoInstanceDataT = {
port: number,
dbPath: string,
dbName: string,
uri: string,
storageEngine: string,
instance: MongoInstance,
childProcess: ChildProcess,
tmpDir?: {
name: string,
removeCallback: Function,
},
};
async function generateDbName(dbName?: string): Promise<string> {
return dbName || uuid();
}
async function generateConnectionString(port: number, dbName: string): Promise<string> {
return `mongodb://localhost:${port}/${dbName}`;
}
export default class MongoMemoryServer {
isRunning: boolean = false;
runningInstance: ?Promise<MongoInstanceDataT>;
opts: MongoMemoryServerOptsT;
debug: Function;
constructor(opts?: $Shape<MongoMemoryServerOptsT> = {}) {
this.opts = opts;
if (!this.opts.instance) this.opts.instance = {};
if (!this.opts.binary) this.opts.binary = {};
this.debug = (msg: string) => {
if (this.opts.debug) {
console.log(msg);
}
};
// autoStart by default
if (!opts.hasOwnProperty('autoStart') || opts.autoStart) {
this.debug('Autostarting MongoDB instance...');
this.start();
}
}
async start(): Promise<boolean> {
if (this.runningInstance) {
throw new Error(
'MongoDB instance already in status startup/running/error. Use opts.debug = true for more info.'
);
}
this.runningInstance = this._startUpInstance()
.catch(err => {
if (err.message === 'Mongod shutting down' || err === 'Mongod shutting down') {
this.debug(`Mongodb does not started. Trying to start on another port one more time...`);
this.opts.instance.port = null;
return this._startUpInstance();
}
throw err;
})
.catch(err => {
if (!this.opts.debug) {
throw new Error(
`${err.message}\n\nUse debug option for more info: ` +
`new MongoMemoryServer({ debug: true })`
);
}
throw err;
});
return this.runningInstance.then(() => true);
}
async _startUpInstance(): Promise<MongoInstanceDataT> {
const data = {};
let tmpDir;
const instOpts = this.opts.instance;
data.port = await getport(instOpts.port);
this.debug = Debug(`Mongo[${data.port}]`);
this.debug.enabled = !!this.opts.debug;
data.dbName = await generateDbName(instOpts.dbName);
data.uri = await generateConnectionString(data.port, data.dbName);
data.storageEngine = instOpts.storageEngine || 'ephemeralForTest';
if (instOpts.dbPath) {
data.dbPath = instOpts.dbPath;
} else {
tmpDir = tmp.dirSync({ prefix: 'mongo-mem-', unsafeCleanup: true });
data.dbPath = tmpDir.name;
}
this.debug(`Starting MongoDB instance with following options: ${JSON.stringify(data)}`);
// Download if not exists mongo binaries in ~/.mongodb-prebuilt
// After that startup MongoDB instance
const instance = await MongoInstance.run({
instance: {
port: data.port,
storageEngine: data.storageEngine,
dbPath: data.dbPath,
debug: this.opts.instance.debug,
},
binary: this.opts.binary,
spawn: this.opts.spawn,
debug: this.debug,
});
data.instance = instance;
data.childProcess = instance.childProcess;
data.tmpDir = tmpDir;
return data;
}
async stop(): Promise<boolean> {
const { instance, port, tmpDir } = (await this.getInstanceData(): MongoInstanceDataT);
this.debug(`Shutdown MongoDB server on port ${port} with pid ${instance.getPid() || ''}`);
await instance.kill();
if (tmpDir) {
this.debug(`Removing tmpDir ${tmpDir.name}`);
tmpDir.removeCallback();
}
this.runningInstance = null;
return true;
}
async getInstanceData(): Promise<MongoInstanceDataT> {
if (this.runningInstance) {
return this.runningInstance;
}
throw new Error(
'Database instance is not running. You should start database by calling start() method. BTW it should start automatically if opts.autoStart!=false. Also you may provide opts.debug=true for more info.'
);
}
async getUri(otherDbName?: string | boolean = false): Promise<string> {
const { uri, port } = (await this.getInstanceData(): MongoInstanceDataT);
// IF true OR string
if (otherDbName) {
if (typeof otherDbName === 'string') {
// generate uri with provided DB name on existed DB instance
return generateConnectionString(port, otherDbName);
}
// generate new random db name
return generateConnectionString(port, await generateDbName());
}
return uri;
}
async getConnectionString(otherDbName: string | boolean = false): Promise<string> {
return this.getUri(otherDbName);
}
async getPort(): Promise<number> {
const { port } = (await this.getInstanceData(): MongoInstanceDataT);
return port;
}
async getDbPath(): Promise<string> {
const { dbPath } = (await this.getInstanceData(): MongoInstanceDataT);
return dbPath;
}
}