From d3e201cac42d5ef9c3e1130ee6577084827af547 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Timoth=C3=A9e=20Rebours?= Date: Tue, 15 Mar 2022 11:26:46 +0100 Subject: [PATCH] cleaner test for modes, avoid testing permissions on Windows, document that it will not work on Windows, add typings tests --- API.md | 5 ++--- CHANGELOG.md | 2 +- index.d.ts | 1 + lib/datastore.js | 9 ++++----- lib/persistence.js | 24 ++++++++++++------------ lib/storage.js | 20 ++++++++++---------- test/persistence.async.test.js | 17 ++++++++++------- typings-tests.ts | 2 +- 8 files changed, 41 insertions(+), 39 deletions(-) diff --git a/API.md b/API.md index 9c2171c..24933d4 100644 --- a/API.md +++ b/API.md @@ -309,8 +309,7 @@ automatically considered in-memory only. It cannot end with a ~ whi perform crash-safe writes. Not used if options.inMemoryOnly is true.

- [.inMemoryOnly] boolean = false -

If set to true, no data will be written in storage. This option has priority over options.filename.

- - [.mode] object -

Permissions to use for FS. Only used for -Node.js storage module.

+ - [.modes] object -

Permissions to use for FS. Only used for Node.js storage module. Will not work on Windows.

- [.fileMode] number = 0o644 -

Permissions to use for database files

- [.dirMode] number = 0o755 -

Permissions to use for database directories

- [.timestampData] boolean = false -

If set to true, createdAt and updatedAt will be created and @@ -838,7 +837,7 @@ with appendfsync option set to no.

- [.corruptAlertThreshold] Number -

Optional, threshold after which an alert is thrown if too much data is corrupt

