'use strict' const test = require('tape') const { DeferredIterator, DeferredKeyIterator, DeferredValueIterator } = require('../../lib/deferred-iterator') const { AbstractIterator, AbstractKeyIterator, AbstractValueIterator } = require('../..') const { mockLevel } = require('../util') const noop = () => {} const identity = (v) => v for (const mode of ['iterator', 'keys', 'values']) { const RealCtor = mode === 'iterator' ? AbstractIterator : mode === 'keys' ? AbstractKeyIterator : AbstractValueIterator const DeferredCtor = mode === 'iterator' ? DeferredIterator : mode === 'keys' ? DeferredKeyIterator : DeferredValueIterator const nextArgs = mode === 'iterator' ? ['key', 'value'] : mode === 'keys' ? ['key'] : ['value'] const privateMethod = '_' + mode const publicMethod = mode // NOTE: adapted from deferred-leveldown test(`deferred ${mode}()`, function (t) { t.plan(8) const keyEncoding = { format: 'utf8', encode (key) { t.is(key, 'foo', 'encoding got key') return key.toUpperCase() }, decode: identity } class MockIterator extends RealCtor { _next (cb) { this.nextTick(cb, null, ...nextArgs) } _close (cb) { this.nextTick(cb) } } const db = mockLevel({ [privateMethod]: function (options) { t.is(options.gt, 'FOO', 'got encoded range option') return new MockIterator(this, options) }, _open: function (options, callback) { t.pass('opened') this.nextTick(callback) } }, { encodings: { utf8: true } }, { keyEncoding }) const it = db[publicMethod]({ gt: 'foo' }) t.ok(it instanceof DeferredCtor, 'is deferred') let nextFirst = false it.next(function (err, ...rest) { nextFirst = true t.error(err, 'no next() error') t.same(rest, nextArgs) }) it.close(function (err) { t.error(err, 'no close() error') t.ok(nextFirst) }) }) // NOTE: adapted from deferred-leveldown test(`deferred ${mode}(): non-deferred operations`, function (t) { t.plan(6) class MockIterator extends RealCtor { _seek (target) { t.is(target, '123') } _next (cb) { this.nextTick(cb, null, ...nextArgs) } } const db = mockLevel({ [privateMethod]: function (options) { return new MockIterator(this, options) } }) db.open(function (err) { t.error(err, 'no open() error') it.seek(123) it.next(function (err, ...rest) { t.error(err, 'no next() error') t.same(rest, nextArgs) it.close(function (err) { t.error(err, 'no close() error') }) }) }) const it = db[publicMethod]({ gt: 'foo' }) t.ok(it instanceof DeferredCtor) }) // NOTE: adapted from deferred-leveldown test(`deferred ${mode}(): iterators are created in order`, function (t) { t.plan(6) const order1 = [] const order2 = [] class MockIterator extends RealCtor {} function db (order) { return mockLevel({ [privateMethod]: function (options) { order.push('iterator created') return new MockIterator(this, options) }, _put: function (key, value, options, callback) { order.push('put') }, _open: function (options, callback) { this.nextTick(callback) } }) } const db1 = db(order1) const db2 = db(order2) db1.open(function (err) { t.error(err, 'no error') t.same(order1, ['iterator created', 'put']) }) db2.open(function (err) { t.error(err, 'no error') t.same(order2, ['put', 'iterator created']) }) t.ok(db1[publicMethod]() instanceof DeferredCtor) db1.put('key', 'value', noop) db2.put('key', 'value', noop) t.ok(db2[publicMethod]() instanceof DeferredCtor) }) for (const method of ['next', 'nextv', 'all']) { test(`deferred ${mode}(): closed upon failed open, verified by ${method}()`, function (t) { t.plan(5) const db = mockLevel({ _open (options, callback) { t.pass('opening') this.nextTick(callback, new Error('_open error')) }, _iterator () { t.fail('should not be called') }, [privateMethod] () { t.fail('should not be called') } }) const it = db[publicMethod]() t.ok(it instanceof DeferredCtor) const original = it._close it._close = function (...args) { t.pass('closed') return original.call(this, ...args) } verifyClosed(t, it, method, () => {}) }) test(`deferred ${mode}(): deferred and real iterators are closed on db.close(), verified by ${method}()`, function (t) { t.plan(10) class MockIterator extends RealCtor { _close (callback) { t.pass('closed') this.nextTick(callback) } } const db = mockLevel({ [privateMethod] (options) { return new MockIterator(this, options) } }) const it = db[publicMethod]() t.ok(it instanceof DeferredCtor) const original = it._close it._close = function (...args) { t.pass('closed') return original.call(this, ...args) } db.close(function (err) { t.ifError(err, 'no close() error') verifyClosed(t, it, method, function () { db.open(function (err) { t.ifError(err, 'no open() error') // Should still be closed verifyClosed(t, it, method, function () { db.close(t.ifError.bind(t)) }) }) }) }) }) } test(`deferred ${mode}(): deferred and real iterators are detached on db.close()`, function (t) { t.plan(4) class MockIterator extends RealCtor {} let real const db = mockLevel({ [privateMethod] (options) { real = new MockIterator(this, options) return real } }) const it = db[publicMethod]() t.ok(it instanceof DeferredCtor) db.close(function (err) { t.ifError(err, 'no close() error') db.open(function (err) { t.ifError(err, 'no open() error') it.close = real.close = it._close = real._close = function () { t.fail('should not be called') } db.close(t.ifError.bind(t)) }) }) }) test(`deferred ${mode}(): defers underlying close()`, function (t) { t.plan(3) class MockIterator extends RealCtor { _close (callback) { order.push('_close') this.nextTick(callback) } } const order = [] const db = mockLevel({ _open (options, callback) { order.push('_open') this.nextTick(callback) }, [privateMethod] (options) { order.push(privateMethod) return new MockIterator(this, options) } }) const it = db[publicMethod]() t.ok(it instanceof DeferredCtor) it.close(function (err) { t.ifError(err, 'no close() error') t.same(order, ['_open', privateMethod, '_close']) }) }) const verifyClosed = function (t, it, method, cb) { const requiredArgs = method === 'nextv' ? [10] : [] it[method](...requiredArgs, function (err) { t.is(err && err.code, 'LEVEL_ITERATOR_NOT_OPEN', `correct error on first ${method}()`) // Should account for userland code that ignores errors it[method](...requiredArgs, function (err) { t.is(err && err.code, 'LEVEL_ITERATOR_NOT_OPEN', `correct error on second ${method}()`) cb() }) }) } }