1081 lines
33 KiB
JavaScript
Raw Normal View History

'use strict'
const test = require('tape')
const sinon = require('sinon')
const isBuffer = require('is-buffer')
const { Buffer } = require('buffer')
const { AbstractLevel, AbstractChainedBatch } = require('..')
const { MinimalLevel } = require('./util')
const getRangeOptions = require('../lib/range-options')
const testCommon = require('./common')({
test,
factory () {
return new AbstractLevel({ encodings: { utf8: true } })
}
})
const rangeOptions = ['gt', 'gte', 'lt', 'lte']
function implement (ctor, methods) {
class Test extends ctor {}
for (const k in methods) {
Test.prototype[k] = methods[k]
}
return Test
}
// Temporary test for browsers
// Not supported on Safari < 12 and Safari iOS < 12
test('async generator', async function (t) {
let ended = false
const end = async () => {
await new Promise((resolve) => setTimeout(resolve, 100))
ended = true
}
async function * generator () {
try {
yield 1
yield 2
yield 3
yield 4
} finally {
// Test that we're always able to cleanup resources
await end()
}
}
const res = []
for await (const x of generator()) {
res.push(x)
if (x === 2) break
}
t.same(res, [1, 2])
t.is(ended, true)
ended = false
try {
for await (const x of generator()) {
res.push(x)
if (x === 2) throw new Error('userland error')
}
} catch (err) {
t.is(err.message, 'userland error')
}
t.same(res, [1, 2, 1, 2])
t.is(ended, true)
})
/**
* Extensibility
*/
test('test core extensibility', function (t) {
const Test = implement(AbstractLevel)
const test = new Test({ encodings: { utf8: true } })
t.equal(test.status, 'opening', 'status is opening')
t.end()
})
test('test open() extensibility when new', function (t) {
const spy = sinon.spy()
const expectedCb = function () {}
const expectedOptions = { createIfMissing: true, errorIfExists: false }
const Test = implement(AbstractLevel, { _open: spy })
const test = new Test({ encodings: { utf8: true } })
const test2 = new Test({ encodings: { utf8: true } })
test.open(expectedCb)
t.equal(spy.callCount, 1, 'got _open() call')
t.equal(spy.getCall(0).thisValue, test, '`this` on _open() was correct')
t.equal(spy.getCall(0).args.length, 2, 'got two arguments')
t.deepEqual(spy.getCall(0).args[0], expectedOptions, 'got default options argument')
test2.open({ options: 1 }, expectedCb)
expectedOptions.options = 1
t.equal(spy.callCount, 2, 'got _open() call')
t.equal(spy.getCall(1).thisValue, test2, '`this` on _open() was correct')
t.equal(spy.getCall(1).args.length, 2, 'got two arguments')
t.deepEqual(spy.getCall(1).args[0], expectedOptions, 'got expected options argument')
t.end()
})
test('test open() extensibility when open', function (t) {
t.plan(3)
const spy = sinon.spy(function (options, cb) { this.nextTick(cb) })
const Test = implement(AbstractLevel, { _open: spy })
const test = new Test({ encodings: { utf8: true } })
test.once('open', function () {
t.is(spy.callCount, 1, 'got _open() call')
test.open(t.ifError.bind(t))
t.is(spy.callCount, 1, 'did not get second _open() call')
})
})
test('test opening explicitly gives a chance to capture an error', function (t) {
t.plan(3)
const spy = sinon.spy(function (options, cb) { this.nextTick(cb, new Error('_open error')) })
const Test = implement(AbstractLevel, { _open: spy })
const test = new Test({ encodings: { utf8: true } })
test.open(function (err) {
t.is(spy.callCount, 1, 'got _open() call')
t.is(err && err.code, 'LEVEL_DATABASE_NOT_OPEN')
t.is(err && err.cause && err.cause.message, '_open error')
})
})
test('test opening explicitly gives a chance to capture an error with promise', async function (t) {
t.plan(3)
const spy = sinon.spy(function (options, cb) { this.nextTick(cb, new Error('_open error')) })
const Test = implement(AbstractLevel, { _open: spy })
const test = new Test({ encodings: { utf8: true } })
try {
await test.open()
} catch (err) {
t.is(spy.callCount, 1, 'got _open() call')
t.is(err.code, 'LEVEL_DATABASE_NOT_OPEN')
t.is(err.cause && err.cause.message, '_open error')
}
})
test('test constructor options are forwarded to open()', function (t) {
t.plan(3)
const spy = sinon.spy(function (options, cb) { this.nextTick(cb) })
const Test = implement(AbstractLevel, { _open: spy })
const test = new Test({ encodings: { utf8: true } }, {
passive: true,
keyEncoding: 'json',
valueEncoding: 'json',
createIfMissing: false,
foo: 123
})
test.open(function (err) {
t.ifError(err, 'no open() error')
t.is(spy.callCount, 1, 'got _open() call')
t.same(spy.getCall(0).args[0], {
foo: 123,
createIfMissing: false,
errorIfExists: false
}, 'does not forward passive, keyEncoding and valueEncoding options')
})
})
test('test close() extensibility when open', function (t) {
t.plan(4)
const spy = sinon.spy(function (cb) { this.nextTick(cb) })
const Test = implement(AbstractLevel, { _close: spy })
const test = new Test({ encodings: { utf8: true } })
test.once('open', function () {
test.close(function (err) {
t.ifError(err, 'no close() error')
t.is(spy.callCount, 1, 'got _close() call')
t.is(spy.getCall(0).thisValue, test, '`this` on _close() was correct')
t.is(spy.getCall(0).args.length, 1, 'got one arguments')
})
})
})
test('test close() extensibility when new', function (t) {
t.plan(3)
const spy = sinon.spy(function (cb) { this.nextTick(cb) })
const Test = implement(AbstractLevel, { _close: spy })
const test = new Test({ encodings: { utf8: true } })
test.close(function (err) {
t.ifError(err, 'no close() error')
t.is(spy.callCount, 1, 'did get _close() call')
})
t.is(spy.callCount, 0, 'did not get _close() call')
})
test('test open(), close(), open() with twice failed open', function (t) {
t.plan(8)
const db = testCommon.factory()
const order = []
let opens = 0
db.on('open', t.fail.bind(t))
db.on('closed', t.fail.bind(t))
db._open = function (options, callback) {
t.pass('called')
this.nextTick(callback, new Error('test' + (++opens)))
}
db.open(function (err) {
t.is(err && err.code, 'LEVEL_DATABASE_NOT_OPEN')
t.is(err && err.cause && err.cause.message, 'test1')
order.push('A')
})
db.close(function (err) {
t.ifError(err)
order.push('B')
})
db.open(function (err) {
t.is(err && err.code, 'LEVEL_DATABASE_NOT_OPEN')
t.is(err && err.cause && err.cause.message, 'test2')
order.push('C')
t.same(order, ['A', 'B', 'C'], 'order is ok')
})
})
test('test open(), close(), open() with first failed open', function (t) {
t.plan(9)
const db = testCommon.factory()
const order = []
let opens = 0
db.on('open', () => { order.push('open event') })
db.on('closed', t.fail.bind(t))
db._open = function (options, callback) {
t.pass('called')
this.nextTick(callback, opens++ ? null : new Error('test'))
}
db.open(function (err) {
t.ifError(err)
t.is(db.status, 'open')
order.push('A')
})
db.close(function (err) {
t.is(err && err.code, 'LEVEL_DATABASE_NOT_CLOSED')
t.is(db.status, 'open')
order.push('B')
})
db.open(function (err) {
t.ifError(err)
t.is(db.status, 'open')
order.push('C')
t.same(order, ['A', 'B', 'open event', 'C'], 'order is ok')
})
})
test('test open(), close(), open() with second failed open', function (t) {
t.plan(10)
const db = testCommon.factory()
const order = []
let opens = 0
db.on('open', t.fail.bind(t))
db.on('closed', t.fail.bind(t))
db._open = function (options, callback) {
t.pass('called')
this.nextTick(callback, opens++ ? new Error('test') : null)
}
db.open(function (err) {
t.is(err && err.code, 'LEVEL_DATABASE_NOT_OPEN')
t.is(db.status, 'closed')
order.push('A')
})
db.close(function (err) {
t.ifError(err)
t.is(db.status, 'closed')
order.push('B')
})
db.open(function (err) {
t.is(err && err.code, 'LEVEL_DATABASE_NOT_OPEN')
t.is(err && err.cause.message, 'test')
t.is(db.status, 'closed')
order.push('C')
t.same(order, ['A', 'B', 'C'], 'order is ok')
})
})
test('test get() extensibility', function (t) {
t.plan(12)
const spy = sinon.spy()
const expectedCb = function () {}
const expectedOptions = { keyEncoding: 'utf8', valueEncoding: 'utf8' }
const expectedKey = 'a key'
const Test = implement(AbstractLevel, { _get: spy })
const test = new Test({ encodings: { utf8: true } }, { keyEncoding: 'utf8' })
test.once('open', function () {
test.get(expectedKey, expectedCb)
t.equal(spy.callCount, 1, 'got _get() call')
t.equal(spy.getCall(0).thisValue, test, '`this` on _get() was correct')
t.equal(spy.getCall(0).args.length, 3, 'got three arguments')
t.equal(spy.getCall(0).args[0], expectedKey, 'got expected key argument')
t.deepEqual(spy.getCall(0).args[1], expectedOptions, 'got default options argument')
t.is(typeof spy.getCall(0).args[2], 'function', 'got cb argument')
test.get(expectedKey, { options: 1 }, expectedCb)
expectedOptions.options = 1
t.equal(spy.callCount, 2, 'got _get() call')
t.equal(spy.getCall(1).thisValue, test, '`this` on _get() was correct')
t.equal(spy.getCall(1).args.length, 3, 'got three arguments')
t.equal(spy.getCall(1).args[0], expectedKey, 'got expected key argument')
t.deepEqual(spy.getCall(1).args[1], expectedOptions, 'got expected options argument')
t.is(typeof spy.getCall(1).args[2], 'function', 'got cb argument')
})
})
test('test getMany() extensibility', function (t) {
t.plan(12)
const spy = sinon.spy()
const expectedCb = function () {}
const expectedOptions = { keyEncoding: 'utf8', valueEncoding: 'utf8' }
const expectedKey = 'a key'
const Test = implement(AbstractLevel, { _getMany: spy })
const test = new Test({ encodings: { utf8: true } })
test.once('open', function () {
test.getMany([expectedKey], expectedCb)
t.equal(spy.callCount, 1, 'got _getMany() call')
t.equal(spy.getCall(0).thisValue, test, '`this` on _getMany() was correct')
t.equal(spy.getCall(0).args.length, 3, 'got three arguments')
t.deepEqual(spy.getCall(0).args[0], [expectedKey], 'got expected keys argument')
t.deepEqual(spy.getCall(0).args[1], expectedOptions, 'got default options argument')
t.is(typeof spy.getCall(0).args[2], 'function', 'got cb argument')
test.getMany([expectedKey], { options: 1 }, expectedCb)
expectedOptions.options = 1
t.equal(spy.callCount, 2, 'got _getMany() call')
t.equal(spy.getCall(1).thisValue, test, '`this` on _getMany() was correct')
t.equal(spy.getCall(1).args.length, 3, 'got three arguments')
t.deepEqual(spy.getCall(1).args[0], [expectedKey], 'got expected key argument')
t.deepEqual(spy.getCall(1).args[1], expectedOptions, 'got expected options argument')
t.is(typeof spy.getCall(1).args[2], 'function', 'got cb argument')
})
})
test('test del() extensibility', function (t) {
t.plan(12)
const spy = sinon.spy()
const expectedCb = function () {}
const expectedOptions = { options: 1, keyEncoding: 'utf8' }
const expectedKey = 'a key'
const Test = implement(AbstractLevel, { _del: spy })
const test = new Test({ encodings: { utf8: true } })
test.once('open', function () {
test.del(expectedKey, expectedCb)
t.equal(spy.callCount, 1, 'got _del() call')
t.equal(spy.getCall(0).thisValue, test, '`this` on _del() was correct')
t.equal(spy.getCall(0).args.length, 3, 'got three arguments')
t.equal(spy.getCall(0).args[0], expectedKey, 'got expected key argument')
t.deepEqual(spy.getCall(0).args[1], { keyEncoding: 'utf8' }, 'got blank options argument')
t.equal(typeof spy.getCall(0).args[2], 'function', 'got cb argument')
test.del(expectedKey, expectedOptions, expectedCb)
t.equal(spy.callCount, 2, 'got _del() call')
t.equal(spy.getCall(1).thisValue, test, '`this` on _del() was correct')
t.equal(spy.getCall(1).args.length, 3, 'got three arguments')
t.equal(spy.getCall(1).args[0], expectedKey, 'got expected key argument')
t.deepEqual(spy.getCall(1).args[1], expectedOptions, 'got expected options argument')
t.equal(typeof spy.getCall(1).args[2], 'function', 'got cb argument')
})
})
test('test put() extensibility', function (t) {
t.plan(14)
const spy = sinon.spy()
const expectedCb = function () {}
const expectedOptions = { options: 1, keyEncoding: 'utf8', valueEncoding: 'utf8' }
const expectedKey = 'a key'
const expectedValue = 'a value'
const Test = implement(AbstractLevel, { _put: spy })
const test = new Test({ encodings: { utf8: true } })
test.once('open', function () {
test.put(expectedKey, expectedValue, expectedCb)
t.equal(spy.callCount, 1, 'got _put() call')
t.equal(spy.getCall(0).thisValue, test, '`this` on _put() was correct')
t.equal(spy.getCall(0).args.length, 4, 'got four arguments')
t.equal(spy.getCall(0).args[0], expectedKey, 'got expected key argument')
t.equal(spy.getCall(0).args[1], expectedValue, 'got expected value argument')
t.deepEqual(spy.getCall(0).args[2], { keyEncoding: 'utf8', valueEncoding: 'utf8' }, 'got default options argument')
t.equal(typeof spy.getCall(0).args[3], 'function', 'got cb argument')
test.put(expectedKey, expectedValue, expectedOptions, expectedCb)
t.equal(spy.callCount, 2, 'got _put() call')
t.equal(spy.getCall(1).thisValue, test, '`this` on _put() was correct')
t.equal(spy.getCall(1).args.length, 4, 'got four arguments')
t.equal(spy.getCall(1).args[0], expectedKey, 'got expected key argument')
t.equal(spy.getCall(1).args[1], expectedValue, 'got expected value argument')
t.deepEqual(spy.getCall(1).args[2], expectedOptions, 'got expected options argument')
t.equal(typeof spy.getCall(1).args[3], 'function', 'got cb argument')
})
})
test('test batch([]) (array-form) extensibility', function (t) {
t.plan(18)
const spy = sinon.spy()
const expectedCb = function () {}
const expectedOptions = { options: 1 }
const expectedArray = [
{ type: 'put', key: '1', value: '1', keyEncoding: 'utf8', valueEncoding: 'utf8' },
{ type: 'del', key: '2', keyEncoding: 'utf8' }
]
const Test = implement(AbstractLevel, { _batch: spy })
const test = new Test({ encodings: { utf8: true } })
test.once('open', function () {
test.batch(expectedArray, expectedCb)
t.equal(spy.callCount, 1, 'got _batch() call')
t.equal(spy.getCall(0).thisValue, test, '`this` on _batch() was correct')
t.equal(spy.getCall(0).args.length, 3, 'got three arguments')
t.deepEqual(spy.getCall(0).args[0], expectedArray, 'got expected array argument')
t.deepEqual(spy.getCall(0).args[1], {}, 'got expected options argument')
t.equal(typeof spy.getCall(0).args[2], 'function', 'got callback argument')
test.batch(expectedArray, expectedOptions, expectedCb)
t.equal(spy.callCount, 2, 'got _batch() call')
t.equal(spy.getCall(1).thisValue, test, '`this` on _batch() was correct')
t.equal(spy.getCall(1).args.length, 3, 'got three arguments')
t.deepEqual(spy.getCall(1).args[0], expectedArray, 'got expected array argument')
t.deepEqual(spy.getCall(1).args[1], expectedOptions, 'got expected options argument')
t.equal(typeof spy.getCall(1).args[2], 'function', 'got callback argument')
test.batch(expectedArray, null, expectedCb)
t.equal(spy.callCount, 3, 'got _batch() call')
t.equal(spy.getCall(2).thisValue, test, '`this` on _batch() was correct')
t.equal(spy.getCall(2).args.length, 3, 'got three arguments')
t.deepEqual(spy.getCall(2).args[0], expectedArray, 'got expected array argument')
t.ok(spy.getCall(2).args[1], 'options should not be null')
t.equal(typeof spy.getCall(2).args[2], 'function', 'got callback argument')
})
})
test('test batch([]) (array-form) with empty array is asynchronous', function (t) {
t.plan(3)
const spy = sinon.spy()
const Test = implement(AbstractLevel, { _batch: spy })
const test = new Test({ encodings: { utf8: true } })
test.once('open', function () {
let async = false
test.batch([], function (err) {
t.ifError(err, 'no error')
t.ok(async, 'callback is asynchronous')
// Assert that asynchronicity is provided by batch() rather than _batch()
t.is(spy.callCount, 0, '_batch() call was bypassed')
})
async = true
})
})
test('test chained batch() extensibility', function (t) {
t.plan(16)
const spy = sinon.spy()
const expectedCb = function () {}
const expectedOptions = { options: 1 }
const Test = implement(AbstractLevel, { _batch: spy })
const test = new Test({ encodings: { utf8: true } })
test.once('open', function () {
test.batch().put('foo', 'bar').del('bang').write(expectedCb)
t.equal(spy.callCount, 1, 'got _batch() call')
t.equal(spy.getCall(0).thisValue, test, '`this` on _batch() was correct')
t.equal(spy.getCall(0).args.length, 3, 'got three arguments')
t.equal(spy.getCall(0).args[0].length, 2, 'got expected array argument')
t.deepEqual(spy.getCall(0).args[0][0], { keyEncoding: 'utf8', valueEncoding: 'utf8', type: 'put', key: 'foo', value: 'bar' }, 'got expected array argument[0]')
t.deepEqual(spy.getCall(0).args[0][1], { keyEncoding: 'utf8', type: 'del', key: 'bang' }, 'got expected array argument[1]')
t.deepEqual(spy.getCall(0).args[1], {}, 'got expected options argument')
t.is(typeof spy.getCall(0).args[2], 'function', 'got callback argument')
test.batch().put('foo', 'bar', expectedOptions).del('bang', expectedOptions).write(expectedOptions, expectedCb)
t.equal(spy.callCount, 2, 'got _batch() call')
t.equal(spy.getCall(1).thisValue, test, '`this` on _batch() was correct')
t.equal(spy.getCall(1).args.length, 3, 'got three arguments')
t.equal(spy.getCall(1).args[0].length, 2, 'got expected array argument')
t.deepEqual(spy.getCall(1).args[0][0], { keyEncoding: 'utf8', valueEncoding: 'utf8', type: 'put', key: 'foo', value: 'bar', options: 1 }, 'got expected array argument[0]')
t.deepEqual(spy.getCall(1).args[0][1], { keyEncoding: 'utf8', type: 'del', key: 'bang', options: 1 }, 'got expected array argument[1]')
t.deepEqual(spy.getCall(1).args[1], { options: 1 }, 'got expected options argument')
t.is(typeof spy.getCall(1).args[2], 'function', 'got callback argument')
})
})
test('test chained batch() with no operations is asynchronous', function (t) {
t.plan(2)
const Test = implement(AbstractLevel, {})
const test = new Test({ encodings: { utf8: true } })
test.once('open', function () {
let async = false
test.batch().write(function (err) {
t.ifError(err, 'no error')
t.ok(async, 'callback is asynchronous')
})
async = true
})
})
test('test chained batch() (custom _chainedBatch) extensibility', function (t) {
t.plan(4)
const spy = sinon.spy()
const Test = implement(AbstractLevel, { _chainedBatch: spy })
const test = new Test({ encodings: { utf8: true } })
test.once('open', function () {
test.batch()
t.equal(spy.callCount, 1, 'got _chainedBatch() call')
t.equal(spy.getCall(0).thisValue, test, '`this` on _chainedBatch() was correct')
test.batch()
t.equal(spy.callCount, 2, 'got _chainedBatch() call')
t.equal(spy.getCall(1).thisValue, test, '`this` on _chainedBatch() was correct')
})
})
test('test AbstractChainedBatch extensibility', function (t) {
const Batch = implement(AbstractChainedBatch)
const db = testCommon.factory()
const test = new Batch(db)
t.ok(test.db === db, 'instance has db reference')
t.end()
})
test('test AbstractChainedBatch expects a db', function (t) {
t.plan(1)
const Test = implement(AbstractChainedBatch)
try {
// eslint-disable-next-line no-new
new Test()
} catch (err) {
t.is(err.message, 'The first argument must be an abstract-level database, received undefined')
}
})
test('test AbstractChainedBatch#write() extensibility', function (t) {
t.plan(3)
let batch
const Test = implement(AbstractChainedBatch, {
_write: function (options, callback) {
t.same(options, {})
t.is(this, batch, 'thisArg on _write() is correct')
this.nextTick(callback)
}
})
const db = testCommon.factory()
db.once('open', function () {
batch = new Test(db)
// Without any operations, _write isn't called
batch.put('foo', 'bar')
batch.write(function (err) {
t.ifError(err)
})
})
})
test('test AbstractChainedBatch#write() extensibility with null options', function (t) {
t.plan(3)
let batch
const Test = implement(AbstractChainedBatch, {
_write: function (options, callback) {
t.same(options, {})
t.is(this, batch, 'thisArg on _write() is correct')
this.nextTick(callback)
}
})
const db = testCommon.factory()
db.once('open', function () {
batch = new Test(db)
// Without any operations, _write isn't called
batch.put('foo', 'bar')
batch.write(null, function (err) {
t.ifError(err)
})
})
})
test('test AbstractChainedBatch#write() extensibility with options', function (t) {
t.plan(3)
let batch
const Test = implement(AbstractChainedBatch, {
_write: function (options, callback) {
t.same(options, { test: true })
t.is(this, batch, 'thisArg on _write() is correct')
this.nextTick(callback)
}
})
const db = testCommon.factory()
db.once('open', function () {
batch = new Test(db)
// Without any operations, _write isn't called
batch.put('foo', 'bar')
batch.write({ test: true }, function (err) {
t.ifError(err)
})
})
})
test('test AbstractChainedBatch#put() extensibility', function (t) {
t.plan(7)
const spy = sinon.spy()
const expectedKey = 'key'
const expectedValue = 'value'
const Test = implement(AbstractChainedBatch, { _put: spy })
const db = testCommon.factory()
db.once('open', function () {
const test = new Test(db)
const returnValue = test.put(expectedKey, expectedValue)
t.equal(spy.callCount, 1, 'got _put call')
t.equal(spy.getCall(0).thisValue, test, '`this` on _put() was correct')
t.equal(spy.getCall(0).args.length, 3, 'got 3 arguments')
t.equal(spy.getCall(0).args[0], expectedKey, 'got expected key argument')
t.equal(spy.getCall(0).args[1], expectedValue, 'got expected value argument')
t.same(spy.getCall(0).args[2], { keyEncoding: 'utf8', valueEncoding: 'utf8' }, 'got expected options argument')
t.equal(returnValue, test, 'get expected return value')
})
})
test('test AbstractChainedBatch#del() extensibility', function (t) {
t.plan(6)
const spy = sinon.spy()
const expectedKey = 'key'
const Test = implement(AbstractChainedBatch, { _del: spy })
const db = testCommon.factory()
db.once('open', function () {
const test = new Test(db)
const returnValue = test.del(expectedKey)
t.equal(spy.callCount, 1, 'got _del call')
t.equal(spy.getCall(0).thisValue, test, '`this` on _del() was correct')
t.equal(spy.getCall(0).args.length, 2, 'got 2 arguments')
t.equal(spy.getCall(0).args[0], expectedKey, 'got expected key argument')
t.same(spy.getCall(0).args[1], { keyEncoding: 'utf8' }, 'got expected options argument')
t.equal(returnValue, test, 'get expected return value')
})
})
test('test AbstractChainedBatch#clear() extensibility', function (t) {
t.plan(4)
const spy = sinon.spy()
const Test = implement(AbstractChainedBatch, { _clear: spy })
const db = testCommon.factory()
db.once('open', function () {
const test = new Test(db)
const returnValue = test.clear()
t.equal(spy.callCount, 1, 'got _clear call')
t.equal(spy.getCall(0).thisValue, test, '`this` on _clear() was correct')
t.equal(spy.getCall(0).args.length, 0, 'got zero arguments')
t.equal(returnValue, test, 'get expected return value')
})
})
test('test clear() extensibility', function (t) {
t.plan((7 * 5) - 4)
const spy = sinon.spy()
const Test = implement(AbstractLevel, { _clear: spy })
const db = new Test({ encodings: { utf8: true } })
const callback = function () {}
db.once('open', function () {
call([callback], { keyEncoding: 'utf8', reverse: false, limit: -1 })
call([null, callback], { keyEncoding: 'utf8', reverse: false, limit: -1 })
call([undefined, callback], { keyEncoding: 'utf8', reverse: false, limit: -1 })
call([{ custom: 1 }, callback], { custom: 1, keyEncoding: 'utf8', reverse: false, limit: -1 })
call([{ reverse: true, limit: 0 }, callback], { keyEncoding: 'utf8', reverse: true, limit: 0 }, true)
call([{ reverse: 1 }, callback], { keyEncoding: 'utf8', reverse: true, limit: -1 })
call([{ reverse: null }, callback], { keyEncoding: 'utf8', reverse: false, limit: -1 })
function call (args, expectedOptions, shouldSkipCall) {
db.clear.apply(db, args)
t.is(spy.callCount, shouldSkipCall ? 0 : 1, 'got _clear() call')
if (!shouldSkipCall) {
t.is(spy.getCall(0).thisValue, db, '`this` on _clear() was correct')
t.is(spy.getCall(0).args.length, 2, 'got two arguments')
t.same(spy.getCall(0).args[0], expectedOptions, 'got expected options argument')
t.is(typeof spy.getCall(0).args[1], 'function', 'got callback argument')
}
spy.resetHistory()
}
})
})
// TODO: replace with encoding test
test.skip('test serialization extensibility (batch array is not mutated)', function (t) {
t.plan(7)
const spy = sinon.spy()
const Test = implement(AbstractLevel, {
_batch: spy,
_serializeKey: function (key) {
t.equal(key, 'no')
return 'foo'
},
_serializeValue: function (value) {
t.equal(value, 'nope')
return 'bar'
}
})
const test = new Test({ encodings: { utf8: true } })
test.once('open', function () {
const op = { type: 'put', key: 'no', value: 'nope' }
test.batch([op], function () {})
t.equal(spy.callCount, 1, 'got _batch() call')
t.equal(spy.getCall(0).args[0][0].key, 'foo', 'got expected key')
t.equal(spy.getCall(0).args[0][0].value, 'bar', 'got expected value')
t.equal(op.key, 'no', 'did not mutate input key')
t.equal(op.value, 'nope', 'did not mutate input value')
})
})
test('clear() does not delete empty or nullish range options', function (t) {
const rangeValues = [Uint8Array.from([]), '', null, undefined]
t.plan(rangeOptions.length * rangeValues.length)
rangeValues.forEach(function (value) {
const Test = implement(AbstractLevel, {
_clear: function (options, callback) {
rangeOptions.forEach(function (key) {
t.ok(key in options, key + ' option should not be deleted')
})
}
})
const db = new Test({ encodings: { utf8: true } })
const options = {}
rangeOptions.forEach(function (key) {
options[key] = value
})
db.once('open', function () {
db.clear(options, function () {})
})
})
})
// TODO: some of these are redundant
test('.status', function (t) {
t.plan(5)
t.test('empty prototype', function (t) {
const Test = implement(AbstractLevel)
const test = new Test({ encodings: { utf8: true } })
t.equal(test.status, 'opening')
test.open(function (err) {
t.error(err)
t.equal(test.status, 'open')
test.close(function (err) {
t.error(err)
t.equal(test.status, 'closed')
t.end()
})
})
t.equal(test.status, 'opening')
})
t.test('open error', function (t) {
const Test = implement(AbstractLevel, {
_open: function (options, cb) {
cb(new Error('_open error'))
}
})
const test = new Test({ encodings: { utf8: true } })
test.open(function (err) {
t.is(err && err.code, 'LEVEL_DATABASE_NOT_OPEN')
t.is(err && err.cause && err.cause.message, '_open error')
t.equal(test.status, 'closed')
t.end()
})
})
t.test('close error', function (t) {
const Test = implement(AbstractLevel, {
_close: function (cb) {
cb(new Error('_close error'))
}
})
const test = new Test({ encodings: { utf8: true } })
test.open(function () {
test.close(function (err) {
t.is(err && err.code, 'LEVEL_DATABASE_NOT_CLOSED')
t.is(err && err.cause && err.cause.message, '_close error')
t.equal(test.status, 'open')
t.end()
})
})
})
t.test('open', function (t) {
const Test = implement(AbstractLevel, {
_open: function (options, cb) {
this.nextTick(cb)
}
})
const test = new Test({ encodings: { utf8: true } })
test.open(function (err) {
t.error(err)
t.equal(test.status, 'open')
t.end()
})
t.equal(test.status, 'opening')
})
t.test('close', function (t) {
const Test = implement(AbstractLevel, {
_close: function (cb) {
this.nextTick(cb)
}
})
const test = new Test({ encodings: { utf8: true } })
test.open(function (err) {
t.error(err)
test.close(function (err) {
t.error(err)
t.equal(test.status, 'closed')
t.end()
})
t.equal(test.status, 'closing')
})
})
})
test('rangeOptions', function (t) {
const keys = rangeOptions.slice()
const db = new AbstractLevel({
encodings: {
utf8: true, buffer: true, view: true
}
})
function setupOptions (create) {
const options = {}
for (const key of keys) {
options[key] = create()
}
return options
}
function verifyOptions (t, options) {
for (const key of keys) {
t.ok(key in options, key + ' option should not be deleted')
}
t.end()
}
t.plan(11)
t.test('setup', async (t) => db.open())
t.test('default options', function (t) {
t.same(getRangeOptions(undefined, db.keyEncoding('utf8')), {
reverse: false,
limit: -1
}, 'correct defaults')
t.end()
})
t.test('set options', function (t) {
t.same(getRangeOptions({ reverse: false, limit: 20 }, db.keyEncoding('utf8')), {
reverse: false,
limit: 20
}, 'options set correctly')
t.end()
})
t.test('ignores invalid limit', function (t) {
// Infinity is valid but is normalized to -1 for use in private API
for (const limit of [Number.POSITIVE_INFINITY, Number.NEGATIVE_INFINITY, NaN, -2, 5.5]) {
t.same(getRangeOptions({ limit }, db.keyEncoding('utf8')).limit, -1)
}
t.end()
})
t.test('ignores not-own property', function (t) {
class Options {}
Options.prototype.limit = 20
const options = new Options()
t.is(options.limit, 20)
t.same(getRangeOptions(options, db.keyEncoding('utf8')), {
reverse: false,
limit: -1
})
t.end()
})
t.test('does not delete empty buffers', function (t) {
const options = setupOptions(() => Buffer.alloc(0))
keys.forEach(function (key) {
t.is(isBuffer(options[key]), true, 'should be buffer')
t.is(options[key].byteLength, 0, 'should be empty')
})
verifyOptions(t, getRangeOptions(options, db.keyEncoding('buffer')))
})
t.test('does not delete empty views', function (t) {
const options = setupOptions(() => Uint8Array.from([]))
keys.forEach(function (key) {
t.is(options[key] instanceof Uint8Array, true, 'should be Uint8Array')
t.is(options[key].byteLength, 0, 'should be empty')
})
verifyOptions(t, getRangeOptions(options, db.keyEncoding('view')))
})
t.test('does not delete empty strings', function (t) {
const options = setupOptions(() => '')
keys.forEach(function (key) {
t.is(typeof options[key], 'string', 'should be string')
t.is(options[key].length, 0, 'should be empty')
})
verifyOptions(t, getRangeOptions(options, db.keyEncoding('utf8')))
})
t.test('does not delete null', function (t) {
const options = setupOptions(() => null)
keys.forEach(function (key) {
t.is(options[key], null)
})
verifyOptions(t, getRangeOptions(options, db.keyEncoding('utf8')))
})
t.test('does not delete undefined', function (t) {
const options = setupOptions(() => undefined)
keys.forEach(function (key) {
t.is(options[key], undefined)
})
verifyOptions(t, getRangeOptions(options, db.keyEncoding('utf8')))
})
t.test('rejects legacy range options', function (t) {
t.plan(2)
for (const key of ['start', 'end']) {
const options = {}
options[key] = 'x'
try {
getRangeOptions(options, db.keyEncoding('utf8'))
} catch (err) {
t.is(err.code, 'LEVEL_LEGACY')
}
}
})
})
require('./self/defer-test')
require('./self/attach-resource-test')
require('./self/abstract-iterator-test')
require('./self/iterator-test')
require('./self/deferred-iterator-test')
require('./self/deferred-operations-test')
require('./self/deferred-chained-batch-test')
require('./self/async-iterator-test')
require('./self/encoding-test')
require('./self/sublevel-test')
// Test the abstract test suite using a minimal implementation
require('./index')({
test,
factory (options) {
return new MinimalLevel(options)
}
})