- [.beforeDeserialization] [serializationHook](#serializationHook) -

Hook you can use to transform data after it was serialized and before it is written to disk.

- [.afterSerialization] [serializationHook](#serializationHook) -

Inverse of afterSerialization.

- - [.mode] object -

Modes to use for FS permissions.

+ - [.modes] object -

Modes to use for FS permissions. Will not work on Windows.

- [.fileMode] number = 0o644 -

Mode to use for files.

- [.dirMode] number = 0o755 -

Mode to use for directories.

diff --git a/CHANGELOG.md b/CHANGELOG.md index 561dd7e..c3c51ce 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -13,7 +13,7 @@ to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). - An auto-generated JSDoc file is generated: [API.md](./API.md). - Added `Datastore#dropDatabaseAsync` and its callback equivalent. - The Error given when the `Datastore#corruptAlertThreshold` is reached now has three properties: `dataLength` which is the amount of lines in the database file (excluding empty lines), `corruptItems` which is the amount of corrupted lines, `corruptionRate` which the rate of corruption between 0 and 1. -- Added a `mode` option which allows to set the file and / or directory modes, by default, it uses `0o644` for files and `0o755` for directories, which may be breaking. +- Added a `modes: { fileMode, dirMode }` option to the Datastore which allows to set the file and / or directory modes, by default, it uses `0o644` for files and `0o755` for directories, which may be breaking. ### Changed - The `corruptionAlertThreshold` now doesn't take into account empty lines, and the error message is slightly changed. diff --git a/index.d.ts b/index.d.ts index 1512156..bbbd4af 100644 --- a/index.d.ts +++ b/index.d.ts @@ -113,6 +113,7 @@ declare namespace Nedb { afterSerialization?(line: string): string; corruptAlertThreshold?: number; compareStrings?(a: string, b: string): number; + modes?: {fileMode: number, dirMode: number}; } interface UpdateOptions { diff --git a/lib/datastore.js b/lib/datastore.js index da39c05..140cccf 100755 --- a/lib/datastore.js +++ b/lib/datastore.js @@ -165,10 +165,9 @@ class Datastore extends EventEmitter { * perform crash-safe writes. Not used if `options.inMemoryOnly` is `true`. * @param {boolean} [options.inMemoryOnly = false] If set to true, no data will be written in storage. This option has * priority over `options.filename`. - * @param {object} [options.mode] Permissions to use for FS. Only used for - * Node.js storage module. - * @param {number} [options.mode.fileMode = 0o644] Permissions to use for database files - * @param {number} [options.mode.dirMode = 0o755] Permissions to use for database directories + * @param {object} [options.modes] Permissions to use for FS. Only used for Node.js storage module. Will not work on Windows. + * @param {number} [options.modes.fileMode = 0o644] Permissions to use for database files + * @param {number} [options.modes.dirMode = 0o755] Permissions to use for database directories * @param {boolean} [options.timestampData = false] If set to true, createdAt and updatedAt will be created and * populated automatically (if not specified by user) * @param {boolean} [options.autoload = false] If used, the database will automatically be loaded from the datafile @@ -263,7 +262,7 @@ class Datastore extends EventEmitter { afterSerialization: options.afterSerialization, beforeDeserialization: options.beforeDeserialization, corruptAlertThreshold: options.corruptAlertThreshold, - mode: options.mode + modes: options.modes }) // This new executor is ready if we don't use persistence diff --git a/lib/persistence.js b/lib/persistence.js index b110ba5..2d21aaa 100755 --- a/lib/persistence.js +++ b/lib/persistence.js @@ -46,18 +46,18 @@ class Persistence { * @param {Number} [options.corruptAlertThreshold] Optional, threshold after which an alert is thrown if too much data is corrupt * @param {serializationHook} [options.beforeDeserialization] Hook you can use to transform data after it was serialized and before it is written to disk. * @param {serializationHook} [options.afterSerialization] Inverse of `afterSerialization`. - * @param {object} [options.mode] Modes to use for FS permissions. - * @param {number} [options.mode.fileMode=0o644] Mode to use for files. - * @param {number} [options.mode.dirMode=0o755] Mode to use for directories. + * @param {object} [options.modes] Modes to use for FS permissions. Will not work on Windows. + * @param {number} [options.modes.fileMode=0o644] Mode to use for files. + * @param {number} [options.modes.dirMode=0o755] Mode to use for directories. */ constructor (options) { this.db = options.db this.inMemoryOnly = this.db.inMemoryOnly this.filename = this.db.filename this.corruptAlertThreshold = options.corruptAlertThreshold !== undefined ? options.corruptAlertThreshold : 0.1 - this.mode = options.mode !== undefined ? options.mode : { fileMode: DEFAULT_FILE_MODE, dirMode: DEFAULT_DIR_MODE } - if (this.mode.fileMode === undefined) this.mode.fileMode = DEFAULT_FILE_MODE - if (this.mode.dirMode === undefined) this.mode.dirMode = DEFAULT_DIR_MODE + this.modes = options.modes !== undefined ? options.modes : { fileMode: DEFAULT_FILE_MODE, dirMode: DEFAULT_DIR_MODE } + if (this.modes.fileMode === undefined) this.modes.fileMode = DEFAULT_FILE_MODE + if (this.modes.dirMode === undefined) this.modes.dirMode = DEFAULT_DIR_MODE if ( !this.inMemoryOnly && this.filename && @@ -112,7 +112,7 @@ class Persistence { } }) - await storage.crashSafeWriteFileLinesAsync(this.filename, lines, this.mode) + await storage.crashSafeWriteFileLinesAsync(this.filename, lines, this.modes) this.db.emit('compaction.done') } @@ -163,7 +163,7 @@ class Persistence { if (toPersist.length === 0) return - await storage.appendFileAsync(this.filename, toPersist, { encoding: 'utf8', mode: this.mode.fileMode }) + await storage.appendFileAsync(this.filename, toPersist, { encoding: 'utf8', mode: this.modes.fileMode }) } /** @@ -305,17 +305,17 @@ class Persistence { // In-memory only datastore if (this.inMemoryOnly) return - await Persistence.ensureDirectoryExistsAsync(path.dirname(this.filename), this.mode.dirMode) - await storage.ensureDatafileIntegrityAsync(this.filename, this.mode.fileMode) + await Persistence.ensureDirectoryExistsAsync(path.dirname(this.filename), this.modes.dirMode) + await storage.ensureDatafileIntegrityAsync(this.filename, this.modes.fileMode) let treatedData if (storage.readFileStream) { // Server side - const fileStream = storage.readFileStream(this.filename, { encoding: 'utf8', mode: this.mode.fileMode }) + const fileStream = storage.readFileStream(this.filename, { encoding: 'utf8', mode: this.modes.fileMode }) treatedData = await this.treatRawStreamAsync(fileStream) } else { // Browser - const rawData = await storage.readFileAsync(this.filename, { encoding: 'utf8', mode: this.mode.fileMode }) + const rawData = await storage.readFileAsync(this.filename, { encoding: 'utf8', mode: this.modes.fileMode }) treatedData = this.treatRawData(rawData) } // Recreate all indexes in the datafile diff --git a/lib/storage.js b/lib/storage.js index 32a9ca3..97ad081 100755 --- a/lib/storage.js +++ b/lib/storage.js @@ -145,7 +145,7 @@ const flushToStorageAsync = async (options) => { } else { filename = options.filename flags = options.isDir ? 'r' : 'r+' - mode = options.mode || DEFAULT_FILE_MODE + mode = options.mode !== undefined ? options.mode : DEFAULT_FILE_MODE } /** * Some OSes and/or storage backends (augmented node fs) do not support fsync (FlushFileBuffers) directories, @@ -227,27 +227,27 @@ const writeFileLinesAsync = (filename, lines, mode = DEFAULT_FILE_MODE) => new P * Fully write or rewrite the datafile, immune to crashes during the write operation (data will not be lost). * @param {string} filename * @param {string[]} lines - * @param {object} [mode={ fileMode: 0o644, dirMode: 0o755 }] - * @param {number} mode.dirMode - * @param {number} mode.fileMode + * @param {object} [modes={ fileMode: 0o644, dirMode: 0o755 }] + * @param {number} modes.dirMode + * @param {number} modes.fileMode * @return {Promise} * @alias module:storage.crashSafeWriteFileLinesAsync */ -const crashSafeWriteFileLinesAsync = async (filename, lines, mode = { fileMode: DEFAULT_FILE_MODE, dirMode: DEFAULT_DIR_MODE }) => { +const crashSafeWriteFileLinesAsync = async (filename, lines, modes = { fileMode: DEFAULT_FILE_MODE, dirMode: DEFAULT_DIR_MODE }) => { const tempFilename = filename + '~' - await flushToStorageAsync({ filename: path.dirname(filename), isDir: true, mode: mode.dirMode }) + await flushToStorageAsync({ filename: path.dirname(filename), isDir: true, mode: modes.dirMode }) const exists = await existsAsync(filename) - if (exists) await flushToStorageAsync({ filename, mode: mode.fileMode }) + if (exists) await flushToStorageAsync({ filename, mode: modes.fileMode }) - await writeFileLinesAsync(tempFilename, lines, mode.fileMode) + await writeFileLinesAsync(tempFilename, lines, modes.fileMode) - await flushToStorageAsync({ filename: tempFilename, mode: mode.fileMode }) + await flushToStorageAsync({ filename: tempFilename, mode: modes.fileMode }) await renameAsync(tempFilename, filename) - await flushToStorageAsync({ filename: path.dirname(filename), isDir: true, mode: mode.dirMode }) + await flushToStorageAsync({ filename: path.dirname(filename), isDir: true, mode: modes.dirMode }) } /** diff --git a/test/persistence.async.test.js b/test/persistence.async.test.js index 4a6987f..7473a7c 100755 --- a/test/persistence.async.test.js +++ b/test/persistence.async.test.js @@ -1002,13 +1002,12 @@ describe('Persistence async', function () { const getMode = async path => { const { mode } = await fs.lstat(path) - const octalString = mode.toString('8') - return parseInt(octalString.substring(octalString.length - 4), '8') + return mode & 0o777 } const testPermissions = async (db, fileMode, dirMode) => { - assert.equal(db.persistence.mode.fileMode, fileMode) - assert.equal(db.persistence.mode.dirMode, dirMode) + assert.equal(db.persistence.modes.fileMode, fileMode) + assert.equal(db.persistence.modes.dirMode, dirMode) await db.loadDatabaseAsync() assert.equal(await getMode(db.filename), fileMode) assert.equal(await getMode(path.dirname(db.filename)), dirMode) @@ -1034,6 +1033,10 @@ const testPermissions = async (db, fileMode, dirMode) => { describe('permissions', function () { const testDb = 'workspace/permissions/test.db' + before('check OS', function () { + if (process.platform === 'win32' || process.platform === 'win64') this.skip() + }) + beforeEach('cleanup', async () => { try { await fs.chmod(path.dirname(testDb), 0o755) @@ -1059,21 +1062,21 @@ describe('permissions', function () { it('Setting only fileMode', async () => { const FILE_MODE = 0o600 const DIR_MODE = 0o755 - const db = new Datastore({ filename: testDb, mode: { fileMode: FILE_MODE } }) + const db = new Datastore({ filename: testDb, modes: { fileMode: FILE_MODE } }) await testPermissions(db, FILE_MODE, DIR_MODE) }) it('Setting only dirMode', async () => { const FILE_MODE = 0o644 const DIR_MODE = 0o700 - const db = new Datastore({ filename: testDb, mode: { dirMode: DIR_MODE } }) + const db = new Datastore({ filename: testDb, modes: { dirMode: DIR_MODE } }) await testPermissions(db, FILE_MODE, DIR_MODE) }) it('Setting fileMode & dirMode', async () => { const FILE_MODE = 0o600 const DIR_MODE = 0o700 - const db = new Datastore({ filename: testDb, mode: { dirMode: DIR_MODE, fileMode: FILE_MODE } }) + const db = new Datastore({ filename: testDb, modes: { dirMode: DIR_MODE, fileMode: FILE_MODE } }) await testPermissions(db, FILE_MODE, DIR_MODE) }) }) diff --git a/typings-tests.ts b/typings-tests.ts index 7d3b574..910c3f2 100644 --- a/typings-tests.ts +++ b/typings-tests.ts @@ -23,7 +23,7 @@ db.loadDatabase((err: Error | null) => { }) // Type 3: Persistent datastore with automatic loading -db = new Datastore({ filename: 'path/to/datafile_2', autoload: true }) +db = new Datastore({ filename: 'path/to/datafile_2', autoload: true, modes: {fileMode: 0o644, dirMode: 0o755} }) // You can issue commands right away // Of course you can create multiple datastores if you need several