321 lines
9.8 KiB
JavaScript
Raw Normal View History

'use strict'
const { Buffer } = require('buffer')
const { verifyNotFoundError, assertAsync } = require('./util')
const { illegalKeys, illegalValues } = require('./util')
let db
exports.setUp = function (test, testCommon) {
test('setUp db', function (t) {
db = testCommon.factory()
db.open(t.end.bind(t))
})
}
exports.args = function (test, testCommon) {
test('test batch() with missing `value`', assertAsync.ctx(function (t) {
t.plan(3)
db.batch([{ type: 'put', key: 'foo1' }], assertAsync(function (err) {
t.is(err && err.code, 'LEVEL_INVALID_VALUE', 'correct error code (callback)')
}))
db.batch([{ type: 'put', key: 'foo1' }]).catch((err) => {
t.is(err.code, 'LEVEL_INVALID_VALUE', 'correct error code (promise)')
})
}))
test('test batch() with illegal values', assertAsync.ctx(function (t) {
t.plan(illegalValues.length * 6)
for (const { name, value } of illegalValues) {
db.batch([{ type: 'put', key: 'foo1', value }], assertAsync(function (err) {
t.ok(err, name + ' - has error (callback)')
t.ok(err instanceof Error, name + ' - is Error (callback)')
t.is(err && err.code, 'LEVEL_INVALID_VALUE', 'correct error code (callback)')
}))
db.batch([{ type: 'put', key: 'foo1', value }]).catch(function (err) {
t.ok(err instanceof Error, name + ' - is Error (promise)')
t.is(err.code, 'LEVEL_INVALID_VALUE', name + ' - correct error code (promise)')
})
}
}))
test('test batch() with missing `key`', assertAsync.ctx(function (t) {
t.plan(3)
db.batch([{ type: 'put', value: 'foo1' }], assertAsync(function (err) {
t.is(err && err.code, 'LEVEL_INVALID_KEY', 'correct error code (callback)')
}))
db.batch([{ type: 'put', value: 'foo1' }]).catch(function (err) {
t.is(err.code, 'LEVEL_INVALID_KEY', 'correct error code (promise)')
})
}))
test('test batch() with illegal keys', assertAsync.ctx(function (t) {
t.plan(illegalKeys.length * 6)
for (const { name, key } of illegalKeys) {
db.batch([{ type: 'put', key, value: 'foo1' }], assertAsync(function (err) {
t.ok(err, name + ' - has error (callback)')
t.ok(err instanceof Error, name + ' - is Error (callback)')
t.is(err && err.code, 'LEVEL_INVALID_KEY', 'correct error code (callback)')
}))
db.batch([{ type: 'put', key, value: 'foo1' }]).catch(function (err) {
t.ok(err instanceof Error, name + ' - is Error (promise)')
t.is(err.code, 'LEVEL_INVALID_KEY', name + ' - correct error code (promise)')
})
}
}))
test('test batch() with missing or incorrect type', assertAsync.ctx(function (t) {
t.plan(10)
db.batch([{ key: 'key', value: 'value' }], assertAsync(function (err) {
t.is(err && err.name, 'TypeError')
t.is(err && err.message, "A batch operation must have a type property that is 'put' or 'del'", 'correct error message (callback)')
}))
db.batch([{ key: 'key', value: 'value', type: 'foo' }], assertAsync(function (err) {
t.is(err && err.name, 'TypeError')
t.is(err && err.message, "A batch operation must have a type property that is 'put' or 'del'", 'correct error message (callback)')
}))
db.batch([{ key: 'key', value: 'value' }]).catch(function (err) {
t.is(err.name, 'TypeError')
t.is(err.message, "A batch operation must have a type property that is 'put' or 'del'", 'correct error message (promise)')
})
db.batch([{ key: 'key', value: 'value', type: 'foo' }]).catch(function (err) {
t.is(err.name, 'TypeError')
t.is(err.message, "A batch operation must have a type property that is 'put' or 'del'", 'correct error message (promise)')
})
}))
test('test batch() with missing or nullish operations', assertAsync.ctx(function (t) {
t.plan(13)
db.batch(assertAsync(function (err) {
t.is(err && err.name, 'TypeError')
t.is(err && err.message, "The first argument 'operations' must be an array", 'correct error message (callback)')
}))
for (const array of [null, undefined]) {
db.batch(array, assertAsync(function (err) {
t.is(err && err.name, 'TypeError')
t.is(err && err.message, "The first argument 'operations' must be an array", 'correct error message (callback)')
}))
db.batch(array).catch(function (err) {
t.is(err.name, 'TypeError')
t.is(err.message, "The first argument 'operations' must be an array", 'correct error message (promise)')
})
}
}))
test('test batch() with null options', function (t) {
t.plan(2)
db.batch([], null, function (err) {
t.error(err)
})
db.batch([], null).then(function () {
t.pass('resolved')
}).catch(t.fail.bind(t))
})
;[null, undefined, 1, true].forEach(function (operation) {
const type = operation === null ? 'null' : typeof operation
test('test batch() with ' + type + ' operation', assertAsync.ctx(function (t) {
t.plan(5)
db.batch([operation], assertAsync(function (err) {
t.is(err && err.name, 'TypeError')
t.is(err && err.message, 'A batch operation must be an object', 'correct error message (callback)')
}))
db.batch([operation]).catch(function (err) {
t.is(err.name, 'TypeError')
t.is(err.message, 'A batch operation must be an object', 'correct error message (promise)')
})
}))
})
test('test batch() with empty array', assertAsync.ctx(function (t) {
t.plan(3)
db.batch([], assertAsync(function (err) {
t.error(err, 'no error from batch()')
}))
db.batch([]).then(function () {
t.pass('resolved')
}).catch(t.fail.bind(t))
}))
}
exports.batch = function (test, testCommon) {
test('test simple batch()', function (t) {
db.batch([{ type: 'put', key: 'foo', value: 'bar' }], function (err) {
t.error(err)
db.get('foo', function (err, value) {
t.error(err)
t.is(value, 'bar')
t.end()
})
})
})
test('test simple batch() with promise', async function (t) {
const db = testCommon.factory()
await db.open()
await db.batch([{ type: 'put', key: 'foo', value: 'bar' }])
t.is(await db.get('foo', { valueEncoding: 'utf8' }), 'bar')
return db.close()
})
test('test multiple batch()', function (t) {
db.batch([
{ type: 'put', key: 'foobatch1', value: 'bar1' },
{ type: 'put', key: 'foobatch2', value: 'bar2' },
{ type: 'put', key: 'foobatch3', value: 'bar3' },
{ type: 'del', key: 'foobatch2' }
], function (err) {
t.error(err)
let r = 0
const done = function () {
if (++r === 3) { t.end() }
}
db.get('foobatch1', function (err, value) {
t.error(err)
t.is(value, 'bar1')
done()
})
db.get('foobatch2', function (err, value) {
t.ok(err, 'entry not found')
t.ok(typeof value === 'undefined', 'value is undefined')
t.ok(verifyNotFoundError(err), 'NotFound error')
done()
})
db.get('foobatch3', function (err, value) {
t.error(err)
t.is(value, 'bar3')
done()
})
})
})
for (const encoding of ['utf8', 'buffer', 'view']) {
if (!testCommon.supports.encodings[encoding]) continue
// NOTE: adapted from memdown
test(`empty values in batch with ${encoding} valueEncoding`, async function (t) {
const db = testCommon.factory({ valueEncoding: encoding })
const values = ['', Uint8Array.from([]), Buffer.alloc(0)]
const expected = encoding === 'utf8' ? values[0] : encoding === 'view' ? values[1] : values[2]
await db.open()
await db.batch(values.map((value, i) => ({ type: 'put', key: String(i), value })))
for (let i = 0; i < values.length; i++) {
const value = await db.get(String(i))
// Buffer is a Uint8Array, so this is allowed
if (encoding === 'view' && Buffer.isBuffer(value)) {
t.same(value, values[2])
} else {
t.same(value, expected)
}
}
return db.close()
})
test(`empty keys in batch with ${encoding} keyEncoding`, async function (t) {
const db = testCommon.factory({ keyEncoding: encoding })
const keys = ['', Uint8Array.from([]), Buffer.alloc(0)]
await db.open()
for (let i = 0; i < keys.length; i++) {
await db.batch([{ type: 'put', key: keys[i], value: String(i) }])
t.same(await db.get(keys[i]), String(i), `got value ${i}`)
}
return db.close()
})
}
}
exports.atomic = function (test, testCommon) {
test('test batch() is atomic', function (t) {
t.plan(4)
let async = false
db.batch([
{ type: 'put', key: 'foobah1', value: 'bar1' },
{ type: 'put', value: 'bar2' },
{ type: 'put', key: 'foobah3', value: 'bar3' }
], function (err) {
t.ok(err, 'should error')
t.ok(async, 'callback is asynchronous')
db.get('foobah1', function (err) {
t.ok(err, 'should not be found')
})
db.get('foobah3', function (err) {
t.ok(err, 'should not be found')
})
})
async = true
})
}
exports.events = function (test, testCommon) {
test('test batch([]) (array-form) emits batch event', async function (t) {
t.plan(2)
const db = testCommon.factory()
await db.open()
t.ok(db.supports.events.batch)
db.on('batch', function (ops) {
t.same(ops, [{ type: 'put', key: 456, value: 99, custom: 123 }])
})
await db.batch([{ type: 'put', key: 456, value: 99, custom: 123 }])
await db.close()
})
}
exports.tearDown = function (test, testCommon) {
test('tearDown', function (t) {
db.close(t.end.bind(t))
})
}
exports.all = function (test, testCommon) {
exports.setUp(test, testCommon)
exports.args(test, testCommon)
exports.batch(test, testCommon)
exports.atomic(test, testCommon)
exports.events(test, testCommon)
exports.tearDown(test, testCommon)
}