diff --git a/package.json b/package.json index 1837b6c..25b075d 100644 --- a/package.json +++ b/package.json @@ -34,35 +34,36 @@ "license": "MIT", "devDependencies": { "aegir": "^18.2.1", - "bitcoinjs-lib": "^4.0.2", + "async": "^2.6.1", + "bitcoinjs-lib": "^4.0.3", "chai": "^4.2.0", + "chai-as-promised": "^7.1.1", "dirty-chai": "^2.0.1", - "ethereumjs-block": "^2.1.0", + "ethereumjs-block": "^2.2.0", "ipfs-block-service": "~0.15.2", "ipfs-repo": "~0.26.1", - "ipld-bitcoin": "~0.1.8", - "ipld-ethereum": "^2.0.1", - "ipld-git": "~0.2.2", + "ipld-bitcoin": "~0.1.9", + "ipld-ethereum": "^2.0.3", + "ipld-git": "~0.2.3", "ipld-in-memory": "^2.0.0", "ipld-zcash": "~0.1.6", - "merkle-patricia-tree": "^2.3.2", + "merkle-patricia-tree": "^3.0.0", "multihashes": "~0.4.14", "ncp": "^2.0.0", - "rimraf": "^2.6.2", - "rlp": "^2.1.0", + "rimraf": "^2.6.3", + "rlp": "^2.2.2", "zcash-bitcore-lib": "~0.13.20-rc3" }, "dependencies": { - "async": "^2.6.1", - "cids": "~0.5.5", + "cids": "~0.5.7", "ipfs-block": "~0.8.0", - "ipld-dag-cbor": "~0.13.0", + "ipld-dag-cbor": "~0.13.1", "ipld-dag-pb": "~0.15.2", "ipld-raw": "^2.0.1", "merge-options": "^1.0.1", - "pull-defer": "~0.2.3", - "pull-stream": "^3.6.9", - "pull-traverse": "^1.0.3" + "multicodec": "~0.5.0", + "promisify-es6": "^1.0.3", + "typical": "^3.0.2" }, "contributors": [ "Alan Shaw ", diff --git a/src/index.js b/src/index.js index 69dc5a6..09273df 100644 --- a/src/index.js +++ b/src/index.js @@ -1,21 +1,15 @@ 'use strict' const Block = require('ipfs-block') -const pull = require('pull-stream') const CID = require('cids') -const doUntil = require('async/doUntil') -const joinPath = require('path').join -const osPathSep = require('path').sep -const pullDeferSource = require('pull-defer').source -const pullTraverse = require('pull-traverse') -const map = require('async/map') -const waterfall = require('async/waterfall') const mergeOptions = require('merge-options') const ipldDagCbor = require('ipld-dag-cbor') const ipldDagPb = require('ipld-dag-pb') const ipldRaw = require('ipld-raw') - -function noop () {} +const multicodec = require('multicodec') +const promisify = require('promisify-es6') +const typical = require('typical') +const { extendIterator } = require('./util') class IPLDResolver { constructor (userOptions) { @@ -29,369 +23,394 @@ class IPLDResolver { // Object with current list of active resolvers this.resolvers = {} - // API entry point - this.support = {} - - // Adds support for an IPLD format - this.support.add = (multicodec, resolver, util) => { - if (this.resolvers[multicodec]) { - throw new Error('Resolver already exists for codec "' + multicodec + '"') - } - - this.resolvers[multicodec] = { - resolver: resolver, - util: util - } - } - - this.support.load = options.loadFormat || ((codec, callback) => { - callback(new Error(`No resolver found for codec "${codec}"`)) - }) - - this.support.rm = (multicodec) => { - if (this.resolvers[multicodec]) { - delete this.resolvers[multicodec] + if (typeof options.loadFormat !== 'function') { + this.loadFormat = async (codec) => { + const codecName = multicodec.print[codec] + throw new Error(`No resolver found for codec "${codecName}"`) } + } else { + this.loadFormat = options.loadFormat } // Enable all supplied formats for (const format of options.formats) { - const { resolver, util } = format - const multicodec = resolver.multicodec - this.support.add(multicodec, resolver, util) + this.addFormat(format) } } - get (cid, path, options, callback) { - if (typeof path === 'function') { - callback = path - path = undefined + /** + * Add support for an IPLD Format. + * + * @param {Object} format - The implementation of an IPLD Format. + * @returns {this} + */ + addFormat (format) { + // IPLD Formats are using strings instead of constants for the multicodec + const codecBuffer = multicodec.getCodeVarint(format.resolver.multicodec) + const codec = multicodec.getCode(codecBuffer) + if (this.resolvers[codec]) { + const codecName = multicodec.print[codec] + throw new Error(`Resolver already exists for codec "${codecName}"`) } - if (typeof options === 'function') { - callback = options - options = {} + this.resolvers[codec] = { + resolver: format.resolver, + util: format.util } - // this removes occurrences of ./, //, ../ - // makes sure that path never starts with ./ or / - // path.join is OS specific. Need to convert back to POSIX format. - if (typeof path === 'string') { - path = joinPath('/', path) - .substr(1) - .split(osPathSep) - .join('/') + return this + } + + /** + * Remove support for an IPLD Format. + * + * @param {number} codec - The codec of the IPLD Format to remove. + * @returns {this} + */ + removeFormat (codec) { + if (this.resolvers[codec]) { + delete this.resolvers[codec] } - if (path === '' || !path) { - return this._get(cid, (err, node) => { - if (err) { - return callback(err) - } - callback(null, { - value: node, - remainderPath: '', - cid - }) - }) + return this + } + + /** + * Retrieves IPLD Nodes along the `path` that is rooted at `cid`. + * + * @param {CID} cid - the CID the resolving starts. + * @param {string} path - the path that should be resolved. + * @returns {Iterable.>} - Returns an async iterator of all the IPLD Nodes that were traversed during the path resolving. Every element is an object with these fields: + * - `remainderPath`: the part of the path that wasn’t resolved yet. + * - `value`: the value where the resolved path points to. If further traversing is possible, then the value is a CID object linking to another IPLD Node. If it was possible to fully resolve the path, value is the value the path points to. So if you need the CID of the IPLD Node you’re currently at, just take the value of the previously returned IPLD Node. + */ + resolve (cid, path) { + if (!CID.isCID(cid)) { + throw new Error('`cid` argument must be a CID') + } + if (typeof path !== 'string') { + throw new Error('`path` argument must be a string') } - let value - - doUntil( - (cb) => { - this._getFormat(cid.codec, (err, format) => { - if (err) return cb(err) - - // get block - // use local resolver - // update path value - this.bs.get(cid, (err, block) => { - if (err) { - return cb(err) - } - - format.resolver.resolve(block.data, path, (err, result) => { - if (err) { - return cb(err) - } - value = result.value - path = result.remainderPath - cb() - }) - }) - }) - }, - () => { - const endReached = !path || path === '' || path === '/' - const isTerminal = value && !IPLDResolver._maybeCID(value) - - if ((endReached && isTerminal) || options.localResolve) { - cid = IPLDResolver._maybeCID(value) || cid - - return true - } else { - value = IPLDResolver._maybeCID(value) - // continue traversing - if (value) { - cid = value + const generator = async function * () { + // End iteration if there isn't a CID to follow anymore + while (cid !== null) { + const format = await this._getFormat(cid.codec) + + // get block + // use local resolver + // update path value + const block = await promisify(this.bs.get.bind(this.bs))(cid) + const result = await promisify(format.resolver.resolve)(block.data, path) + + // Prepare for the next iteration if there is a `remainderPath` + path = result.remainderPath + let value = result.value + // NOTE vmx 2018-11-29: Not all IPLD Formats return links as + // CIDs yet. Hence try to convert old style links to CIDs + if (Object.keys(value).length === 1 && '/' in value) { + try { + value = new CID(value['/']) + } catch (_error) { + value = null } - return false - } - }, - (err, results) => { - if (err) { - return callback(err) } - return callback(null, { - value: value, + cid = CID.isCID(value) ? value : null + + yield { remainderPath: path, - cid - }) + value + } } - ) - } + }.bind(this) - getStream (cid, path, options) { - const deferred = pullDeferSource() + return extendIterator(generator()) + } - this.get(cid, path, options, (err, result) => { - if (err) { - return deferred.resolve( - pull.error(err) - ) - } - deferred.resolve( - pull.values([result]) - ) - }) + /** + * Get a node by CID. + * + * @param {CID} cid - The CID of the IPLD Node that should be retrieved. + * @returns {Promise.} - Returns a Promise with the IPLD Node that correspond to the given `cid`. + */ + async get (cid) { + const block = await promisify(this.bs.get.bind(this.bs))(cid) + const format = await this._getFormat(block.cid.codec) + const node = await promisify(format.util.deserialize)(block.data) - return deferred + return node } /** * Get multiple nodes back from an array of CIDs. * - * @param {Array} cids - * @param {function(Error, Array)} callback - * @returns {void} + * @param {Iterable.} cids - The CIDs of the IPLD Nodes that should be retrieved. + * @returns {Iterable.>} - Returns an async iterator with the IPLD Nodes that correspond to the given `cids`. */ - getMany (cids, callback) { - if (!Array.isArray(cids)) { - return callback(new Error('Argument must be an array of CIDs')) + getMany (cids) { + if (!typical.isIterable(cids) || typical.isString(cids) || + Buffer.isBuffer(cids)) { + throw new Error('`cids` must be an iterable of CIDs') } - this.bs.getMany(cids, (err, blocks) => { - if (err) { - return callback(err) + + const generator = async function * () { + for await (const cid of cids) { + yield this.get(cid) } - map(blocks, (block, mapCallback) => { - this._getFormat(block.cid.codec, (err, format) => { - if (err) return mapCallback(err) - format.util.deserialize(block.data, mapCallback) - }) - }, - callback) - }) + }.bind(this) + + return extendIterator(generator()) } - put (node, options, callback) { - if (typeof options === 'function') { - callback = options - return setImmediate(() => callback( - new Error('IPLDResolver.put requires options') - )) + /** + * Stores the given IPLD Node of a recognized IPLD Format. + * + * @param {Object} node - The deserialized IPLD node that should be inserted. + * @param {number} format - The multicodec of the format that IPLD Node should be encoded in. + * @param {Object} [userOptions] - Options is an object with the following properties. + * @param {number} [userOtions.hashAlg=hash algorithm of the given multicodec] - The hashing algorithm that is used to calculate the CID. + * @param {number} [userOptions.cidVersion=1] - The CID version to use. + * @param {boolean} [userOptions.onlyHash=false] - If true the serialized form of the IPLD Node will not be passed to the underlying block store. + * @returns {Promise.} - Returns the CID of the serialized IPLD Nodes. + */ + async put (node, format, userOptions) { + if (format === undefined) { + throw new Error('`put` requires a format') + } + if (typeof format !== 'number') { + throw new Error('`format` parameter must be number (multicodec)') } - callback = callback || noop - if (options.cid && CID.isCID(options.cid)) { - if (options.onlyHash) { - return setImmediate(() => callback(null, options.cid)) - } + const formatImpl = await this._getFormat(format) + const defaultOptions = { + hashAlg: formatImpl.defaultHashAlg, + cidVersion: 1, + onlyHash: false + } + const options = mergeOptions(defaultOptions, userOptions) - return this._put(options.cid, node, callback) + const cidOptions = { + version: options.cidVersion, + hashAlg: options.hashAlg, + onlyHash: options.onlyHash } + const cid = await promisify(formatImpl.util.cid)(node, cidOptions) - this._getFormat(options.format, (err, format) => { - if (err) return callback(err) + if (!options.onlyHash) { + await this._store(cid, node) + } - format.util.cid(node, options, (err, cid) => { - if (err) { - return callback(err) - } + return cid + } + + /** + * Stores the given IPLD Nodes of a recognized IPLD Format. + * + * @param {Iterable.} nodes - Deserialized IPLD nodes that should be inserted. + * @param {number} format - The multicodec of the format that IPLD Node should be encoded in. + * @param {Object} [userOptions] - Options are applied to any of the `nodes` and is an object with the following properties. + * @param {number} [userOtions.hashAlg=hash algorithm of the given multicodec] - The hashing algorithm that is used to calculate the CID. + * @param {number} [userOptions.cidVersion=1] - The CID version to use. + * @param {boolean} [userOptions.onlyHash=false] - If true the serialized form of the IPLD Node will not be passed to the underlying block store. + * @returns {Iterable.>} - Returns an async iterator with the CIDs of the serialized IPLD Nodes. + */ + putMany (nodes, format, userOptions) { + if (!typical.isIterable(nodes) || typical.isString(nodes) || + Buffer.isBuffer(nodes)) { + throw new Error('`nodes` must be an iterable') + } + if (format === undefined) { + throw new Error('`put` requires a format') + } + if (typeof format !== 'number') { + throw new Error('`format` parameter must be number (multicodec)') + } - if (options.onlyHash) { - return callback(null, cid) + let options + let formatImpl + + const generator = async function * () { + for await (const node of nodes) { + // Lazy load the options not when the iterator is initialized, but + // when we hit the first iteration. This way the constructor can be + // a synchronous function. + if (options === undefined) { + formatImpl = await this._getFormat(format) + const defaultOptions = { + hashAlg: formatImpl.defaultHashAlg, + cidVersion: 1, + onlyHash: false + } + options = mergeOptions(defaultOptions, userOptions) } - this._put(cid, node, callback) - }) - }) + yield this.put(node, format, options) + } + }.bind(this) + + return extendIterator(generator()) } - treeStream (cid, path, options) { - if (typeof path === 'object') { - options = path - path = undefined + /** + * Remove an IPLD Node by the given CID. + * + * @param {CID} cid - The CID of the IPLD Node that should be removed. + * @return {Promise.} The CID of the removed IPLD Node. + */ + async remove (cid) { + return promisify(this.bs.delete.bind(this.bs))(cid) + } + + /** + * Remove IPLD Nodes by the given CIDs. + * + * Throws an error if any of the Blocks can’t be removed. This operation is + * *not* atomic, some Blocks might have already been removed. + * + * @param {Iterable.} cids - The CIDs of the IPLD Nodes that should be removed. + * @return {Iterable.>} Returns an async iterator with the CIDs of the removed IPLD Nodes. + */ + removeMany (cids) { + if (!typical.isIterable(cids) || typical.isString(cids) || + Buffer.isBuffer(cids)) { + throw new Error('`cids` must be an iterable of CIDs') } - options = options || {} + const generator = async function * () { + for await (const cid of cids) { + yield this.remove(cid) + } + }.bind(this) + + return extendIterator(generator()) + } - let p + /** + * Returns all the paths that can be resolved into. + * + * @param {Object} cid - The ID to get the paths from + * @param {string} [offsetPath=''] - the path to start to retrieve the other paths from. + * @param {Object} [userOptions] + * @param {number} [userOptions.recursive=false] - whether to get the paths recursively or not. `false` resolves only the paths of the given CID. + * @returns {Iterable.>} - Returns an async iterator with paths that can be resolved into + */ + tree (cid, offsetPath, userOptions) { + if (typeof offsetPath === 'object') { + userOptions = offsetPath + offsetPath = undefined + } + offsetPath = offsetPath || '' - if (!options.recursive) { - p = pullDeferSource() + const defaultOptions = { + recursive: false + } + const options = mergeOptions(defaultOptions, userOptions) + + // If a path is a link then follow it and return its CID + const maybeRecurse = async (block, treePath) => { + // A treepath we might want to follow recursively + const format = await this._getFormat(block.cid.codec) + const link = await promisify( + format.resolver.isLink)(block.data, treePath) + // Something to follow recusively, hence push it into the queue + if (link) { + const cid = IPLDResolver._maybeCID(link) + return cid + } else { + return null + } + } - waterfall([ - (cb) => this._getFormat(cid.codec, cb), - (format, cb) => this.bs.get(cid, (err, block) => { - if (err) return cb(err) - cb(null, format, block) - }), - (format, block, cb) => format.resolver.tree(block.data, cb) - ], (err, paths) => { - if (err) { - p.abort(err) - return p + const generator = async function * () { + // The list of paths that will get returned + const treePaths = [] + // The current block, needed to call `isLink()` on every interation + let block + // The list of items we want to follow recursively. The items are + // an object consisting of the CID and the currently already resolved + // path + const queue = [{ cid, basePath: '' }] + // The path that was already traversed + let basePath + + // End of iteration if there aren't any paths left to return or + // if we don't want to traverse recursively and have already + // returne the first level + while (treePaths.length > 0 || queue.length > 0) { + // There aren't any paths left, get them from the given CID + if (treePaths.length === 0 && queue.length > 0) { + ({ cid, basePath } = queue.shift()) + const format = await this._getFormat(cid.codec) + block = await promisify(this.bs.get.bind(this.bs))(cid) + + const paths = await promisify(format.resolver.tree)(block.data) + treePaths.push(...paths) } - p.resolve(pull.values(paths)) - }) - } - // recursive - if (options.recursive) { - p = pull( - pullTraverse.widthFirst({ - basePath: null, - cid: cid - }, (el) => { - // pass the paths through the pushable pull stream - // continue traversing the graph by returning - // the next cids with deferred - - if (typeof el === 'string') { - return pull.empty() - } + const treePath = treePaths.shift() + let fullPath = basePath + treePath - const deferred = pullDeferSource() - const cid = el.cid - - waterfall([ - (cb) => this._getFormat(cid.codec, cb), - (format, cb) => this.bs.get(cid, (err, block) => { - if (err) return cb(err) - cb(null, format, block) - }), - (format, block, cb) => format.resolver.tree(block.data, (err, paths) => { - if (err) { - return cb(err) - } - map(paths, (p, cb) => { - format.resolver.isLink(block.data, p, (err, link) => { - if (err) { - return cb(err) - } - cb(null, { path: p, link: link }) - }) - }, cb) - }) - ], (err, paths) => { - if (err) { - deferred.abort(err) - return deferred - } - - deferred.resolve(pull.values(paths.map((p) => { - const base = el.basePath ? el.basePath + '/' + p.path : p.path - if (p.link) { - return { - basePath: base, - cid: IPLDResolver._maybeCID(p.link) - } - } - return base - }))) - }) - return deferred - }), - pull.map((e) => { - if (typeof e === 'string') { - return e + // Only follow links if recursion is intended + if (options.recursive) { + cid = await maybeRecurse(block, treePath) + if (cid !== null) { + queue.push({ cid, basePath: fullPath + '/' }) } - return e.basePath - }), - pull.filter(Boolean) - ) - } + } - // filter out by path - if (path) { - return pull( - p, - pull.map((el) => { - if (el.indexOf(path) === 0) { - el = el.slice(path.length + 1) - return el + // Return it if it matches the given offset path, but is not the + // offset path itself + if (fullPath.startsWith(offsetPath) && + fullPath.length > offsetPath.length) { + if (offsetPath.length > 0) { + fullPath = fullPath.slice(offsetPath.length + 1) } - }), - pull.filter(Boolean) - ) - } - return p - } + yield fullPath + } + } + }.bind(this) - remove (cids, callback) { - this.bs.delete(cids, callback) + return extendIterator(generator()) } /* */ /* internals */ /* */ + async _getFormat (codec) { + // TODO vmx 2019-01-24: Once all CIDs support accessing the codec code + // instead of the name, remove this part + if (typeof codec === 'string') { + const constantName = codec.toUpperCase().replace(/-/g, '_') + codec = multicodec[constantName] + } - _get (cid, callback) { - waterfall([ - (cb) => this._getFormat(cid.codec, cb), - (format, cb) => this.bs.get(cid, (err, block) => { - if (err) return cb(err) - cb(null, format, block) - }), - (format, block, cb) => { - format.util.deserialize(block.data, (err, deserialized) => { - if (err) { - return cb(err) - } - cb(null, deserialized) - }) - } - ], callback) - } - - _getFormat (codec, callback) { if (this.resolvers[codec]) { - return callback(null, this.resolvers[codec]) + return this.resolvers[codec] } // If not supported, attempt to dynamically load this format - this.support.load(codec, (err, format) => { - if (err) return callback(err) - this.resolvers[codec] = format - callback(null, format) - }) + const format = await this.loadFormat(codec) + this.addFormat(format) + return format } - _put (cid, node, callback) { - callback = callback || noop + async _store (cid, node) { + const format = await this._getFormat(cid.codec) + const serialized = await promisify(format.util.serialize)(node) + const block = new Block(serialized, cid) + await promisify(this.bs.put.bind(this.bs))(block) + } - waterfall([ - (cb) => this._getFormat(cid.codec, cb), - (format, cb) => format.util.serialize(node, cb), - (buf, cb) => this.bs.put(new Block(buf, cid), cb) - ], (err) => { - if (err) { - return callback(err) - } - callback(null, cid) - }) + /** + * Deserialize a given block + * + * @param {Object} block - The block to deserialize + * @return {Object} = Returns the deserialized node + */ + async _deserialize (block) { + const format = await this._getFormat(block.cid.codec) + return promisify(format.util.deserialize)(block.data) } /** diff --git a/src/util.js b/src/util.js new file mode 100644 index 0000000..b5a1644 --- /dev/null +++ b/src/util.js @@ -0,0 +1,30 @@ +'use strict' + +exports.first = async (iterator) => { + for await (const value of iterator) { + return value + } +} + +exports.last = async (iterator) => { + let value + for await (value of iterator) { + // Intentionally empty + } + return value +} + +exports.all = async (iterator) => { + const values = [] + for await (const value of iterator) { + values.push(value) + } + return values +} + +exports.extendIterator = (iterator) => { + iterator.first = () => exports.first(iterator) + iterator.last = () => exports.last(iterator) + iterator.all = () => exports.all(iterator) + return iterator +} diff --git a/test/basics.js b/test/basics.js index 42ad2a8..622a1d0 100644 --- a/test/basics.js +++ b/test/basics.js @@ -3,12 +3,14 @@ const chai = require('chai') const dirtyChai = require('dirty-chai') +const chaiAsProised = require('chai-as-promised') const expect = chai.expect chai.use(dirtyChai) +chai.use(chaiAsProised) const BlockService = require('ipfs-block-service') const CID = require('cids') const multihash = require('multihashes') -const pull = require('pull-stream') +const multicodec = require('multicodec') const inMemory = require('ipld-in-memory') const IPLDResolver = require('../src') @@ -33,76 +35,61 @@ module.exports = (repo) => { }) describe('validation', () => { - it('get - errors on unknown resolver', (done) => { + it('resolve - errors on unknown resolver', async () => { const bs = new BlockService(repo) const r = new IPLDResolver({ blockService: bs }) // choosing a format that is not supported - const cid = new CID(1, 'base1', multihash.encode(Buffer.from('abcd', 'hex'), 'sha1')) - r.get(cid, '/', {}, (err, result) => { - expect(err).to.exist() - expect(err.message).to.eql('No resolver found for codec "base1"') - done() - }) + const cid = new CID( + 1, + 'blake2b-8', + multihash.encode(Buffer.from('abcd', 'hex'), 'sha1') + ) + const result = r.resolve(cid, '') + await expect(result.next()).to.be.rejectedWith( + 'No resolver found for codec "blake2b-8"') }) - it('_get - errors on unknown resolver', (done) => { + it('put - errors on unknown resolver', async () => { const bs = new BlockService(repo) const r = new IPLDResolver({ blockService: bs }) // choosing a format that is not supported - const cid = new CID(1, 'base1', multihash.encode(Buffer.from('abcd', 'hex'), 'sha1')) - r.get(cid, (err, result) => { - expect(err).to.exist() - expect(err.message).to.eql('No resolver found for codec "base1"') - done() - }) + await expect(r.put(null, multicodec.BLAKE2B_8)).to.be.rejectedWith( + 'No resolver found for codec "blake2b-8"') }) - it('put - errors on unknown resolver', (done) => { + it('put - errors if no format is provided', async () => { const bs = new BlockService(repo) const r = new IPLDResolver({ blockService: bs }) - // choosing a format that is not supported - r.put(null, { format: 'base1' }, (err, result) => { - expect(err).to.exist() - expect(err.message).to.eql('No resolver found for codec "base1"') - done() - }) + await expect(r.put(null)).to.be.rejectedWith('`put` requires a format') }) - it('put - errors if no options', (done) => { + it('putMany - errors on unknown resolver', async () => { const bs = new BlockService(repo) const r = new IPLDResolver({ blockService: bs }) - r.put(null, (err, result) => { - expect(err).to.exist() - expect(err.message).to.eql('IPLDResolver.put requires options') - done() - }) + // choosing a format that is not supported + const result = r.putMany([null], multicodec.BLAKE2B_8) + await expect(result.next()).to.be.rejectedWith( + 'No resolver found for codec "blake2b-8"') }) - it('_put - errors on unknown resolver', (done) => { + it('putMany - errors if no format is provided', () => { const bs = new BlockService(repo) const r = new IPLDResolver({ blockService: bs }) - // choosing a format that is not supported - const cid = new CID(1, 'base1', multihash.encode(Buffer.from('abcd', 'hex'), 'sha1')) - r._put(cid, null, (err, result) => { - expect(err).to.exist() - expect(err.message).to.eql('No resolver found for codec "base1"') - done() - }) + expect(() => r.putMany([null])).to.be.throw('`put` requires a format') }) - it('treeStream - errors on unknown resolver', (done) => { + it('tree - errors on unknown resolver', async () => { const bs = new BlockService(repo) const r = new IPLDResolver({ blockService: bs }) // choosing a format that is not supported - const cid = new CID(1, 'base1', multihash.encode(Buffer.from('abcd', 'hex'), 'sha1')) - pull( - r.treeStream(cid, '/', {}), - pull.collect(function (err) { - expect(err).to.exist() - expect(err.message).to.eql('No resolver found for codec "base1"') - done() - }) + const cid = new CID( + 1, + 'blake2b-8', + multihash.encode(Buffer.from('abcd', 'hex'), 'sha1') ) + const result = r.tree(cid) + await expect(result.next()).to.be.rejectedWith( + 'No resolver found for codec "blake2b-8"') }) }) } diff --git a/test/format-support.js b/test/format-support.js index be05e1e..2171fc1 100644 --- a/test/format-support.js +++ b/test/format-support.js @@ -3,10 +3,13 @@ const chai = require('chai') const dirtyChai = require('dirty-chai') +const chaiAsProised = require('chai-as-promised') const expect = chai.expect chai.use(dirtyChai) +chai.use(chaiAsProised) const BlockService = require('ipfs-block-service') const dagCBOR = require('ipld-dag-cbor') +const multicodec = require('multicodec') const IPLDResolver = require('../src') @@ -14,72 +17,65 @@ module.exports = (repo) => { describe('IPLD format support', () => { let data, cid - before((done) => { + before(async () => { const bs = new BlockService(repo) const resolver = new IPLDResolver({ blockService: bs }) data = { now: Date.now() } - dagCBOR.util.cid(data, (err, c) => { - expect(err).to.not.exist() - cid = c - resolver.put(data, { cid }, done) - }) + cid = await resolver.put(data, multicodec.DAG_CBOR) }) describe('Dynamic format loading', () => { - it('should fail to dynamically load format', (done) => { + it('should fail to dynamically load format', async () => { const bs = new BlockService(repo) const resolver = new IPLDResolver({ blockService: bs, formats: [] }) - resolver.get(cid, '/', (err) => { - expect(err).to.exist() - expect(err.message).to.equal('No resolver found for codec "dag-cbor"') - done() - }) + const result = resolver.resolve(cid, '') + await expect(result.next()).to.be.rejectedWith( + 'No resolver found for codec "dag-cbor"') }) - it('should fail to dynamically load format via loadFormat option', (done) => { + it('should fail to dynamically load format via loadFormat option', async () => { const errMsg = 'BOOM' + Date.now() const bs = new BlockService(repo) const resolver = new IPLDResolver({ blockService: bs, formats: [], - loadFormat (codec, callback) { - if (codec !== 'dag-cbor') return callback(new Error('unexpected codec')) - setTimeout(() => callback(new Error(errMsg))) + async loadFormat (codec) { + if (codec !== multicodec.DAG_CBOR) { + throw new Error('unexpected codec') + } + throw new Error(errMsg) } }) - resolver.get(cid, '/', (err) => { - expect(err).to.exist() - expect(err.message).to.equal(errMsg) - done() - }) + const result = resolver.resolve(cid, '') + await expect(result.next()).to.be.rejectedWith(errMsg) }) - it('should dynamically load missing format', (done) => { + it('should dynamically load missing format', async () => { const bs = new BlockService(repo) const resolver = new IPLDResolver({ blockService: bs, formats: [], - loadFormat (codec, callback) { - if (codec !== 'dag-cbor') return callback(new Error('unexpected codec')) - setTimeout(() => callback(null, dagCBOR)) + async loadFormat (codec) { + if (codec !== multicodec.DAG_CBOR) { + throw new Error('unexpected codec') + } + return dagCBOR } }) - resolver.get(cid, '/', (err, result) => { - expect(err).to.not.exist() - expect(result.value).to.eql(data) - done() - }) + const result = resolver.resolve(cid, '') + const node = await result.first() + expect(node.value).to.eql(data) }) - it('should not dynamically load format added statically', (done) => { + it('should not dynamically load format added statically', async () => { const bs = new BlockService(repo) const resolver = new IPLDResolver({ blockService: bs, @@ -89,11 +85,8 @@ module.exports = (repo) => { } }) - resolver.get(cid, '/', (err, result) => { - expect(err).to.not.exist() - expect(result.value).to.eql(data) - done() - }) + const result = resolver.resolve(cid, '') + await result.next() }) }) }) diff --git a/test/ipld-all.js b/test/ipld-all.js index a75e50a..5d43ebb 100644 --- a/test/ipld-all.js +++ b/test/ipld-all.js @@ -8,8 +8,10 @@ */ const chai = require('chai') +const chaiAsProised = require('chai-as-promised') const dirtyChai = require('dirty-chai') const expect = chai.expect +chai.use(chaiAsProised) chai.use(dirtyChai) const dagPB = require('ipld-dag-pb') const dagCBOR = require('ipld-dag-cbor') @@ -17,6 +19,7 @@ const each = require('async/each') const waterfall = require('async/waterfall') const CID = require('cids') const inMemory = require('ipld-in-memory') +const multicodec = require('multicodec') const IPLDResolver = require('../src') @@ -52,57 +55,45 @@ describe('IPLD Resolver for dag-cbor + dag-pb', () => { cidCbor = cid each([ - { node: nodePb, cid: cidPb }, - { node: nodeCbor, cid: cidCbor } + { node: nodePb, format: multicodec.DAG_PB, cidVersion: 0 }, + { node: nodeCbor, format: multicodec.DAG_CBOR, cidVersion: 1 } ], (nac, cb) => { - resolver.put(nac.node, { cid: nac.cid }, cb) + resolver.put(nac.node, nac.format, { + cidVersion: nac.cidVersion + }).then( + () => cb(null), + (error) => cb(error) + ) }, cb) } ], done) }) - it('resolve through different formats', (done) => { - resolver.get(cidCbor, 'pb/Data', (err, result) => { - expect(err).to.not.exist() - expect(result.value).to.eql(Buffer.from('I am inside a Protobuf')) - done() - }) - }) + it('resolve through different formats', async () => { + const result = resolver.resolve(cidCbor, 'pb/Data') + const [node1, node2] = await result.all() - it('does not store nodes when onlyHash is passed', (done) => { - waterfall([ - (cb) => dagPB.DAGNode.create(Buffer.from('Some data here'), cb), - (node, cb) => resolver.put(node, { - onlyHash: true, - version: 1, - hashAlg: 'sha2-256', - format: 'dag-pb' - }, cb), - (cid, cb) => resolver.bs._repo.blocks.has(cid, cb) - ], (error, result) => { - if (error) { - return done(error) - } + expect(node1.remainderPath).to.eql('Data') + expect(node1.value).to.eql(cidPb) - expect(result).to.be.false() - done() - }) + expect(node2.remainderPath).to.eql('') + expect(node2.value).to.eql(Buffer.from('I am inside a Protobuf')) }) - it('does not store nodes when onlyHash is passed and a CID is passed', (done) => { - const cid = new CID('QmTmxQfEHbQzntsXPTU4ae2ZgBGwseBmS12AkZnKCkuf2G') - + it('does not store nodes when onlyHash is passed', (done) => { waterfall([ (cb) => dagPB.DAGNode.create(Buffer.from('Some data here'), cb), - (node, cb) => resolver.put(node, { - onlyHash: true, - cid - }, cb), - (cid2, cb) => { - expect(cid2).to.equal(cid) - - resolver.bs._repo.blocks.has(cid2, cb) - } + (node, cb) => { + resolver.put(node, multicodec.DAG_PB, { + onlyHash: true, + cidVersion: 1, + hashAlg: multicodec.SHA2_256 + }).then( + (cid) => cb(null, cid), + (error) => cb(error) + ) + }, + (cid, cb) => resolver.bs._repo.blocks.has(cid, cb) ], (error, result) => { if (error) { return done(error) @@ -113,49 +104,42 @@ describe('IPLD Resolver for dag-cbor + dag-pb', () => { }) }) - describe('getMany', () => { - it('should return nodes correctly', (done) => { - resolver.getMany([cidCbor, cidPb], (err, result) => { - expect(err).to.not.exist() - expect(result.length).to.equal(2) - expect(result).to.deep.equal([nodeCbor, nodePb]) - done() - }) + describe('get', () => { + it('should return nodes correctly', async () => { + const result = resolver.getMany([cidCbor, cidPb]) + const [node1, node2] = await result.all() + expect(node1).to.eql(nodeCbor) + expect(node2).to.eql(nodePb) }) - it('should return nodes in input order', (done) => { - resolver.getMany([cidPb, cidCbor], (err, result) => { - expect(err).to.not.exist() - expect(result.length).to.equal(2) - expect(result).to.deep.equal([nodePb, nodeCbor]) - done() - }) + it('should return nodes in input order', async () => { + const result = resolver.getMany([cidPb, cidCbor]) + const [node1, node2] = await result.all() + expect(node1).to.eql(nodePb) + expect(node2).to.eql(nodeCbor) }) - it('should return error on invalid CID', (done) => { - resolver.getMany([cidCbor, 'invalidcid'], (err, result) => { - expect(err.message).to.equal('Not a valid cid') - expect(result).to.be.undefined() - done() - }) + it('should return error on invalid CID', async () => { + const result = resolver.getMany([cidCbor, 'invalidcid']) + // First node is valid + await result.next() + // Second one is not + await expect(result.next()).to.be.rejectedWith('Not a valid cid') }) - it('should return error on non-existent CID', (done) => { + it('should return error on non-existent CID', async () => { const nonExistentCid = new CID( 'Qma4hjFTnCasJ8PVp3mZbZK5g2vGDT4LByLJ7m8ciyRFZP') - resolver.getMany([cidCbor, nonExistentCid], (err, result) => { - expect(err.message).to.equal('Not Found') - expect(result).to.be.undefined() - done() - }) + const result = resolver.getMany([cidCbor, nonExistentCid]) + // First node is valid + await result.next() + // Second one is not + await expect(result.next()).to.be.rejectedWith('Not Found') }) - it('should return error on invalid input', (done) => { - resolver.getMany('astring', (err, result) => { - expect(err.message).to.equal('Argument must be an array of CIDs') - expect(result).to.be.undefined() - done() - }) + it('should return error on invalid input', () => { + expect(() => resolver.getMany('astring')).to.throw( + '`cids` must be an iterable of CIDs') }) }) }) diff --git a/test/ipld-bitcoin.js b/test/ipld-bitcoin.js index f3421f1..42572c0 100644 --- a/test/ipld-bitcoin.js +++ b/test/ipld-bitcoin.js @@ -10,8 +10,7 @@ const ipldBitcoin = require('ipld-bitcoin') const BitcoinBlock = require('bitcoinjs-lib').Block const multihash = require('multihashes') const series = require('async/series') -const each = require('async/each') -const pull = require('pull-stream') +const multicodec = require('multicodec') const IPLDResolver = require('../src') @@ -83,127 +82,84 @@ module.exports = (repo) => { } ], store) - function store () { - pull( - pull.values([ - { node: node1, cid: cid1 }, - { node: node2, cid: cid2 }, - { node: node3, cid: cid3 } - ]), - pull.asyncMap((nac, cb) => resolver.put(nac.node, { cid: nac.cid }, cb)), - pull.onEnd(done) - ) + async function store () { + const nodes = [node1, node2, node3] + const result = resolver.putMany(nodes, multicodec.BITCOIN_BLOCK) + ;[cid1, cid2, cid3] = await result.all() + + done() } }) - describe('internals', () => { - it('resolver._put', (done) => { - each([ - { node: node1, cid: cid1 }, - { node: node2, cid: cid2 }, - { node: node3, cid: cid3 } - ], (nc, cb) => { - resolver._put(nc.cid, nc.node, cb) - }, done) + describe('public api', () => { + it('resolver.put with format', async () => { + const cid = await resolver.put(node1, multicodec.BITCOIN_BLOCK) + expect(cid.version).to.equal(1) + expect(cid.codec).to.equal('bitcoin-block') + expect(cid.multihash).to.exist() + const mh = multihash.decode(cid.multihash) + expect(mh.name).to.equal('dbl-sha2-256') }) - it('resolver._get', (done) => { - resolver.put(node1, { cid: cid1 }, (err) => { - expect(err).to.not.exist() - resolver.get(cid1, (err, result) => { - expect(err).to.not.exist() - expect(node1.version).to.eql(result.value.version) - done() - }) + it('resolver.put with format + hashAlg', async () => { + const cid = await resolver.put(node1, multicodec.BITCOIN_BLOCK, { + hashAlg: multicodec.SHA3_512 }) + expect(cid.version).to.equal(1) + expect(cid.codec).to.equal('bitcoin-block') + expect(cid.multihash).to.exist() + const mh = multihash.decode(cid.multihash) + expect(mh.name).to.equal('sha3-512') }) - }) - describe('public api', () => { - it('resolver.put', (done) => { - resolver.put(node1, { cid: cid1 }, done) + it('resolves value within 1st node scope', async () => { + const result = resolver.resolve(cid1, 'version') + const node = await result.first() + expect(node.remainderPath).to.eql('') + expect(node.value).to.eql(1) }) - it('resolver.put with format', (done) => { - resolver.put(node1, { format: 'bitcoin-block' }, (err, cid) => { - expect(err).to.not.exist() - expect(cid).to.exist() - expect(cid.version).to.equal(1) - expect(cid.codec).to.equal('bitcoin-block') - expect(cid.multihash).to.exist() - const mh = multihash.decode(cid.multihash) - expect(mh.name).to.equal('dbl-sha2-256') - done() - }) - }) + it('resolves value within nested scope (1 level)', async () => { + const result = resolver.resolve(cid2, 'parent/version') + const [node1, node2] = await result.all() - it('resolver.put with format + hashAlg', (done) => { - resolver.put(node1, { format: 'bitcoin-block', hashAlg: 'sha3-512' }, (err, cid) => { - expect(err).to.not.exist() - expect(cid).to.exist() - expect(cid.version).to.equal(1) - expect(cid.codec).to.equal('bitcoin-block') - expect(cid.multihash).to.exist() - const mh = multihash.decode(cid.multihash) - expect(mh.name).to.equal('sha3-512') - done() - }) + expect(node1.remainderPath).to.eql('version') + expect(node1.value).to.eql(cid1) + + expect(node2.remainderPath).to.eql('') + expect(node2.value).to.eql(1) }) - it('root path (same as get)', (done) => { - resolver.get(cid1, '/', (err, result) => { - expect(err).to.not.exist() + it('resolves value within nested scope (2 levels)', async () => { + const result = resolver.resolve(cid3, 'parent/parent/version') + const [node1, node2, node3] = await result.all() - ipldBitcoin.util.cid(result.value, (err, cid) => { - expect(err).to.not.exist() - expect(cid).to.eql(cid1) - done() - }) - }) - }) + expect(node1.remainderPath).to.eql('parent/version') + expect(node1.value).to.eql(cid2) - it('value within 1st node scope', (done) => { - resolver.get(cid1, 'version', (err, result) => { - expect(err).to.not.exist() - expect(result.value).to.eql(1) - done() - }) - }) + expect(node2.remainderPath).to.eql('version') + expect(node2.value).to.eql(cid1) - it('value within nested scope (1 level)', (done) => { - resolver.get(cid2, 'parent/version', (err, result) => { - expect(err).to.not.exist() - expect(result.value).to.eql(1) - done() - }) + expect(node3.remainderPath).to.eql('') + expect(node3.value).to.eql(1) }) - it('value within nested scope (2 levels)', (done) => { - resolver.get(cid3, 'parent/parent/version', (err, result) => { - expect(err).to.not.exist() - expect(result.value).to.eql(1) - done() - }) + it('resolver.get round-trip', async () => { + const cid = await resolver.put(node1, multicodec.BITCOIN_BLOCK) + const node = await resolver.get(cid) + expect(node).to.deep.equal(node1) }) - it('resolver.remove', (done) => { - resolver.put(node1, { cid: cid1 }, (err) => { - expect(err).to.not.exist() - resolver.get(cid1, (err, result) => { - expect(err).to.not.exist() - expect(result.value.version).to.eql(1) - remove() - }) - }) + it('resolver.remove', async () => { + const cid = await resolver.put(node1, multicodec.BITCOIN_BLOCK) + const sameAsNode1 = await resolver.get(cid) + expect(sameAsNode1).to.deep.equal(node1) + return remove() - function remove () { - resolver.remove(cid1, (err) => { - expect(err).to.not.exist() - resolver.get(cid1, (err) => { - expect(err).to.exist() - done() - }) - }) + async function remove () { + await resolver.remove(cid) + // Verify that the item got really deleted + await expect(resolver.get(cid)).to.eventually.be.rejected() } }) }) diff --git a/test/ipld-dag-cbor.js b/test/ipld-dag-cbor.js index eacc3b9..4ace848 100644 --- a/test/ipld-dag-cbor.js +++ b/test/ipld-dag-cbor.js @@ -3,13 +3,14 @@ const chai = require('chai') const dirtyChai = require('dirty-chai') +const chaiAsProised = require('chai-as-promised') const expect = chai.expect chai.use(dirtyChai) +chai.use(chaiAsProised) const BlockService = require('ipfs-block-service') const dagCBOR = require('ipld-dag-cbor') const series = require('async/series') -const each = require('async/each') -const pull = require('pull-stream') +const multicodec = require('multicodec') const multihash = require('multihashes') const IPLDResolver = require('../src') @@ -67,287 +68,148 @@ module.exports = (repo) => { } ], store) - function store () { - pull( - pull.values([ - { node: node1, cid: cid1 }, - { node: node2, cid: cid2 }, - { node: node3, cid: cid3 } - ]), - pull.asyncMap((nac, cb) => resolver.put(nac.node, { cid: nac.cid }, cb)), - pull.onEnd(done) - ) - } - }) - - describe('internals', () => { - it('resolver._put', (done) => { - each([ - { node: node1, cid: cid1 }, - { node: node2, cid: cid2 }, - { node: node3, cid: cid3 } - ], (nc, cb) => { - resolver._put(nc.cid, nc.node, cb) - }, done) - }) + async function store () { + const nodes = [node1, node2, node3] + const result = resolver.putMany(nodes, multicodec.DAG_CBOR) + ;[cid1, cid2, cid3] = await result.all() - it('resolver._get', (done) => { - resolver.put(node1, { cid: cid1 }, (err) => { - expect(err).to.not.exist() - resolver._get(cid1, (err, node) => { - expect(err).to.not.exist() - expect(node1).to.eql(node) - done() - }) - }) - }) + done() + } }) describe('public api', () => { - it('resolver.put with CID', (done) => { - resolver.put(node1, { cid: cid1 }, done) + it('resolver.put with format', async () => { + const cid = await resolver.put(node1, multicodec.DAG_CBOR) + expect(cid.version).to.equal(1) + expect(cid.codec).to.equal('dag-cbor') + expect(cid.multihash).to.exist() + const mh = multihash.decode(cid.multihash) + expect(mh.name).to.equal('sha2-256') }) - it('resolver.put with format', (done) => { - resolver.put(node1, { format: 'dag-cbor' }, (err, cid) => { - expect(err).to.not.exist() - expect(cid).to.exist() - expect(cid.version).to.equal(1) - expect(cid.codec).to.equal('dag-cbor') - expect(cid.multihash).to.exist() - const mh = multihash.decode(cid.multihash) - expect(mh.name).to.equal('sha2-256') - done() + it('resolver.put with format + hashAlg', async () => { + const cid = await resolver.put(node1, multicodec.DAG_CBOR, { + hashAlg: multicodec.SHA3_512 }) + expect(cid).to.exist() + expect(cid.version).to.equal(1) + expect(cid.codec).to.equal('dag-cbor') + expect(cid.multihash).to.exist() + const mh = multihash.decode(cid.multihash) + expect(mh.name).to.equal('sha3-512') }) - it('resolver.put with format + hashAlg', (done) => { - resolver.put(node1, { format: 'dag-cbor', hashAlg: 'sha3-512' }, (err, cid) => { - expect(err).to.not.exist() - expect(cid).to.exist() - expect(cid.version).to.equal(1) - expect(cid.codec).to.equal('dag-cbor') - expect(cid.multihash).to.exist() - const mh = multihash.decode(cid.multihash) - expect(mh.name).to.equal('sha3-512') - done() - }) + it('resolves value within 1st node scope', async () => { + const result = resolver.resolve(cid1, 'someData') + const node = await result.first() + expect(node.remainderPath).to.eql('') + expect(node.value).to.eql('I am 1') }) - it('resolver.get just CID', (done) => { - resolver.get(cid1, (err, result) => { - expect(err).to.not.exist() - - dagCBOR.util.cid(result.value, (err, cid) => { - expect(err).to.not.exist() - expect(cid).to.eql(cid1) - done() - }) - }) - }) + it('resolves value within nested scope (0 level)', async () => { + const result = resolver.resolve(cid2, 'one') + const [node1, node2] = await result.all() - it('resolver.get root path', (done) => { - resolver.get(cid1, '/', (err, result) => { - expect(err).to.not.exist() + expect(node1.remainderPath).to.eql('') + expect(node1.value).to.eql(cid1) - dagCBOR.util.cid(result.value, (err, cid) => { - expect(err).to.not.exist() - expect(cid).to.eql(cid1) - done() - }) - }) + expect(node2.remainderPath).to.eql('') + expect(node2.value).to.eql({ someData: 'I am 1' }) }) - it('resolver.get relative path `.` (same as get /)', (done) => { - resolver.get(cid1, '.', (err, result) => { - expect(err).to.not.exist() - - dagCBOR.util.cid(result.value, (err, cid) => { - expect(err).to.not.exist() - expect(cid).to.eql(cid1) - done() - }) - }) - }) + it('resolves value within nested scope (1 level)', async () => { + const result = resolver.resolve(cid2, 'one/someData') + const [node1, node2] = await result.all() - it('resolver.get relative path `./` (same as get /)', (done) => { - resolver.get(cid1, './', (err, result) => { - expect(err).to.not.exist() + expect(node1.remainderPath).to.eql('someData') + expect(node1.value).to.eql(cid1) - dagCBOR.util.cid(result.value, (err, cid) => { - expect(err).to.not.exist() - expect(cid).to.eql(cid1) - done() - }) - }) + expect(node2.remainderPath).to.eql('') + expect(node2.value).to.eql('I am 1') }) - it('resolver.get relative path `./one/someData` (same as get one/someData)', (done) => { - resolver.get(cid2, './one/someData', (err, result) => { - expect(err).to.not.exist() - expect(result.value).to.eql('I am 1') - done() - }) - }) + it('resolves value within nested scope (2 levels)', async () => { + const result = resolver.resolve(cid3, 'two/one/someData') + const [node1, node2, node3] = await result.all() - it('resolver.get relative path `one/./someData` (same as get one/someData)', (done) => { - resolver.get(cid2, 'one/./someData', (err, result) => { - expect(err).to.not.exist() - expect(result.value).to.eql('I am 1') - done() - }) - }) + expect(node1.remainderPath).to.eql('one/someData') + expect(node1.value).to.eql(cid2) - it('resolver.get double slash at the beginning `//one/someData` (same as get one/someData)', (done) => { - resolver.get(cid2, '//one/someData', (err, result) => { - expect(err).to.not.exist() - expect(result.value).to.eql('I am 1') - done() - }) - }) - - it('resolver.get double slash in the middle `one//someData` (same as get one/someData)', (done) => { - resolver.get(cid2, 'one//someData', (err, result) => { - expect(err).to.not.exist() - expect(result.value).to.eql('I am 1') - done() - }) - }) + expect(node2.remainderPath).to.eql('someData') + expect(node2.value).to.eql(cid1) - it('resolver.get value within 1st node scope', (done) => { - resolver.get(cid1, 'someData', (err, result) => { - expect(err).to.not.exist() - expect(result.value).to.eql('I am 1') - done() - }) + expect(node3.remainderPath).to.eql('') + expect(node3.value).to.eql('I am 1') }) - it('resolver.get value within nested scope (0 level)', (done) => { - resolver.get(cid2, 'one', (err, result) => { - expect(err).to.not.exist() - expect(result.value).to.eql({ - someData: 'I am 1' - }) - done() - }) + it('fails resolving unavailable path', async () => { + const result = resolver.resolve(cid3, `foo/${Date.now()}`) + await expect(result.next()).to.be.rejectedWith( + 'path not available at root') }) - it('resolver.get value within nested scope (1 level)', (done) => { - resolver.get(cid2, 'one/someData', (err, result) => { - expect(err).to.not.exist() - expect(result.value).to.eql('I am 1') - done() - }) - }) - - it('resolver.get value within nested scope (2 levels)', (done) => { - resolver.get(cid3, 'two/one/someData', (err, result) => { - expect(err).to.not.exist() - expect(result.value).to.eql('I am 1') - expect(result.remainderPath).to.eql('') - expect(result.cid).to.deep.eql(cid1) - - done() - }) - }) - - it('resolver.get calls callback for unavailable path', (done) => { - resolver.get(cid3, `foo/${Date.now()}`, (err) => { - expect(err).to.exist() - expect(err.message).to.contain('path not available') - done() - }) + it('resolver.get round-trip', async () => { + const cid = await resolver.put(node1, multicodec.DAG_CBOR) + const node = await resolver.get(cid) + expect(node).to.deep.equal(node1) }) - it('resolver.tree', (done) => { - pull( - resolver.treeStream(cid3), - pull.collect((err, values) => { - expect(err).to.not.exist() - expect(values).to.eql([ - 'one', - 'two', - 'someData' - ]) - done() - }) - ) + it('resolver.tree', async () => { + const result = resolver.tree(cid3) + const paths = await result.all() + expect(paths).to.eql([ + 'one', + 'two', + 'someData' + ]) }) - it('resolver.tree with exist()ent path', (done) => { - pull( - resolver.treeStream(cid3, 'one'), - pull.collect((err, values) => { - expect(err).to.not.exist() - expect(values).to.eql([]) - done() - }) - ) + it('resolver.tree with exist()ent path', async () => { + const result = resolver.tree(cid3, 'one') + const paths = await result.all() + expect(paths).to.eql([]) }) - it('resolver.tree with non exist()ent path', (done) => { - pull( - resolver.treeStream(cid3, 'bananas'), - pull.collect((err, values) => { - expect(err).to.not.exist() - expect(values).to.eql([]) - done() - }) - ) + it('resolver.tree with non exist()ent path', async () => { + const result = resolver.tree(cid3, 'bananas') + const paths = await result.all() + expect(paths).to.eql([]) }) - it('resolver.tree recursive', (done) => { - pull( - resolver.treeStream(cid3, { recursive: true }), - pull.collect((err, values) => { - expect(err).to.not.exist() - expect(values).to.eql([ - 'one', - 'two', - 'someData', - 'one/someData', - 'two/one', - 'two/someData', - 'two/one/someData' - ]) - done() - }) - ) + it('resolver.tree recursive', async () => { + const result = resolver.tree(cid3, { recursive: true }) + const paths = await result.all() + expect(paths).to.eql([ + 'one', + 'two', + 'someData', + 'one/someData', + 'two/one', + 'two/someData', + 'two/one/someData' + ]) }) - it('resolver.tree with exist()ent path recursive', (done) => { - pull( - resolver.treeStream(cid3, 'two', { recursive: true }), - pull.collect((err, values) => { - expect(err).to.not.exist() - expect(values).to.eql([ - 'one', - 'someData', - 'one/someData' - ]) - done() - }) - ) + it('resolver.tree with exist()ent path recursive', async () => { + const result = resolver.tree(cid3, 'two', { recursive: true }) + const paths = await result.all() + expect(paths).to.eql([ + 'one', + 'someData', + 'one/someData' + ]) }) - it('resolver.remove', (done) => { - resolver.put(node1, { cid: cid1 }, (err) => { - expect(err).to.not.exist() - resolver.get(cid1, (err, result) => { - expect(err).to.not.exist() - expect(node1).to.eql(result.value) - remove() - }) - }) + it('resolver.remove', async () => { + const cid = await resolver.put(node1, multicodec.DAG_CBOR) + const sameAsNode1 = await resolver.get(cid) + expect(sameAsNode1).to.deep.equal(node1) + return remove() - function remove () { - resolver.remove(cid1, (err) => { - expect(err).to.not.exist() - resolver.get(cid1, (err) => { - expect(err).to.exist() - done() - }) - }) + async function remove () { + await resolver.remove(cid) + // Verify that the item got really deleted + await expect(resolver.get(cid)).to.eventually.be.rejected() } }) }) diff --git a/test/ipld-dag-pb.js b/test/ipld-dag-pb.js index bfadb8e..331218f 100644 --- a/test/ipld-dag-pb.js +++ b/test/ipld-dag-pb.js @@ -2,15 +2,16 @@ 'use strict' const chai = require('chai') +const chaiAsProised = require('chai-as-promised') const dirtyChai = require('dirty-chai') const expect = chai.expect +chai.use(chaiAsProised) chai.use(dirtyChai) const BlockService = require('ipfs-block-service') const dagPB = require('ipld-dag-pb') const series = require('async/series') -const each = require('async/each') -const pull = require('pull-stream') const multihash = require('multihashes') +const multicodec = require('multicodec') const IPLDResolver = require('../src') module.exports = (repo) => { @@ -95,190 +96,104 @@ module.exports = (repo) => { }) }) } - ], cids) + ], store) - function cids () { - series([ - (cb) => { - dagPB.util.cid(node1, (err, cid) => { - expect(err).to.not.exist() - cid1 = cid - cb() - }) - }, - (cb) => { - dagPB.util.cid(node2, (err, cid) => { - expect(err).to.not.exist() - cid2 = cid - cb() - }) - }, - (cb) => { - dagPB.util.cid(node3, (err, cid) => { - expect(err).to.not.exist() - cid3 = cid - cb() - }) - } - ], store) - } + async function store () { + const nodes = [node1, node2, node3] + const result = resolver.putMany(nodes, multicodec.DAG_PB) + ;[cid1, cid2, cid3] = await result.all() - function store () { - pull( - pull.values([ - { node: node1, cid: cid1 }, - { node: node2, cid: cid2 }, - { node: node3, cid: cid3 } - ]), - pull.asyncMap((nac, cb) => resolver.put(nac.node, { cid: nac.cid }, cb)), - pull.onEnd(done) - ) + done() } }) - describe('internals', () => { - it('resolver._put', (done) => { - each([ - { node: node1, cid: cid1 }, - { node: node2, cid: cid2 }, - { node: node3, cid: cid3 } - ], (nc, cb) => { - resolver._put(nc.cid, nc.node, cb) - }, done) - }) - - it('resolver._get', (done) => { - resolver.put(node1, { cid: cid1 }, (err) => { - expect(err).to.not.exist() - resolver._get(cid1, (err, node) => { - expect(err).to.not.exist() - done() - }) - }) - }) - }) - describe('public api', () => { - it('resolver.put with CID', (done) => { - resolver.put(node1, { cid: cid1 }, done) + it('resolver.put with format', async () => { + const cid = await resolver.put(node1, multicodec.DAG_PB) + expect(cid.version).to.equal(1) + expect(cid.codec).to.equal('dag-pb') + expect(cid.multihash).to.exist() + const mh = multihash.decode(cid.multihash) + expect(mh.name).to.equal('sha2-256') }) - it('resolver.put with format', (done) => { - resolver.put(node1, { format: 'dag-pb' }, (err, cid) => { - expect(err).to.not.exist() - expect(cid).to.exist() - expect(cid.version).to.equal(0) - expect(cid.codec).to.equal('dag-pb') - expect(cid.multihash).to.exist() - const mh = multihash.decode(cid.multihash) - expect(mh.name).to.equal('sha2-256') - done() + it('resolver.put with format + hashAlg', async () => { + const cid = await resolver.put(node1, multicodec.DAG_PB, { + hashAlg: multicodec.SHA3_512 }) + expect(cid.version).to.equal(1) + expect(cid.codec).to.equal('dag-pb') + expect(cid.multihash).to.exist() + const mh = multihash.decode(cid.multihash) + expect(mh.name).to.equal('sha3-512') }) - it('resolver.put with format + hashAlg', (done) => { - resolver.put(node1, { format: 'dag-pb', hashAlg: 'sha3-512' }, (err, cid) => { - expect(err).to.not.exist() - expect(cid).to.exist() - expect(cid.version).to.equal(1) - expect(cid.codec).to.equal('dag-pb') - expect(cid.multihash).to.exist() - const mh = multihash.decode(cid.multihash) - expect(mh.name).to.equal('sha3-512') - done() - }) + it('resolves a value within 1st node scope', async () => { + const result = resolver.resolve(cid1, 'Data') + const node = await result.first() + expect(node.remainderPath).to.eql('') + expect(node.value).to.eql(Buffer.from('I am 1')) }) - it('resolver.get just CID', (done) => { - resolver.put(node1, { cid: cid1 }, (err) => { - expect(err).to.not.exist() - resolver.get(cid1, (done)) - }) - }) + it('resolves a value within nested scope (1 level)', async () => { + const result = resolver.resolve(cid2, 'Links/0/Hash/Data') + const [node1, node2] = await result.all() - it('resolver.getStream', (done) => { - resolver.put(node1, { cid: cid1 }, (err) => { - expect(err).to.not.exist() - pull( - resolver.getStream(cid1), - pull.collect(done) - ) - }) - }) - - it('resolver.get root path', (done) => { - resolver.get(cid1, '/', (err, result) => { - expect(err).to.not.exist() + expect(node1.remainderPath).to.eql('Data') + expect(node1.value).to.eql(cid1.toV0()) - dagPB.util.cid(result.value, (err, cid) => { - expect(err).to.not.exist() - expect(cid).to.eql(cid1) - done() - }) - }) + expect(node2.remainderPath).to.eql('') + expect(node2.value).to.eql(Buffer.from('I am 1')) }) - it('resolver.get value within 1st node scope', (done) => { - resolver.get(cid1, 'Data', (err, result) => { - expect(err).to.not.exist() - expect(result.value).to.eql(Buffer.from('I am 1')) - done() - }) - }) + it('resolves value within nested scope (2 levels)', async () => { + const result = resolver.resolve(cid3, 'Links/1/Hash/Links/0/Hash/Data') + const [node1, node2, node3] = await result.all() - it('resolver.get value within nested scope (1 level)', (done) => { - resolver.get(cid2, 'Links/0/Hash/Data', (err, result) => { - expect(err).to.not.exist() - expect(result.value).to.eql(Buffer.from('I am 1')) - done() - }) - }) + expect(node1.remainderPath).to.eql('Links/0/Hash/Data') + expect(node1.value).to.eql(cid2.toV0()) - it('resolver.get value within nested scope (2 levels)', (done) => { - resolver.get(cid3, 'Links/1/Hash/Links/0/Hash/Data', (err, result) => { - expect(err).to.not.exist() - expect(result.value).to.eql(Buffer.from('I am 1')) - done() - }) - }) + expect(node2.remainderPath).to.eql('Data') + expect(node2.value).to.eql(cid1.toV0()) - it('resolver.get with option localResolve: true', (done) => { - resolver.get(cid3, 'Links/1/Hash/Links/0/Hash/Data', { localResolve: true }, (err, result) => { - expect(err).to.not.exist() - expect(result.remainderPath).to.equal('Links/0/Hash/Data') - expect(result.value).to.eql({ - '/': 'QmS149H7EbyMuZ2wtEF1sAd7gPwjj4rKAorweAjKMkxr8D' - }) - expect(result.cid).to.deep.equal(cid2) - done() - }) + expect(node3.remainderPath).to.eql('') + expect(node3.value).to.eql(Buffer.from('I am 1')) }) - it('resolver.get value within nested scope (1 level) returns cid of node traversed to', (done) => { - resolver.get(cid2, 'Links/0/Hash/Data', (err, result) => { - expect(err).to.not.exist() - expect(result.cid).to.deep.equal(cid1) - done() - }) + it('resolver.get round-trip', async () => { + const cid = await resolver.put(node1, multicodec.DAG_PB) + const node = await resolver.get(cid) + // `size` is lazy, without a call to it a deep equal check would fail + const _ = node.size // eslint-disable-line no-unused-vars + expect(node).to.deep.equal(node1) }) - it('resolver.remove', (done) => { - resolver.put(node1, { cid: cid1 }, (err) => { - expect(err).to.not.exist() - resolver.get(cid1, (err, node) => { - expect(err).to.not.exist() - remove() + it('resolver.remove', async () => { + // TODO vmx 2018-12-12: The same repo is used for all tests, there + // seems to be some race condition with inserting and removing items. + // Hence create a unique item for this test. Though the tests + // should really be independent so that there are no race conditions. + const createNode = new Promise((resolve, reject) => { + const data = Buffer.from('a dag-pb node') + dagPB.DAGNode.create(data, (err, node) => { + if (err) { + return reject(err) + } + return resolve(node) }) }) - - function remove () { - resolver.remove(cid1, (err) => { - expect(err).to.not.exist() - resolver.get(cid1, (err) => { - expect(err).to.exist() - done() - }) - }) + const node = await createNode + const cid = await resolver.put(node, multicodec.DAG_PB) + const sameAsNode = await resolver.get(cid) + // `size` is lazy, without a call to it a deep equal check would fail + const _ = sameAsNode.size // eslint-disable-line no-unused-vars + expect(sameAsNode.data).to.deep.equal(node.data) + return remove() + + async function remove () { + await resolver.remove(cid) + // Verify that the item got really deleted + await expect(resolver.get(cid)).to.eventually.be.rejected() } }) }) diff --git a/test/ipld-eth-block.js b/test/ipld-eth-block.js index bf86d16..e77eec8 100644 --- a/test/ipld-eth-block.js +++ b/test/ipld-eth-block.js @@ -2,16 +2,16 @@ 'use strict' const chai = require('chai') +const chaiAsProised = require('chai-as-promised') const dirtyChai = require('dirty-chai') const expect = chai.expect +chai.use(chaiAsProised) chai.use(dirtyChai) const BlockService = require('ipfs-block-service') const ipldEthBlock = require('ipld-ethereum').ethBlock const EthBlockHeader = require('ethereumjs-block/header') const multihash = require('multihashes') -const series = require('async/series') -const each = require('async/each') -const pull = require('pull-stream') +const multicodec = require('multicodec') const IPLDResolver = require('../src') @@ -26,176 +26,100 @@ module.exports = (repo) => { let cid2 let cid3 - before((done) => { + before(async () => { const bs = new BlockService(repo) resolver = new IPLDResolver({ blockService: bs, formats: [ipldEthBlock] }) - series([ - (cb) => { - node1 = new EthBlockHeader({ - number: 1 - }) - - ipldEthBlock.util.cid(node1, (err, cid) => { - expect(err).to.not.exist() - cid1 = cid - cb() - }) - }, - (cb) => { - node2 = new EthBlockHeader({ - number: 2, - parentHash: node1.hash() - }) - - ipldEthBlock.util.cid(node2, (err, cid) => { - expect(err).to.not.exist() - cid2 = cid - cb() - }) - }, - (cb) => { - node3 = new EthBlockHeader({ - number: 3, - parentHash: node2.hash() - }) - - ipldEthBlock.util.cid(node3, (err, cid) => { - expect(err).to.not.exist() - cid3 = cid - cb() - }) - } - ], store) - - function store () { - pull( - pull.values([ - { node: node1, cid: cid1 }, - { node: node2, cid: cid2 }, - { node: node3, cid: cid3 } - ]), - pull.asyncMap((nac, cb) => resolver.put(nac.node, { cid: nac.cid }, cb)), - pull.onEnd(done) - ) - } - }) - - describe('internals', () => { - it('resolver._put', (done) => { - each([ - { node: node1, cid: cid1 }, - { node: node2, cid: cid2 }, - { node: node3, cid: cid3 } - ], (nc, cb) => { - resolver._put(nc.cid, nc.node, cb) - }, done) + node1 = new EthBlockHeader({ + number: 1 }) - - it('resolver._get', (done) => { - resolver.put(node1, { cid: cid1 }, (err) => { - expect(err).to.not.exist() - resolver.get(cid1, (err, result) => { - expect(err).to.not.exist() - expect(node1.number.toString('hex')).to.eql('01') - expect(node1.raw).to.eql(result.value.raw) - expect(node1.hash()).to.eql(result.value.hash()) - done() - }) - }) + node2 = new EthBlockHeader({ + number: 2, + parentHash: node1.hash() }) + node3 = new EthBlockHeader({ + number: 3, + parentHash: node2.hash() + }) + + const nodes = [node1, node2, node3] + const result = resolver.putMany(nodes, multicodec.ETH_BLOCK) + ;[cid1, cid2, cid3] = await result.all() }) describe('public api', () => { - it('resolver.put', (done) => { - resolver.put(node1, { cid: cid1 }, done) + it('resolver.put with format', async () => { + const cid = await resolver.put(node1, multicodec.ETH_BLOCK) + expect(cid.version).to.equal(1) + expect(cid.codec).to.equal('eth-block') + expect(cid.multihash).to.exist() + const mh = multihash.decode(cid.multihash) + expect(mh.name).to.equal('keccak-256') }) - it('resolver.put with format', (done) => { - resolver.put(node1, { format: 'eth-block' }, (err, cid) => { - expect(err).to.not.exist() - expect(cid).to.exist() - expect(cid.version).to.equal(1) - expect(cid.codec).to.equal('eth-block') - expect(cid.multihash).to.exist() - const mh = multihash.decode(cid.multihash) - expect(mh.name).to.equal('keccak-256') - done() + it('resolver.put with format + hashAlg', async () => { + const cid = await resolver.put(node1, multicodec.ETH_BLOCK, { + hashAlg: multicodec.KECCAK_512 }) + expect(cid.version).to.equal(1) + expect(cid.codec).to.equal('eth-block') + expect(cid.multihash).to.exist() + const mh = multihash.decode(cid.multihash) + expect(mh.name).to.equal('keccak-512') }) - it('resolver.put with format + hashAlg', (done) => { - resolver.put(node1, { format: 'eth-block', hashAlg: 'keccak-512' }, (err, cid) => { - expect(err).to.not.exist() - expect(cid).to.exist() - expect(cid.version).to.equal(1) - expect(cid.codec).to.equal('eth-block') - expect(cid.multihash).to.exist() - const mh = multihash.decode(cid.multihash) - expect(mh.name).to.equal('keccak-512') - done() - }) + it('resolves value within 1st node scope', async () => { + const result = resolver.resolve(cid1, 'number') + const node = await result.first() + expect(node.remainderPath).to.eql('') + expect(node.value.toString('hex')).to.eql('01') }) - it('root path (same as get)', (done) => { - resolver.get(cid1, '/', (err, result) => { - expect(err).to.not.exist() + it('resolves value within nested scope (1 level)', async () => { + const result = resolver.resolve(cid2, 'parent/number') + const [node1, node2] = await result.all() - ipldEthBlock.util.cid(result.value, (err, cid) => { - expect(err).to.not.exist() - expect(cid).to.eql(cid1) - done() - }) - }) - }) + expect(node1.remainderPath).to.eql('number') + expect(node1.value).to.eql(cid1) - it('value within 1st node scope', (done) => { - resolver.get(cid1, 'number', (err, result) => { - expect(err).to.not.exist() - expect(result.value.toString('hex')).to.eql('01') - done() - }) + expect(node2.remainderPath).to.eql('') + expect(node2.value.toString('hex')).to.eql('01') }) - it('value within nested scope (1 level)', (done) => { - resolver.get(cid2, 'parent/number', (err, result) => { - expect(err).to.not.exist() - expect(result.value.toString('hex')).to.eql('01') - done() - }) + it('resolves value within nested scope (2 levels)', async () => { + const result = resolver.resolve(cid3, 'parent/parent/number') + const [node1, node2, node3] = await result.all() + + expect(node1.remainderPath).to.eql('parent/number') + expect(node1.value).to.eql(cid2) + + expect(node2.remainderPath).to.eql('number') + expect(node2.value).to.eql(cid1) + + expect(node3.remainderPath).to.eql('') + expect(node3.value.toString('hex')).to.eql('01') }) - it('value within nested scope (2 levels)', (done) => { - resolver.get(cid3, 'parent/parent/number', (err, result) => { - expect(err).to.not.exist() - expect(result.value.toString('hex')).to.eql('01') - done() - }) + it('resolver.get round-trip', async () => { + const cid = await resolver.put(node1, multicodec.ETH_BLOCK) + const node = await resolver.get(cid) + // TODO vmx 2018-12-12: Find out why the full nodes not deep equal + expect(node.raw).to.deep.equal(node1.raw) }) - it('resolver.remove', (done) => { - resolver.put(node1, { cid: cid1 }, (err) => { - expect(err).to.not.exist() - resolver.get(cid1, (err, result) => { - expect(err).to.not.exist() - const node = result.value - expect(node1.raw).to.eql(node.raw) - expect(node1.hash()).to.eql(node.hash()) - remove() - }) - }) + it('resolver.remove', async () => { + const cid = await resolver.put(node1, multicodec.ETH_BLOCK) + const sameAsNode1 = await resolver.get(cid) + expect(sameAsNode1.raw).to.deep.equal(node1.raw) + return remove() - function remove () { - resolver.remove(cid1, (err) => { - expect(err).to.not.exist() - resolver.get(cid1, (err) => { - expect(err).to.exist() - done() - }) - }) + async function remove () { + await resolver.remove(cid) + // Verify that the item got really deleted + await expect(resolver.get(cid)).to.eventually.be.rejected() } }) }) diff --git a/test/ipld-eth.js b/test/ipld-eth.js index 9a02004..27af1b7 100644 --- a/test/ipld-eth.js +++ b/test/ipld-eth.js @@ -13,8 +13,7 @@ const loadFixture = require('aegir/fixtures') const async = require('async') const EthBlockHeader = require('ethereumjs-block/header') const EthTrieNode = require('merkle-patricia-tree/trieNode') -const multihashes = require('multihashes') -const CID = require('cids') +const multicodec = require('multicodec') const IPLDResolver = require('../src') @@ -34,8 +33,7 @@ module.exports = (repo) => { async.waterfall([ readFilesFixture, - generateCids, - putInStore + generateCids ], done) function readFilesFixture (cb) { @@ -54,62 +52,58 @@ module.exports = (repo) => { function generateCids (fileData, cb) { ethObjs = { - child: generateForType('child', 'eth-block', fileData.child), - block: generateForType('block', 'eth-block', fileData.block), - stateRoot: generateForType('stateRoot', 'eth-state-trie', fileData.stateRoot), - state0: generateForType('state0', 'eth-state-trie', fileData.state0), - state00: generateForType('state00', 'eth-state-trie', fileData.state00), - state000: generateForType('state000', 'eth-state-trie', fileData.state000), - state0000: generateForType('state0000', 'eth-state-trie', fileData.state0000), - state00001: generateForType('state00001', 'eth-state-trie', fileData.state00001), - state000017: generateForType('state000017', 'eth-state-trie', fileData.state000017) + child: generateForType('child', multicodec.ETH_BLOCK, fileData.child), + block: generateForType('block', multicodec.ETH_BLOCK, fileData.block), + stateRoot: generateForType('stateRoot', multicodec.ETH_STATE_TRIE, fileData.stateRoot), + state0: generateForType('state0', multicodec.ETH_STATE_TRIE, fileData.state0), + state00: generateForType('state00', multicodec.ETH_STATE_TRIE, fileData.state00), + state000: generateForType('state000', multicodec.ETH_STATE_TRIE, fileData.state000), + state0000: generateForType('state0000', multicodec.ETH_STATE_TRIE, fileData.state0000), + state00001: generateForType('state00001', multicodec.ETH_STATE_TRIE, fileData.state00001), + state000017: generateForType('state000017', multicodec.ETH_STATE_TRIE, fileData.state000017) } cb() } - function generateForType (label, type, rawData) { + async function generateForType (label, type, rawData) { let node switch (type) { - case 'eth-block': node = new EthBlockHeader(rawData); break - case 'eth-state-trie': node = new EthTrieNode(rlp.decode(rawData)); break + case multicodec.ETH_BLOCK: node = new EthBlockHeader(rawData); break + case multicodec.ETH_STATE_TRIE: node = new EthTrieNode(rlp.decode(rawData)); break default: throw new Error('Unknown type!') } - const multihash = multihashes.encode(node.hash(), 'keccak-256') - const cid = new CID(1, type, multihash) + const cid = await resolver.put(node, type) + return { raw: rawData, node: node, cid: cid } } - - function putInStore (cb) { - async.each(ethObjs, (nodeData, next) => { - resolver.put(nodeData.node, { cid: nodeData.cid }, next) - }, cb) - } }) - describe('resolver.get', () => { - it('block-to-block', (done) => { - resolver.get(ethObjs.child.cid, '/parent', (err, result) => { - expect(err).to.not.exist() - expect(result.remainderPath).to.equal('') - expect(result.value.number.toString('hex')).to.equal('302516') - done() - }) + describe('resolver.resolve', () => { + it('block-to-block', async () => { + const child = await ethObjs.child + const result = resolver.resolve(child.cid, 'parent') + const [node1, node2] = await result.all() + + expect(node1.remainderPath).to.eql('') + + expect(node2.remainderPath).to.eql('') + expect(node2.value.number.toString('hex')).to.eql('302516') }) - it('block-to-account resolve', (done) => { - resolver.get(ethObjs.child.cid, '/parent/state/0/0/0/0/1/7/2/7/8/a/1/e/6/e/9/6/3/5/e/1/a/3/f/1/1/e/b/0/2/2/d/a/1/f/5/7/e/a/0/0/4/d/8/5/2/d/9/d/1/9/4/2/d/4/3/6/0/8/5/4/0/4/7/1/nonce', (err, result) => { - expect(err).to.not.exist() - expect(result.value.toString('hex'), '03') - expect(result.remainderPath).to.equal('') - done() - }) + it('block-to-account resolve', async () => { + const child = await ethObjs.child + const result = resolver.resolve(child.cid, + 'parent/state/0/0/0/0/1/7/2/7/8/a/1/e/6/e/9/6/3/5/e/1/a/3/f/1/1/e/b/0/2/2/d/a/1/f/5/7/e/a/0/0/4/d/8/5/2/d/9/d/1/9/4/2/d/4/3/6/0/8/5/4/0/4/7/1/nonce') + const node = await result.last() + expect(node.value.toString('hex'), '03') + expect(node.remainderPath).to.equal('') }) }) }) diff --git a/test/ipld-git.js b/test/ipld-git.js index 059b0f3..86636f7 100644 --- a/test/ipld-git.js +++ b/test/ipld-git.js @@ -9,8 +9,7 @@ const BlockService = require('ipfs-block-service') const ipldGit = require('ipld-git') const multihash = require('multihashes') const series = require('async/series') -const each = require('async/each') -const pull = require('pull-stream') +const multicodec = require('multicodec') const IPLDResolver = require('../src') @@ -134,148 +133,113 @@ module.exports = (repo) => { } ], store) - function store () { - pull( - pull.values([ - { node: blobNode, cid: blobCid }, - { node: treeNode, cid: treeCid }, - { node: commitNode, cid: commitCid }, - { node: commit2Node, cid: commit2Cid }, - { node: tagNode, cid: tagCid } - ]), - pull.asyncMap((nac, cb) => resolver.put(nac.node, { cid: nac.cid }, cb)), - pull.onEnd(done) - ) + async function store () { + const nodes = [blobNode, treeNode, commitNode, commit2Node, tagNode] + const result = resolver.putMany(nodes, multicodec.GIT_RAW) + ;[blobCid, treeCid, commitCid, commit2Cid, tagCid] = await result.all() + + done() } }) - describe('internals', () => { - it('resolver._put', (done) => { - each([ - { node: blobNode, cid: blobCid }, - { node: treeNode, cid: treeCid }, - { node: commitNode, cid: commitCid }, - { node: commit2Node, cid: commit2Cid }, - { node: tagNode, cid: tagCid } - ], (nc, cb) => { - resolver._put(nc.cid, nc.node, cb) - }, done) + describe('public api', () => { + it('resolver.put with format', async () => { + const cid = await resolver.put(blobNode, multicodec.GIT_RAW) + expect(cid.version).to.equal(1) + expect(cid.codec).to.equal('git-raw') + expect(cid.multihash).to.exist() + const mh = multihash.decode(cid.multihash) + expect(mh.name).to.equal('sha1') }) - it('resolver._get', (done) => { - resolver.put(blobNode, { cid: blobCid }, (err) => { - expect(err).to.not.exist() - resolver.get(blobCid, (err, result) => { - expect(err).to.not.exist() - expect(blobNode.toString('hex')).to.eql(result.value.toString('hex')) - done() - }) + it('resolver.put with format + hashAlg', async () => { + const cid = await resolver.put(blobNode, multicodec.GIT_RAW, { + hashAlg: multicodec.SHA3_512 }) + expect(cid.version).to.equal(1) + expect(cid.codec).to.equal('git-raw') + expect(cid.multihash).to.exist() + const mh = multihash.decode(cid.multihash) + expect(mh.name).to.equal('sha3-512') }) - }) - describe('public api', () => { - it('resolver.put', (done) => { - resolver.put(blobNode, { cid: blobCid }, done) + it('resolves value within 1st node scope', async () => { + const result = resolver.resolve(commitCid, 'message') + const node = await result.first() + expect(node.remainderPath).to.eql('') + expect(node.value).to.eql('Initial commit\n') }) - it('resolver.put with format', (done) => { - resolver.put(blobNode, { format: 'git-raw' }, (err, cid) => { - expect(err).to.not.exist() - expect(cid).to.exist() - expect(cid.version).to.equal(1) - expect(cid.codec).to.equal('git-raw') - expect(cid.multihash).to.exist() - const mh = multihash.decode(cid.multihash) - expect(mh.name).to.equal('sha1') - done() - }) - }) + it('resolves value within nested node scope (commit/tree)', async () => { + const result = resolver.resolve(commitCid, 'tree/somefile/mode') + const [node1, node2] = await result.all() - it('resolver.put with format + hashAlg', (done) => { - resolver.put(blobNode, { format: 'git-raw', hashAlg: 'sha3-512' }, (err, cid) => { - expect(err).to.not.exist() - expect(cid).to.exist() - expect(cid.version).to.equal(1) - expect(cid.codec).to.equal('git-raw') - expect(cid.multihash).to.exist() - const mh = multihash.decode(cid.multihash) - expect(mh.name).to.equal('sha3-512') - done() - }) + expect(node1.remainderPath).to.eql('somefile/mode') + expect(node1.value).to.eql(treeCid) + + expect(node2.remainderPath).to.eql('') + expect(node2.value).to.eql('100644') }) - it('resolver.get root path', (done) => { - resolver.get(blobCid, '/', (err, result) => { - expect(err).to.not.exist() + it('resolves value within nested node scope (commit/tree/blob)', async () => { + const result = resolver.resolve(commitCid, 'tree/somefile/hash') + const [node1, node2, node3] = await result.all() - ipldGit.util.cid(result.value, (err, cid) => { - expect(err).to.not.exist() - expect(cid).to.eql(blobCid) - done() - }) - }) - }) + expect(node1.remainderPath).to.eql('somefile/hash') + expect(node1.value).to.eql(treeCid) - it('value within 1st node scope', (done) => { - resolver.get(commitCid, 'message', (err, result) => { - expect(err).to.not.exist() - expect(result.value).to.eql('Initial commit\n') - done() - }) - }) + expect(node2.remainderPath).to.eql('') + expect(node2.value).to.eql(blobCid) - it('value within nested node scope (commit/tree)', (done) => { - resolver.get(commitCid, 'tree/somefile/mode', (err, result) => { - expect(err).to.not.exist() - expect(result.value).to.eql('100644') - done() - }) + expect(node3.remainderPath).to.eql('') + expect(node3.value).to.eql(blobNode) }) - it('value within nested node scope (commit/tree/blob)', (done) => { - resolver.get(commitCid, 'tree/somefile/hash', (err, result) => { - expect(err).to.not.exist() - expect(blobNode.toString('hex')).to.eql(result.value.toString('hex')) - done() - }) + it('resolves value within nested node scope (commit/commit/tree/blob)', async () => { + const result = resolver.resolve(commit2Cid, 'parents/0/tree/somefile/hash') + const nodes = await result.all() + + const node1 = nodes.shift() + expect(node1.remainderPath).to.eql('tree/somefile/hash') + expect(node1.value).to.eql(commitCid) + + // The nodes in between were already tested by some other test + const last = await nodes.pop() + expect(last.remainderPath).to.eql('') + expect(last.value).to.eql(blobNode) }) - it('value within nested node scope (commit/commit/tree/blob)', (done) => { - resolver.get(commit2Cid, 'parents/0/tree/somefile/hash', (err, result) => { - expect(err).to.not.exist() - expect(blobNode.toString('hex')).to.eql(result.value.toString('hex')) - done() - }) + it('resolves value within nested node scope (tag/commit/commit/tree/blob)', async () => { + const result = resolver.resolve(tagCid, + 'object/parents/0/tree/somefile/hash') + const nodes = await result.all() + + const node1 = nodes.shift() + expect(node1.remainderPath).to.eql('parents/0/tree/somefile/hash') + expect(node1.value).to.eql(commit2Cid) + + // The nodes in between were already tested by some other test + const last = nodes.pop() + expect(last.remainderPath).to.eql('') + expect(last.value).to.eql(blobNode) }) - it('value within nested node scope (tag/commit/commit/tree/blob)', (done) => { - resolver.get(tagCid, 'object/parents/0/tree/somefile/hash', (err, result) => { - expect(err).to.not.exist() - expect(blobNode.toString('hex')).to.eql(result.value.toString('hex')) - done() - }) + it('resolver.get round-trip', async () => { + const cid = await resolver.put(blobNode, multicodec.GIT_RAW) + const node = await resolver.get(cid) + expect(node).to.deep.equal(blobNode) }) - it('resolver.remove', (done) => { - resolver.put(blobNode, { cid: blobCid }, (err) => { - expect(err).to.not.exist() - resolver.get(blobCid, (err, result) => { - expect(err).to.not.exist() - const node = result.value - expect(blobNode.toString('hex')).to.eql(node.toString('hex')) - remove() - }) - }) + it('resolver.remove', async () => { + const cid = await resolver.put(blobNode, multicodec.GIT_RAW) + const sameAsBlobNode = await resolver.get(cid) + expect(sameAsBlobNode).to.deep.equal(blobNode) + return remove() - function remove () { - resolver.remove(blobCid, (err) => { - expect(err).to.not.exist() - resolver.get(blobCid, (err) => { - expect(err).to.exist() - done() - }) - }) + async function remove () { + await resolver.remove(cid) + // Verify that the item got really deleted + await expect(resolver.get(cid)).to.eventually.be.rejected() } }) }) diff --git a/test/ipld-zcash.js b/test/ipld-zcash.js index c8bba30..9d9de42 100644 --- a/test/ipld-zcash.js +++ b/test/ipld-zcash.js @@ -10,8 +10,7 @@ const ipldZcash = require('ipld-zcash') const ZcashBlockHeader = require('zcash-bitcore-lib').BlockHeader const multihash = require('multihashes') const series = require('async/series') -const each = require('async/each') -const pull = require('pull-stream') +const multicodec = require('multicodec') const IPLDResolver = require('../src') @@ -88,127 +87,84 @@ module.exports = (repo) => { } ], store) - function store () { - pull( - pull.values([ - { node: node1, cid: cid1 }, - { node: node2, cid: cid2 }, - { node: node3, cid: cid3 } - ]), - pull.asyncMap((nac, cb) => resolver.put(nac.node, { cid: nac.cid }, cb)), - pull.onEnd(done) - ) + async function store () { + const nodes = [node1, node2, node3] + const result = resolver.putMany(nodes, multicodec.ZCASH_BLOCK) + ;[cid1, cid2, cid3] = await result.all() + + done() } }) - describe('internals', () => { - it('resolver._put', (done) => { - each([ - { node: node1, cid: cid1 }, - { node: node2, cid: cid2 }, - { node: node3, cid: cid3 } - ], (nc, cb) => { - resolver._put(nc.cid, nc.node, cb) - }, done) + describe('public api', () => { + it('resolver.put with format', async () => { + const cid = await resolver.put(node1, multicodec.ZCASH_BLOCK) + expect(cid.version).to.equal(1) + expect(cid.codec).to.equal('zcash-block') + expect(cid.multihash).to.exist() + const mh = multihash.decode(cid.multihash) + expect(mh.name).to.equal('dbl-sha2-256') }) - it('resolver._get', (done) => { - resolver.put(node1, { cid: cid1 }, (err) => { - expect(err).to.not.exist() - resolver.get(cid1, (err, result) => { - expect(err).to.not.exist() - expect(node1.version).to.eql(result.value.version) - done() - }) + it('resolver.put with format + hashAlg', async () => { + const cid = await resolver.put(node1, multicodec.ZCASH_BLOCK, { + hashAlg: multicodec.SHA3_512 }) + expect(cid.version).to.equal(1) + expect(cid.codec).to.equal('zcash-block') + expect(cid.multihash).to.exist() + const mh = multihash.decode(cid.multihash) + expect(mh.name).to.equal('sha3-512') }) - }) - describe('public api', () => { - it('resolver.put', (done) => { - resolver.put(node1, { cid: cid1 }, done) + it('resolves value within 1st node scope', async () => { + const result = resolver.resolve(cid1, 'version') + const node = await result.first() + expect(node.remainderPath).to.eql('') + expect(node.value).to.eql(1) }) - it('resolver.put with format', (done) => { - resolver.put(node1, { format: 'zcash-block' }, (err, cid) => { - expect(err).to.not.exist() - expect(cid).to.exist() - expect(cid.version).to.equal(1) - expect(cid.codec).to.equal('zcash-block') - expect(cid.multihash).to.exist() - const mh = multihash.decode(cid.multihash) - expect(mh.name).to.equal('dbl-sha2-256') - done() - }) - }) + it('resolves value within nested scope (1 level)', async () => { + const result = resolver.resolve(cid2, 'parent/version') + const [node1, node2] = await result.all() - it('resolver.put with format + hashAlg', (done) => { - resolver.put(node1, { format: 'zcash-block', hashAlg: 'sha3-512' }, (err, cid) => { - expect(err).to.not.exist() - expect(cid).to.exist() - expect(cid.version).to.equal(1) - expect(cid.codec).to.equal('zcash-block') - expect(cid.multihash).to.exist() - const mh = multihash.decode(cid.multihash) - expect(mh.name).to.equal('sha3-512') - done() - }) + expect(node1.remainderPath).to.eql('version') + expect(node1.value).to.eql(cid1) + + expect(node2.remainderPath).to.eql('') + expect(node2.value).to.eql(1) }) - it('root path (same as get)', (done) => { - resolver.get(cid1, '/', (err, result) => { - expect(err).to.not.exist() + it('resolves value within nested scope (2 levels)', async () => { + const result = resolver.resolve(cid3, 'parent/parent/version') + const [node1, node2, node3] = await result.all() - ipldZcash.util.cid(result.value, (err, cid) => { - expect(err).to.not.exist() - expect(cid).to.eql(cid1) - done() - }) - }) - }) + expect(node1.remainderPath).to.eql('parent/version') + expect(node1.value).to.eql(cid2) - it('value within 1st node scope', (done) => { - resolver.get(cid1, 'version', (err, result) => { - expect(err).to.not.exist() - expect(result.value).to.eql(1) - done() - }) - }) + expect(node2.remainderPath).to.eql('version') + expect(node2.value).to.eql(cid1) - it('value within nested scope (1 level)', (done) => { - resolver.get(cid2, 'parent/version', (err, result) => { - expect(err).to.not.exist() - expect(result.value).to.eql(1) - done() - }) + expect(node3.remainderPath).to.eql('') + expect(node3.value).to.eql(1) }) - it('value within nested scope (2 levels)', (done) => { - resolver.get(cid3, 'parent/parent/version', (err, result) => { - expect(err).to.not.exist() - expect(result.value).to.eql(1) - done() - }) + it('resolver.get round-trip', async () => { + const cid = await resolver.put(node1, multicodec.ZCASH_BLOCK) + const node = await resolver.get(cid) + expect(node.toString()).to.deep.equal(node1.toString()) }) - it('resolver.remove', (done) => { - resolver.put(node1, { cid: cid1 }, (err) => { - expect(err).to.not.exist() - resolver.get(cid1, (err, result) => { - expect(err).to.not.exist() - expect(result.value.version).to.eql(1) - remove() - }) - }) + it('resolver.remove', async () => { + const cid = await resolver.put(node1, multicodec.ZCASH_BLOCK) + const sameAsNode1 = await resolver.get(cid) + expect(sameAsNode1).to.deep.equal(node1) + return remove() - function remove () { - resolver.remove(cid1, (err) => { - expect(err).to.not.exist() - resolver.get(cid1, (err) => { - expect(err).to.exist() - done() - }) - }) + async function remove () { + await resolver.remove(cid) + // Verify that the item got really deleted + await expect(resolver.get(cid)).to.eventually.be.rejected() } }) })