Skip to content

Commit 4401834

Browse files
committed
feat(errors): unfiy binary X_OK checking and throwing errors
1 parent 68adbbf commit 4401834

File tree

6 files changed

+113
-15
lines changed

6 files changed

+113
-15
lines changed

docs/guides/error-warning-details.md

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -67,3 +67,11 @@ This Error gets thrown when this package runs on an unsupported architecture by
6767
## AuthNotObjectError
6868

6969
*extend documentation*
70+
71+
## InsufficientPermissionsError
72+
73+
*extend documentation*
74+
75+
## BinaryNotFoundError
76+
77+
*extend documentation*

packages/mongodb-memory-server-core/src/util/DryMongoBinary.ts

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,6 @@
1-
import { promises as fspromises, constants } from 'fs';
21
import debug from 'debug';
32
import { envToBool, resolveConfig, ResolveConfigVariables } from './resolveConfig';
4-
import { isNullOrUndefined, pathExists } from './utils';
3+
import { checkBinaryPermissions, isNullOrUndefined, pathExists } from './utils';
54
import * as path from 'path';
65
import { arch, homedir, platform } from 'os';
76
import findCacheDir from 'find-cache-dir';
@@ -148,9 +147,10 @@ export class DryMongoBinary {
148147
* @return System Binary path or undefined
149148
*/
150149
static async getSystemPath(systemBinary: string): Promise<string | undefined> {
150+
// REFACTOR: change this function to always return "string"
151151
log('getSystempath');
152152
try {
153-
await fspromises.access(systemBinary, constants.X_OK); // check if the provided path exists and has the execute bit for current user
153+
await checkBinaryPermissions(systemBinary);
154154

155155
log(`getSystemPath: found system binary path at "${systemBinary}"`);
156156

packages/mongodb-memory-server-core/src/util/MongoInstance.ts

Lines changed: 9 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -2,11 +2,17 @@ import { ChildProcess, fork, spawn, SpawnOptions } from 'child_process';
22
import * as path from 'path';
33
import { MongoBinary, MongoBinaryOpts } from './MongoBinary';
44
import debug from 'debug';
5-
import { assertion, uriTemplate, isNullOrUndefined, killProcess, ManagerBase } from './utils';
5+
import {
6+
assertion,
7+
uriTemplate,
8+
isNullOrUndefined,
9+
killProcess,
10+
ManagerBase,
11+
checkBinaryPermissions,
12+
} from './utils';
613
import { lt } from 'semver';
714
import { EventEmitter } from 'events';
815
import { MongoClient, MongoClientOptions, MongoNetworkError } from 'mongodb';
9-
import { promises as fspromises, constants } from 'fs';
1016
import { KeyFileMissingError, StartBinaryFailedError } from './errors';
1117

1218
// ignore the nodejs warning for coverage
@@ -294,15 +300,7 @@ export class MongoInstance extends EventEmitter implements ManagerBase {
294300
});
295301

296302
const mongoBin = await MongoBinary.getPath(this.binaryOpts);
297-
try {
298-
await fspromises.access(mongoBin, constants.X_OK);
299-
} catch (err) {
300-
console.error(
301-
`Mongod File at "${mongoBin}" does not have sufficient permissions to be used by this process\n` +
302-
'Needed Permissions: Execute (--x)\n'
303-
);
304-
throw err;
305-
}
303+
await checkBinaryPermissions(mongoBin);
306304
this.debug('start: Starting Processes');
307305
this.mongodProcess = this._launchMongod(mongoBin);
308306
// This assertion is here because somewhere between nodejs 12 and 16 the types for "childprocess.pid" changed to include "| undefined"

packages/mongodb-memory-server-core/src/util/__tests__/utils.test.ts

Lines changed: 59 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,11 @@
11
import { Stats, promises as fspromises } from 'fs';
22
import { ChildProcess } from 'child_process';
33
import * as utils from '../utils';
4+
import * as tmp from 'tmp';
5+
import { resolve } from 'path';
6+
import { BinaryNotFoundError, InsufficientPermissionsError } from '../errors';
7+
8+
tmp.setGracefulCleanup();
49

510
describe('utils', () => {
611
describe('uriTemplate', () => {
@@ -133,4 +138,58 @@ describe('utils', () => {
133138
expect(utils.getHost('mongodb://user:pass@localhost:0000/')).toEqual('localhost:0000');
134139
});
135140
});
141+
142+
describe('checkBinaryPermissions', () => {
143+
let tmpDir: tmp.DirResult;
144+
beforeEach(() => {
145+
jest.restoreAllMocks();
146+
tmpDir = tmp.dirSync({ prefix: 'mongo-mem-utils-', unsafeCleanup: true });
147+
});
148+
afterEach(() => {
149+
tmpDir.removeCallback();
150+
});
151+
152+
it('should throw nothing', async () => {
153+
jest.spyOn(fspromises, 'access');
154+
const binaryPath = resolve(tmpDir.name, 'noThrow');
155+
156+
await fspromises.writeFile(binaryPath, '', { mode: 0o777 });
157+
158+
await utils.checkBinaryPermissions(binaryPath);
159+
160+
expect(fspromises.access).toHaveBeenCalledTimes(1);
161+
});
162+
163+
it('should throw InsufficientPermissionsError', async () => {
164+
jest.spyOn(fspromises, 'access');
165+
const binaryPath = resolve(tmpDir.name, 'throwInsufficientPermissionsError');
166+
167+
await fspromises.writeFile(binaryPath, '', { mode: 0o600 });
168+
169+
try {
170+
await utils.checkBinaryPermissions(binaryPath);
171+
fail('Expected "utils.checkBinaryPermissions" to throw');
172+
} catch (err) {
173+
expect(err).toBeInstanceOf(InsufficientPermissionsError);
174+
expect(err).toHaveProperty('path', binaryPath);
175+
}
176+
177+
expect(fspromises.access).toHaveBeenCalledTimes(1);
178+
});
179+
180+
it('should throw BinaryNotFoundError', async () => {
181+
jest.spyOn(fspromises, 'access');
182+
const binaryPath = resolve(tmpDir.name, 'doesNotExist');
183+
184+
try {
185+
await utils.checkBinaryPermissions(binaryPath);
186+
fail('Expected "utils.checkBinaryPermissions" to throw');
187+
} catch (err) {
188+
expect(err).toBeInstanceOf(BinaryNotFoundError);
189+
expect(err).toHaveProperty('path', binaryPath);
190+
}
191+
192+
expect(fspromises.access).toHaveBeenCalledTimes(1);
193+
});
194+
});
136195
});

packages/mongodb-memory-server-core/src/util/errors.ts

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -65,6 +65,7 @@ export class EnsureInstanceError extends Error {
6565
}
6666
}
6767

68+
// REFACTOR: merge this error with BinaryNotFoundError
6869
export class NoSystemBinaryFoundError extends Error {
6970
constructor(public binaryPath: string) {
7071
super(
@@ -102,3 +103,15 @@ export class AuthNotObjectError extends Error {
102103
super('"auth" was not a object when it was expected!');
103104
}
104105
}
106+
107+
export class InsufficientPermissionsError extends Error {
108+
constructor(public path: string) {
109+
super(`File "${path}" does not have the required Permissions, required Permissions: "--x"`);
110+
}
111+
}
112+
113+
export class BinaryNotFoundError extends Error {
114+
constructor(public path: string) {
115+
super(`No Binary at path "${path}" was found! (ENOENT)`);
116+
}
117+
}

packages/mongodb-memory-server-core/src/util/utils.ts

Lines changed: 21 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,9 @@
11
import debug from 'debug';
22
import { ChildProcess } from 'child_process';
33
import { AutomaticAuth } from '../MongoMemoryServer';
4-
import { promises as fspromises, Stats } from 'fs';
4+
import { promises as fspromises, Stats, constants } from 'fs';
55
import { LinuxOS } from './getos';
6+
import { BinaryNotFoundError, InsufficientPermissionsError } from './errors';
67

78
const log = debug('MongoMS:utils');
89

@@ -228,3 +229,22 @@ export abstract class ManagerAdvanced extends ManagerBase {
228229
abstract getUri(otherDB?: string | boolean): string;
229230
abstract cleanup(force: boolean): Promise<void>;
230231
}
232+
233+
/**
234+
* Check that the Binary has sufficient Permissions to be executed
235+
* @param path The Path to check
236+
*/
237+
export async function checkBinaryPermissions(path: string): Promise<void> {
238+
try {
239+
await fspromises.access(path, constants.X_OK); // check if the provided path exists and has the execute bit for current user
240+
} catch (err) {
241+
if (err?.code === 'EACCES') {
242+
throw new InsufficientPermissionsError(path);
243+
}
244+
if (err?.code === 'ENOENT') {
245+
throw new BinaryNotFoundError(path);
246+
}
247+
248+
throw err;
249+
}
250+
}

0 commit comments

Comments
 (0)