You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

804 lines
17 KiB

var test = require('tape')
var encdown = require('..')
var memdown = require('memdown')
var Buffer = require('safe-buffer').Buffer
var hasOwnProperty = Object.prototype.hasOwnProperty
var noop = function () {}
test('opens and closes the underlying db', function (t) {
var _db = {
open: function (opts, cb) {
t.pass('open called')
setImmediate(cb)
},
close: function (cb) {
t.pass('close called')
setImmediate(cb)
}
}
var db = encdown(_db)
db.open(function (err) {
t.error(err, 'no error')
db.close(function (err) {
t.error(err, 'no error')
t.end()
})
})
})
test('encodings default to utf8', function (t) {
var db = encdown(memdown())
t.ok(db.db, '.db should be set')
t.ok(db.codec, '.codec should be set')
t.deepEqual(db.codec.opts, {
keyEncoding: 'utf8',
valueEncoding: 'utf8'
}, 'correct defaults')
t.end()
})
test('default utf8 encoding stringifies numbers', function (t) {
t.plan(3)
var db = encdown({
put: function (key, value, callback) {
t.is(key, '1')
t.is(value, '2')
},
batch: function (ops, options, callback) {
t.same(ops, [{ type: 'put', key: '3', value: '4' }])
}
})
db.put(1, 2, noop)
db.batch([{ type: 'put', key: 3, value: 4 }], noop)
})
test('test safe decode in get', function (t) {
var memdb = memdown()
var db = encdown(memdb, { valueEncoding: 'utf8' })
db.put('foo', 'this {} is [] not : json', function (err) {
t.error(err, 'no error')
var db2 = encdown(memdb, { valueEncoding: 'json' })
db2.get('foo', function (err, value) {
t.equals('EncodingError', err.name)
memdb.close(t.end.bind(t))
})
})
})
test('can decode from string to json', function (t) {
var memdb = memdown()
var db = encdown(memdb, { valueEncoding: 'utf8' })
var data = { thisis: 'json' }
db.put('foo', JSON.stringify(data), function (err) {
t.error(err, 'no error')
var db2 = encdown(memdb, { valueEncoding: 'json' })
db2.get('foo', function (err, value) {
t.error(err, 'no error')
t.deepEqual(value, data, 'JSON.parse')
memdb.close(t.end.bind(t))
})
})
})
test('can decode from json to string', function (t) {
var memdb = memdown()
var db = encdown(memdb, { valueEncoding: 'json' })
var data = { thisis: 'json' }
db.put('foo', data, function (err) {
t.error(err, 'no error')
var db2 = encdown(memdb, { valueEncoding: 'utf8' })
db2.get('foo', function (err, value) {
t.error(err, 'no error')
t.deepEqual(value, JSON.stringify(data), 'JSON.stringify')
memdb.close(t.end.bind(t))
})
})
})
test('binary encoding, using batch', function (t) {
var data = [
{
type: 'put',
key: Buffer.from([1, 2, 3]),
value: Buffer.from([4, 5, 6])
},
{
type: 'put',
key: Buffer.from([7, 8, 9]),
value: Buffer.from([10, 11, 12])
}
]
var db = encdown(memdown(), {
keyEncoding: 'binary',
valueEncoding: 'binary'
})
db.batch(data, function (err) {
t.error(err, 'no error')
db.get(data[0].key, function (err, value) {
t.error(err, 'no error')
t.deepEqual(value, data[0].value)
db.get(data[1].key, function (err, value) {
t.error(err, 'no error')
t.deepEqual(value, data[1].value)
db.close(t.end.bind(t))
})
})
})
})
test('default encoding retrieves a string from underlying store', function (t) {
t.plan(1)
var down = {
get: function (key, options, cb) {
t.is(options.asBuffer, false, '.asBuffer is false')
}
}
var db = encdown(down)
db.get('key', noop)
})
test('custom value encoding that retrieves a string from underlying store', function (t) {
t.plan(1)
var down = {
get: function (key, options, cb) {
t.is(options.asBuffer, false, '.asBuffer is false')
}
}
var db = encdown(down, {
valueEncoding: {
buffer: false
}
})
db.get('key', noop)
})
test('get() forwards error from underlying store', function (t) {
t.plan(1)
var down = {
get: function (key, options, cb) {
process.nextTick(cb, new Error('error from store'))
}
}
encdown(down).get('key', function (err) {
t.is(err.message, 'error from store')
})
})
test('_del() encodes key', function (t) {
t.plan(1)
var down = {
del: function (key, options, cb) {
t.is(key, '2')
}
}
encdown(down).del(2, noop)
})
test('chainedBatch.put() encodes key and value', function (t) {
t.plan(2)
var down = {
batch: function () {
return {
put: function (key, value) {
t.is(key, '1')
t.is(value, '2')
}
}
}
}
encdown(down).batch().put(1, 2)
})
test('chainedBatch.del() encodes key', function (t) {
t.plan(1)
var down = {
batch: function () {
return {
del: function (key) {
t.is(key, '1')
}
}
}
}
encdown(down).batch().del(1)
})
test('chainedBatch.clear() is forwarded to underlying store', function (t) {
t.plan(1)
var down = {
batch: function () {
return {
clear: function () {
t.pass('called')
}
}
}
}
encdown(down).batch().clear()
})
test('chainedBatch.write() is forwarded to underlying store', function (t) {
t.plan(1)
var down = {
batch: function () {
return {
write: function () {
t.pass('called')
}
}
}
}
encdown(down).batch().write(noop)
})
test('custom value encoding that retrieves a buffer from underlying store', function (t) {
t.plan(1)
var down = {
get: function (key, options, cb) {
t.is(options.asBuffer, true, '.asBuffer is true')
}
}
var db = encdown(down, {
valueEncoding: {
buffer: true
}
})
db.get('key', noop)
})
test('.keyAsBuffer and .valueAsBuffer defaults to false', function (t) {
t.plan(2)
var down = {
iterator: function (options) {
t.is(options.keyAsBuffer, false)
t.is(options.valueAsBuffer, false)
}
}
encdown(down).iterator()
})
test('.keyAsBuffer and .valueAsBuffer as buffers if encoding says so', function (t) {
t.plan(2)
var down = {
iterator: function (options) {
t.is(options.keyAsBuffer, true)
t.is(options.valueAsBuffer, true)
}
}
var db = encdown(down, {
keyEncoding: {
buffer: true
},
valueEncoding: {
buffer: true
}
})
db.iterator()
})
test('.keyAsBuffer and .valueAsBuffer as strings if encoding says so', function (t) {
t.plan(2)
var down = {
iterator: function (options) {
t.is(options.keyAsBuffer, false)
t.is(options.valueAsBuffer, false)
}
}
var db = encdown(down, {
keyEncoding: {
buffer: false
},
valueEncoding: {
buffer: false
}
})
db.iterator()
})
test('iterator options.keys and options.values default to true', function (t) {
t.plan(2)
var down = {
iterator: function (options) {
t.is(options.keys, true)
t.is(options.values, true)
}
}
encdown(down).iterator()
})
test('iterator skips keys if options.keys is false', function (t) {
t.plan(4)
var down = {
iterator: function (options) {
t.is(options.keys, false)
return {
next: function (callback) {
callback(null, '', 'value')
}
}
}
}
var keyEncoding = {
decode: function (key) {
t.fail('should not be called')
}
}
var db = encdown(down, { keyEncoding: keyEncoding })
var it = db.iterator({ keys: false })
it.next(function (err, key, value) {
t.ifError(err, 'no next error')
t.is(key, undefined, 'normalized key to undefined')
t.is(value, 'value', 'got value')
})
})
test('iterator skips values if options.values is false', function (t) {
t.plan(4)
var down = {
iterator: function (options) {
t.is(options.values, false)
return {
next: function (callback) {
callback(null, 'key', '')
}
}
}
}
var valueEncoding = {
decode: function (value) {
t.fail('should not be called')
}
}
var db = encdown(down, { valueEncoding: valueEncoding })
var it = db.iterator({ values: false })
it.next(function (err, key, value) {
t.ifError(err, 'no next error')
t.is(key, 'key', 'got key')
t.is(value, undefined, 'normalized value to undefined')
})
})
test('iterator encodes range options', function (t) {
t.plan(7)
var keyEncoding = {
encode: function (key) {
return 'encoded_' + key
},
buffer: false
}
var db = encdown({
iterator: function (options) {
t.is(options.start, 'encoded_1')
t.is(options.end, 'encoded_2')
t.is(options.gt, 'encoded_3')
t.is(options.gte, 'encoded_4')
t.is(options.lt, 'encoded_5')
t.is(options.lte, 'encoded_6')
t.is(options.foo, 7)
}
}, { keyEncoding })
db.iterator({ start: 1, end: 2, gt: 3, gte: 4, lt: 5, lte: 6, foo: 7 })
})
test('iterator does not strip nullish range options', function (t) {
t.plan(12)
encdown({
iterator: function (options) {
t.is(options.gt, null)
t.is(options.gte, null)
t.is(options.lt, null)
t.is(options.lte, null)
}
}).iterator({
gt: null,
gte: null,
lt: null,
lte: null
})
encdown({
iterator: function (options) {
t.ok(hasOwnProperty.call(options, 'gt'))
t.ok(hasOwnProperty.call(options, 'gte'))
t.ok(hasOwnProperty.call(options, 'lt'))
t.ok(hasOwnProperty.call(options, 'lte'))
t.is(options.gt, undefined)
t.is(options.gte, undefined)
t.is(options.lt, undefined)
t.is(options.lte, undefined)
}
}).iterator({
gt: undefined,
gte: undefined,
lt: undefined,
lte: undefined
})
})
test('iterator does not add nullish range options', function (t) {
t.plan(4)
encdown({
iterator: function (options) {
t.notOk(hasOwnProperty.call(options, 'gt'))
t.notOk(hasOwnProperty.call(options, 'gte'))
t.notOk(hasOwnProperty.call(options, 'lt'))
t.notOk(hasOwnProperty.call(options, 'lte'))
}
}).iterator({})
})
test('iterator forwards next() error from underlying iterator', function (t) {
t.plan(1)
var down = {
iterator: function () {
return {
next: function (callback) {
process.nextTick(callback, new Error('from underlying iterator'))
}
}
}
}
var db = encdown(down)
var it = db.iterator()
it.next(function (err, key, value) {
t.is(err.message, 'from underlying iterator')
})
})
test('iterator forwards end() to underlying iterator', function (t) {
t.plan(2)
var down = {
iterator: function () {
return {
end: function (callback) {
t.pass('called')
process.nextTick(callback)
}
}
}
}
var db = encdown(down)
var it = db.iterator()
it.end(function () {
t.pass('called')
})
})
test('iterator catches decoding error from keyEncoding', function (t) {
t.plan(5)
var down = {
iterator: function () {
return {
next: function (callback) {
process.nextTick(callback, null, 'key', 'value')
}
}
}
}
var db = encdown(down, {
keyEncoding: {
decode: function (key) {
t.is(key, 'key')
throw new Error('from codec')
}
}
})
db.iterator().next(function (err, key, value) {
t.is(err.message, 'from codec')
t.is(err.name, 'EncodingError')
t.is(key, undefined)
t.is(value, undefined)
})
})
test('iterator catches decoding error from valueEncoding', function (t) {
t.plan(5)
var down = {
iterator: function () {
return {
next: function (callback) {
process.nextTick(callback, null, 'key', 'value')
}
}
}
}
var db = encdown(down, {
valueEncoding: {
decode: function (value) {
t.is(value, 'value')
throw new Error('from codec')
}
}
})
db.iterator().next(function (err, key, value) {
t.is(err.message, 'from codec')
t.is(err.name, 'EncodingError')
t.is(key, undefined)
t.is(value, undefined)
})
})
test('proxies approximateSize() if it exists', function (t) {
t.is(typeof encdown({ approximateSize: noop }).approximateSize, 'function')
t.ok(encdown({ approximateSize: noop }).supports.additionalMethods.approximateSize)
t.is(encdown({}).approximateSize, undefined)
t.notOk(encdown({}).supports.additionalMethods.approximateSize)
t.end()
})
test('proxies compactRange() if it exists', function (t) {
t.is(typeof encdown({ compactRange: noop }).compactRange, 'function')
t.ok(encdown({ compactRange: noop }).supports.additionalMethods.compactRange)
t.is(encdown({}).compactRange, undefined)
t.notOk(encdown({}).supports.additionalMethods.compactRange)
t.end()
})
test('encodes start and end of approximateSize()', function (t) {
var db = encdown({
approximateSize: function (start, end) {
t.is(start, '1')
t.is(end, '2')
t.end()
}
})
db.approximateSize(1, 2, noop)
})
test('encodes start and end of compactRange()', function (t) {
var db = encdown({
compactRange: function (start, end) {
t.is(start, '1')
t.is(end, '2')
t.end()
}
})
db.compactRange(1, 2, noop)
})
test('encodes start and end of approximateSize() with custom encoding', function (t) {
var db = encdown({
approximateSize: function (start, end) {
t.is(start, '"a"')
t.is(end, '"b"')
t.end()
}
})
db.approximateSize('a', 'b', { keyEncoding: 'json' }, noop)
})
test('encodes start and end of compactRange() with custom encoding', function (t) {
var db = encdown({
compactRange: function (start, end) {
t.is(start, '"a"')
t.is(end, '"b"')
t.end()
}
})
db.compactRange('a', 'b', { keyEncoding: 'json' }, noop)
})
test('encodes seek target', function (t) {
t.plan(1)
var db = encdown({
iterator: function () {
return {
seek: function (target) {
t.is(target, '123', 'encoded number')
}
}
}
}, { keyEncoding: 'json' })
db.iterator().seek(123)
})
test('encodes seek target with custom encoding', function (t) {
t.plan(1)
var targets = []
var db = encdown({
iterator: function () {
return {
seek: function (target) {
targets.push(target)
}
}
}
})
db.iterator().seek('a')
db.iterator({ keyEncoding: 'json' }).seek('a')
t.same(targets, ['a', '"a"'], 'encoded targets')
})
test('encodes nullish seek target', function (t) {
t.plan(1)
var targets = []
var db = encdown({
iterator: function () {
return {
seek: function (target) {
targets.push(target)
}
}
}
}, { keyEncoding: { encode: String } })
// Unlike keys, nullish targets should not be rejected;
// assume that the encoding gives these types meaning.
db.iterator().seek(null)
db.iterator().seek(undefined)
t.same(targets, ['null', 'undefined'], 'encoded')
})
test('clear() forwards default options', function (t) {
t.plan(3)
var down = {
clear: function (options, callback) {
t.is(options.reverse, false)
t.is(options.limit, -1)
t.is(callback, noop)
}
}
encdown(down).clear(noop)
})
test('clear() forwards error from underlying store', function (t) {
t.plan(1)
var down = {
clear: function (options, cb) {
process.nextTick(cb, new Error('error from store'))
}
}
encdown(down).clear(function (err) {
t.is(err.message, 'error from store')
})
})
test('clear() encodes range options', function (t) {
t.plan(5)
var keyEncoding = {
encode: function (key) {
return 'encoded_' + key
},
buffer: false
}
var db = encdown({
clear: function (options) {
t.is(options.gt, 'encoded_1')
t.is(options.gte, 'encoded_2')
t.is(options.lt, 'encoded_3')
t.is(options.lte, 'encoded_4')
t.is(options.foo, 5)
}
}, { keyEncoding })
db.clear({ gt: 1, gte: 2, lt: 3, lte: 4, foo: 5 }, noop)
})
test('clear() does not strip nullish range options', function (t) {
t.plan(12)
encdown({
clear: function (options) {
t.is(options.gt, null)
t.is(options.gte, null)
t.is(options.lt, null)
t.is(options.lte, null)
}
}).clear({
gt: null,
gte: null,
lt: null,
lte: null
}, noop)
encdown({
clear: function (options) {
t.ok(hasOwnProperty.call(options, 'gt'))
t.ok(hasOwnProperty.call(options, 'gte'))
t.ok(hasOwnProperty.call(options, 'lt'))
t.ok(hasOwnProperty.call(options, 'lte'))
t.is(options.gt, undefined)
t.is(options.gte, undefined)
t.is(options.lt, undefined)
t.is(options.lte, undefined)
}
}).clear({
gt: undefined,
gte: undefined,
lt: undefined,
lte: undefined
}, noop)
})
test('clear() does not add nullish range options', function (t) {
t.plan(4)
encdown({
clear: function (options) {
t.notOk(hasOwnProperty.call(options, 'gt'))
t.notOk(hasOwnProperty.call(options, 'gte'))
t.notOk(hasOwnProperty.call(options, 'lt'))
t.notOk(hasOwnProperty.call(options, 'lte'))
}
}).clear({}, noop)
})