cleaner test for modes, avoid testing permissions on Windows, document that it will not work on Windows, add typings tests

pull/27/head
Timothée Rebours 3 years ago
parent fc52b75316
commit d3e201cac4
  1. 5
      API.md
  2. 2
      CHANGELOG.md
  3. 1
      index.d.ts
  4. 9
      lib/datastore.js
  5. 24
      lib/persistence.js
  6. 20
      lib/storage.js
  7. 17
      test/persistence.async.test.js
  8. 2
      typings-tests.ts

@ -309,8 +309,7 @@ automatically considered in-memory only. It cannot end with a <code>~</code> whi
perform crash-safe writes. Not used if <code>options.inMemoryOnly</code> is <code>true</code>.</p>
- [.inMemoryOnly] <code>boolean</code> <code> = false</code> - <p>If set to true, no data will be written in storage. This option has
priority over <code>options.filename</code>.</p>
- [.mode] <code>object</code> - <p>Permissions to use for FS. Only used for
Node.js storage module.</p>
- [.modes] <code>object</code> - <p>Permissions to use for FS. Only used for Node.js storage module. Will not work on Windows.</p>
- [.fileMode] <code>number</code> <code> = 0o644</code> - <p>Permissions to use for database files</p>
- [.dirMode] <code>number</code> <code> = 0o755</code> - <p>Permissions to use for database directories</p>
- [.timestampData] <code>boolean</code> <code> = false</code> - <p>If set to true, createdAt and updatedAt will be created and
@ -838,7 +837,7 @@ with <code>appendfsync</code> option set to <code>no</code>.</p>
- [.corruptAlertThreshold] <code>Number</code> - <p>Optional, threshold after which an alert is thrown if too much data is corrupt</p>
- [.beforeDeserialization] [<code>serializationHook</code>](#serializationHook) - <p>Hook you can use to transform data after it was serialized and before it is written to disk.</p>
- [.afterSerialization] [<code>serializationHook</code>](#serializationHook) - <p>Inverse of <code>afterSerialization</code>.</p>
- [.mode] <code>object</code> - <p>Modes to use for FS permissions.</p>
- [.modes] <code>object</code> - <p>Modes to use for FS permissions. Will not work on Windows.</p>
- [.fileMode] <code>number</code> <code> = 0o644</code> - <p>Mode to use for files.</p>
- [.dirMode] <code>number</code> <code> = 0o755</code> - <p>Mode to use for directories.</p>

@ -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.

1
index.d.ts vendored

@ -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 {

@ -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

@ -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

@ -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<void>}
* @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 })
}
/**

@ -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)
})
})

@ -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

Loading…
Cancel
Save