diff --git a/package.json b/package.json index 0e8ceaff7b..3bdaa3b57a 100644 --- a/package.json +++ b/package.json @@ -74,12 +74,12 @@ "ncp": "^2.0.0", "qs": "^6.5.2", "rimraf": "^2.6.2", + "sinon": "^7.1.1", "stream-to-promise": "^2.2.0" }, "dependencies": { "@nodeutils/defaults-deep": "^1.1.0", "async": "^2.6.1", - "base32.js": "~0.1.0", "big.js": "^5.2.2", "binary-querystring": "~0.1.2", "bl": "^2.1.2", diff --git a/src/core/components/init.js b/src/core/components/init.js index 02da39b647..3473516b9f 100644 --- a/src/core/components/init.js +++ b/src/core/components/init.js @@ -113,7 +113,7 @@ module.exports = function init (self) { (_, cb) => { const offlineDatastore = new OfflineDatastore(self._repo) - self._ipns = new IPNS(offlineDatastore, self) + self._ipns = new IPNS(offlineDatastore, self._repo, self._peerInfo, self._keychain, self._options) cb(null, true) }, // add empty unixfs dir object (go-ipfs assumes this exists) diff --git a/src/core/components/pre-start.js b/src/core/components/pre-start.js index 9914a8167e..13d914acc4 100644 --- a/src/core/components/pre-start.js +++ b/src/core/components/pre-start.js @@ -7,10 +7,6 @@ const waterfall = require('async/waterfall') const Keychain = require('libp2p-keychain') const defaultsDeep = require('@nodeutils/defaults-deep') const NoKeychain = require('./no-keychain') - -const IPNS = require('../ipns') -const OfflineDatastore = require('../ipns/routing/offline-datastore') - /* * Load stuff from Repo into memory */ @@ -99,13 +95,6 @@ module.exports = function preStart (self) { cb() }, - // Setup offline routing for IPNS. - (cb) => { - const offlineDatastore = new OfflineDatastore(self._repo) - - self._ipns = new IPNS(offlineDatastore, self) - cb() - }, (cb) => self.pin._load(cb) ], callback) } diff --git a/src/core/components/start.js b/src/core/components/start.js index 52cceee57c..518d613b0e 100644 --- a/src/core/components/start.js +++ b/src/core/components/start.js @@ -43,14 +43,14 @@ module.exports = (self) => { // TODO Add IPNS pubsub if enabled - // NOTE: Until the IPNS over DHT is not ready, it is being replaced by the local repo datastore - // When DHT is added, If local option enabled, should receive offlineDatastore as well + // NOTE: IPNS routing is being replaced by the local repo datastore while the IPNS over DHT is not ready + // When DHT is added, if local option enabled, should receive offlineDatastore as well const offlineDatastore = new OfflineDatastore(self._repo) ipnsStores.push(offlineDatastore) // Create ipns routing with a set of datastores const routing = new TieredDatastore(ipnsStores) - self._ipns = new IPNS(routing, self) + self._ipns = new IPNS(routing, self._repo, self._peerInfo, self._keychain, self._options) self._bitswap = new Bitswap( self._libp2pNode, diff --git a/src/core/ipns/index.js b/src/core/ipns/index.js index 65b2b74564..06b43ca330 100644 --- a/src/core/ipns/index.js +++ b/src/core/ipns/index.js @@ -4,6 +4,7 @@ const { createFromPrivKey } = require('peer-id') const series = require('async/series') const Receptacle = require('receptacle') +const errcode = require('err-code') const debug = require('debug') const log = debug('jsipfs:ipns') log.error = debug('jsipfs:ipns:error') @@ -16,9 +17,9 @@ const path = require('./path') const defaultRecordTtl = 60 * 1000 class IPNS { - constructor (routing, ipfs) { - this.publisher = new IpnsPublisher(routing, ipfs._repo) - this.republisher = new IpnsRepublisher(this.publisher, ipfs) + constructor (routing, repo, peerInfo, keychain, options) { + this.publisher = new IpnsPublisher(routing, repo) + this.republisher = new IpnsRepublisher(this.publisher, repo, peerInfo, keychain, options) this.resolver = new IpnsResolver(routing) this.cache = new Receptacle({ max: 1000 }) // Create an LRU cache with max 1000 items this.routing = routing @@ -55,11 +56,20 @@ class IPNS { // Resolve resolve (name, options, callback) { + if (typeof name !== 'string') { + const errMsg = `name received is not valid` + + log.error(errMsg) + return callback(errcode(new Error(errMsg), 'ERR_INVALID_NAME')) + } + if (typeof options === 'function') { callback = options options = {} } + options = options || {} + // If recursive, we should not try to get the cached value if (!options.nocache && !options.recursive) { // Try to get the record from cache diff --git a/src/core/ipns/publisher.js b/src/core/ipns/publisher.js index 0c77031c56..9e8fcdf3f3 100644 --- a/src/core/ipns/publisher.js +++ b/src/core/ipns/publisher.js @@ -32,7 +32,7 @@ class IpnsPublisher { PeerId.createFromPrivKey(privKey.bytes, (err, peerId) => { if (err) { - callback(err) + return callback(err) } this._updateOrCreateRecord(privKey, value, lifetime, peerId, (err, record) => { diff --git a/src/core/ipns/republisher.js b/src/core/ipns/republisher.js index 9ef02158d6..7345dceebd 100644 --- a/src/core/ipns/republisher.js +++ b/src/core/ipns/republisher.js @@ -18,10 +18,12 @@ const defaultBroadcastInterval = 4 * hour const defaultRecordLifetime = 24 * hour class IpnsRepublisher { - constructor (publisher, ipfs) { + constructor (publisher, repo, peerInfo, keychain, options) { this._publisher = publisher - this._ipfs = ipfs - this._repo = ipfs._repo + this._repo = repo + this._peerInfo = peerInfo + this._keychain = keychain + this._options = options this._republishHandle = null } @@ -62,8 +64,8 @@ class IpnsRepublisher { } } - const { privKey } = this._ipfs._peerInfo.id - const { pass } = this._ipfs._options + const { privKey } = this._peerInfo.id + const { pass } = this._options republishHandle.runPeriodically((done) => { this._republishEntries(privKey, pass, () => done(defaultBroadcastInterval)) @@ -98,8 +100,8 @@ class IpnsRepublisher { } // keychain needs pass to get the cryptographic keys - if (this._ipfs._keychain && Boolean(pass)) { - this._ipfs._keychain.listKeys((err, list) => { + if (this._keychain && Boolean(pass)) { + this._keychain.listKeys((err, list) => { if (err) { log.error(err) return @@ -107,7 +109,7 @@ class IpnsRepublisher { each(list, (key, cb) => { waterfall([ - (cb) => this._ipfs._keychain.exportKey(key.name, pass, cb), + (cb) => this._keychain.exportKey(key.name, pass, cb), (pem, cb) => crypto.keys.import(pem, pass, cb) ], (err, privKey) => { if (err) { diff --git a/src/core/ipns/resolver.js b/src/core/ipns/resolver.js index 56c8c813bb..8ea7480c7d 100644 --- a/src/core/ipns/resolver.js +++ b/src/core/ipns/resolver.js @@ -89,7 +89,14 @@ class IpnsResolver { // resolve ipns entries from the provided routing _resolveName (name, callback) { - const peerId = PeerId.createFromB58String(name) + let peerId + + try { + peerId = PeerId.createFromB58String(name) + } catch (err) { + return callback(err) + } + const { routingKey } = ipns.getIdKeys(peerId.toBytes()) // TODO DHT - get public key from routing? @@ -97,11 +104,18 @@ class IpnsResolver { // https://github.com/libp2p/go-libp2p-routing/blob/master/routing.go#L99 this._routing.get(routingKey.toBuffer(), (err, res) => { - if (err || !res) { + if (err) { const errMsg = `record requested was not found for ${name} (${routingKey}) in the network` log.error(errMsg) - return callback(errcode(new Error(errMsg), 'ERR_NO_NETWORK_RECORD_FOUND')) + return callback(errcode(new Error(errMsg), 'ERR_NO_RECORD_FOUND')) + } + + if (!res) { + const errMsg = `record requested was empty for ${name} (${routingKey}) in the network` + + log.error(errMsg) + return callback(errcode(new Error(errMsg), 'ERR_EMPTY_RECORD_FOUND')) } if (!Buffer.isBuffer(res)) { @@ -111,8 +125,15 @@ class IpnsResolver { return callback(errcode(new Error(errMsg), 'ERR_INVALID_RECORD_RECEIVED')) } - const record = Record.deserialize(res) - const ipnsEntry = ipns.unmarshal(record.value) + let ipnsEntry + + try { + const record = Record.deserialize(res) + ipnsEntry = ipns.unmarshal(record.value) + } catch (err) { + log.error(err) + return callback(err) + } ipns.extractPublicKey(peerId, ipnsEntry, (err, pubKey) => { if (err) { diff --git a/src/core/ipns/routing/offline-datastore.js b/src/core/ipns/routing/offline-datastore.js index 1a7e4d1b89..26de52528c 100644 --- a/src/core/ipns/routing/offline-datastore.js +++ b/src/core/ipns/routing/offline-datastore.js @@ -3,6 +3,11 @@ const { Key } = require('interface-datastore') const { encodeBase32 } = require('./utils') +const errcode = require('err-code') +const debug = require('debug') +const log = debug('jsipfs:ipns:offline-datastore') +log.error = debug('jsipfs:ipns:offline-datastore:error') + // Offline datastore aims to mimic the same encoding as routing when storing records // to the local datastore class OfflineDatastore { @@ -18,8 +23,30 @@ class OfflineDatastore { * @returns {void} */ put (key, value, callback) { - // encode key properly - base32(/ipns/{cid}) - const routingKey = new Key('/' + encodeBase32(key), false) + if (!Buffer.isBuffer(key)) { + const errMsg = `Offline datastore key must be a buffer` + + log.error(errMsg) + return callback(errcode(new Error(errMsg), 'ERR_INVALID_KEY')) + } + + if (!Buffer.isBuffer(value)) { + const errMsg = `Offline datastore value must be a buffer` + + log.error(errMsg) + return callback(errcode(new Error(errMsg), 'ERR_INVALID_VALUE')) + } + + let routingKey + + try { + routingKey = this._routingKey(key) + } catch (err) { + const errMsg = `Not possible to generate the routing key` + + log.error(errMsg) + return callback(errcode(new Error(errMsg), 'ERR_GENERATING_ROUTING_KEY')) + } this._repo.datastore.put(routingKey, value, callback) } @@ -31,11 +58,31 @@ class OfflineDatastore { * @returns {void} */ get (key, callback) { - // encode key properly - base32(/ipns/{cid}) - const routingKey = new Key('/' + encodeBase32(key), false) + if (!Buffer.isBuffer(key)) { + const errMsg = `Offline datastore key must be a buffer` + + log.error(errMsg) + return callback(errcode(new Error(errMsg), 'ERR_INVALID_KEY')) + } + + let routingKey + + try { + routingKey = this._routingKey(key) + } catch (err) { + const errMsg = `Not possible to generate the routing key` + + log.error(errMsg) + return callback(errcode(new Error(errMsg), 'ERR_GENERATING_ROUTING_KEY')) + } this._repo.datastore.get(routingKey, callback) } + + // encode key properly - base32(/ipns/{cid}) + _routingKey (key) { + return new Key('/' + encodeBase32(key), false) + } } exports = module.exports = OfflineDatastore diff --git a/src/core/ipns/routing/utils.js b/src/core/ipns/routing/utils.js index 8c0cd278b9..8448d77346 100644 --- a/src/core/ipns/routing/utils.js +++ b/src/core/ipns/routing/utils.js @@ -1,8 +1,7 @@ 'use strict' -const base32 = require('base32.js') +const multibase = require('multibase') module.exports.encodeBase32 = (buf) => { - const enc = new base32.Encoder() - return enc.write(buf).finalize() + return multibase.encode('base32', buf) } diff --git a/test/core/name.js b/test/core/name.js index 3bab36ece0..768a1780ca 100644 --- a/test/core/name.js +++ b/test/core/name.js @@ -1,4 +1,4 @@ -/* eslint max-nested-callbacks: ["error", 6] */ +/* eslint max-nested-callbacks: ["error", 7] */ /* eslint-env mocha */ 'use strict' @@ -7,12 +7,14 @@ const chai = require('chai') const dirtyChai = require('dirty-chai') const expect = chai.expect chai.use(dirtyChai) +const sinon = require('sinon') const fs = require('fs') const isNode = require('detect-node') const IPFS = require('../../src') const ipnsPath = require('../../src/core/ipns/path') +const { Key } = require('interface-datastore') const DaemonFactory = require('ipfsd-ctl') const df = DaemonFactory.create({ type: 'proc' }) @@ -24,191 +26,402 @@ describe('name', function () { return } - let node - let nodeId - let ipfsd - - before(function (done) { - this.timeout(40 * 1000) - df.spawn({ - exec: IPFS, - args: [`--pass ${hat()}`], - config: { Bootstrap: [] } - }, (err, _ipfsd) => { - expect(err).to.not.exist() - ipfsd = _ipfsd - node = _ipfsd.api - - node.id().then((res) => { - expect(res.id).to.exist() - - nodeId = res.id - done() - }) - }) - }) + describe('working locally', function () { + let node + let nodeId + let ipfsd + + before(function (done) { + this.timeout(40 * 1000) + df.spawn({ + exec: IPFS, + args: [`--pass ${hat()}`], + config: { Bootstrap: [] } + }, (err, _ipfsd) => { + expect(err).to.not.exist() + ipfsd = _ipfsd + node = _ipfsd.api - after((done) => ipfsd.stop(done)) + node.id().then((res) => { + expect(res.id).to.exist() - it('should publish correctly with the default options', function (done) { - node.name.publish(ipfsRef, { resolve: false }, (err, res) => { - expect(err).to.not.exist() - expect(res).to.exist() - expect(res.name).to.equal(nodeId) - done() + nodeId = res.id + done() + }) + }) }) - }) - it('should publish and then resolve correctly with the default options', function (done) { - node.name.publish(ipfsRef, { resolve: false }, (err, res) => { - expect(err).to.not.exist() - expect(res).to.exist() + after((done) => ipfsd.stop(done)) - node.name.resolve(nodeId, (err, res) => { + it('should publish and then resolve correctly with the default options', function (done) { + node.name.publish(ipfsRef, { resolve: false }, (err, res) => { expect(err).to.not.exist() expect(res).to.exist() - expect(res.path).to.equal(ipfsRef) - done() + + node.name.resolve(nodeId, (err, res) => { + expect(err).to.not.exist() + expect(res).to.exist() + expect(res.path).to.equal(ipfsRef) + done() + }) }) }) - }) - it('should publish correctly with the lifetime option and resolve', function (done) { - node.name.publish(ipfsRef, { resolve: false, lifetime: '2h' }, (err, res) => { - expect(err).to.not.exist() - expect(res).to.exist() - - node.name.resolve(nodeId, (err, res) => { + it('should publish correctly with the lifetime option and resolve', function (done) { + node.name.publish(ipfsRef, { resolve: false, lifetime: '2h' }, (err, res) => { expect(err).to.not.exist() expect(res).to.exist() - expect(res.path).to.equal(ipfsRef) - done() - }) - }) - }) - it('should not get the entry correctly if its validity time expired', function (done) { - node.name.publish(ipfsRef, { resolve: false, lifetime: '1ms' }, (err, res) => { - expect(err).to.not.exist() - expect(res).to.exist() - - setTimeout(function () { - node.name.resolve(nodeId, (err) => { - expect(err).to.exist() + node.name.resolve(nodeId, (err, res) => { + expect(err).to.not.exist() + expect(res).to.exist() + expect(res.path).to.equal(ipfsRef) done() }) - }, 2) + }) }) - }) - it('should recursively resolve to an IPFS hash', function (done) { - this.timeout(80 * 1000) - const keyName = hat() + it('should not get the entry correctly if its validity time expired', function (done) { + node.name.publish(ipfsRef, { resolve: false, lifetime: '1ms' }, (err, res) => { + expect(err).to.not.exist() + expect(res).to.exist() - node.key.gen(keyName, { type: 'rsa', size: 2048 }, function (err, key) { - expect(err).to.not.exist() + setTimeout(function () { + node.name.resolve(nodeId, (err) => { + expect(err).to.exist() + done() + }) + }, 2) + }) + }) + + it('should recursively resolve to an IPFS hash', function (done) { + this.timeout(80 * 1000) + const keyName = hat() - node.name.publish(ipfsRef, { resolve: false }, (err) => { + node.key.gen(keyName, { type: 'rsa', size: 2048 }, function (err, key) { expect(err).to.not.exist() - node.name.publish(`/ipns/${nodeId}`, { resolve: false, key: keyName }, (err) => { + node.name.publish(ipfsRef, { resolve: false }, (err) => { expect(err).to.not.exist() - node.name.resolve(key.id, { recursive: true }, (err, res) => { + node.name.publish(`/ipns/${nodeId}`, { resolve: false, key: keyName }, (err) => { expect(err).to.not.exist() - expect(res).to.exist() - expect(res.path).to.equal(ipfsRef) - done() + + node.name.resolve(key.id, { recursive: true }, (err, res) => { + expect(err).to.not.exist() + expect(res).to.exist() + expect(res.path).to.equal(ipfsRef) + done() + }) }) }) }) }) - }) - - it('should not recursively resolve to an IPFS hash if the option recursive is not provided', function (done) { - this.timeout(80 * 1000) - const keyName = hat() - node.key.gen(keyName, { type: 'rsa', size: 2048 }, function (err, key) { - expect(err).to.not.exist() + it('should not recursively resolve to an IPFS hash if the option recursive is not provided', function (done) { + this.timeout(80 * 1000) + const keyName = hat() - node.name.publish(ipfsRef, { resolve: false }, (err) => { + node.key.gen(keyName, { type: 'rsa', size: 2048 }, function (err, key) { expect(err).to.not.exist() - node.name.publish(`/ipns/${nodeId}`, { resolve: false, key: keyName }, (err) => { + node.name.publish(ipfsRef, { resolve: false }, (err) => { expect(err).to.not.exist() - node.name.resolve(key.id, (err, res) => { + node.name.publish(`/ipns/${nodeId}`, { resolve: false, key: keyName }, (err) => { expect(err).to.not.exist() - expect(res).to.exist() - expect(res.path).to.equal(`/ipns/${nodeId}`) - done() + + node.name.resolve(key.id, (err, res) => { + expect(err).to.not.exist() + expect(res).to.exist() + expect(res.path).to.equal(`/ipns/${nodeId}`) + done() + }) }) }) }) }) }) -}) -describe('ipns.path', function () { - const path = 'test/fixtures/planets/solar-system.md' - const fixture = { - path, - content: fs.readFileSync(path) - } + describe('republisher', function () { + if (!isNode) { + return + } + + let node + let ipfsd + + before(function (done) { + this.timeout(40 * 1000) + df.spawn({ + exec: IPFS, + args: [`--pass ${hat()}`], + config: { Bootstrap: [] } + }, (err, _ipfsd) => { + expect(err).to.not.exist() + ipfsd = _ipfsd + node = _ipfsd.api - let node - let ipfsd - let nodeId + done() + }) + }) - if (!isNode) { - return - } + afterEach(() => { + sinon.restore() + }) - before(function (done) { - this.timeout(40 * 1000) - df.spawn({ - exec: IPFS, - args: [`--pass ${hat()}`], - config: { Bootstrap: [] } - }, (err, _ipfsd) => { - expect(err).to.not.exist() - node = _ipfsd.api - ipfsd = _ipfsd - - node.id().then((res) => { - expect(res.id).to.exist() - - nodeId = res.id + after((done) => ipfsd.stop(done)) + + it('should republish entries after 60 seconds', function (done) { + this.timeout(100 * 1000) + sinon.spy(node._ipns.republisher, '_republishEntries') + + setTimeout(function () { + expect(node._ipns.republisher._republishEntries.calledOnce).to.equal(true) done() - }) + }, 60 * 1000) }) - }) - after((done) => ipfsd.stop(done)) + it('should error if run republish again', function (done) { + this.timeout(100 * 1000) + sinon.spy(node._ipns.republisher, '_republishEntries') + + try { + node._ipns.republisher.start() + } catch (err) { + expect(err).to.exist() + expect(err.code).to.equal('ERR_REPUBLISH_ALREADY_RUNNING') // already runs when starting + done() + } + }) + }) - it('should resolve an ipfs path correctly', function (done) { - node.files.add(fixture, (err, res) => { - expect(err).to.not.exist() - ipnsPath.resolvePath(node, `/ipfs/${res[0].hash}`, (err, value) => { + describe('errors', function () { + if (!isNode) { + return + } + + let node + let nodeId + let ipfsd + + before(function (done) { + this.timeout(40 * 1000) + df.spawn({ + exec: IPFS, + args: [`--pass ${hat()}`], + config: { Bootstrap: [] } + }, (err, _ipfsd) => { expect(err).to.not.exist() - expect(value).to.exist() + ipfsd = _ipfsd + node = _ipfsd.api + + node.id().then((res) => { + expect(res.id).to.exist() + + nodeId = res.id + done() + }) + }) + }) + + after((done) => ipfsd.stop(done)) + + it('should error to publish if does not receive private key', function (done) { + node._ipns.publisher.publish(null, ipfsRef, (err) => { + expect(err).to.exist() + expect(err.code).to.equal('ERR_UNDEFINED_PARAMETER') + done() + }) + }) + + it('should error to publish if an invalid private key is received', function (done) { + node._ipns.publisher.publish({ bytes: 'not that valid' }, ipfsRef, (err) => { + expect(err).to.exist() + done() + }) + }) + + it('should error to publish if _updateOrCreateRecord fails', function (done) { + const stub = sinon.stub(node._ipns.publisher, '_updateOrCreateRecord').callsArgWith(4, 'error') + + node.name.publish(ipfsRef, { resolve: false }, (err, res) => { + expect(err).to.exist() + + stub.restore() + done() + }) + }) + + it('should error to publish if _putRecordToRouting receives an invalid peer id', function (done) { + node._ipns.publisher._putRecordToRouting(undefined, undefined, (err) => { + expect(err).to.exist() + done() + }) + }) + + it('should error to publish if receives an invalid datastore key', function (done) { + const stub = sinon.stub(Key, 'isKey').returns(false) + + node.name.publish(ipfsRef, { resolve: false }, (err, res) => { + expect(err).to.exist() + expect(err.code).to.equal('ERR_INVALID_DATASTORE_KEY') + + stub.restore() + done() + }) + }) + + it('should error to publish if we receive a unexpected error getting from datastore', function (done) { + const stub = sinon.stub(node._ipns.publisher._repo.datastore, 'get').callsArgWith(1, 'error-unexpected') + + node.name.publish(ipfsRef, { resolve: false }, (err, res) => { + expect(err).to.exist() + expect(err.code).to.equal('ERR_UNEXPECTED_DATASTORE_RESPONSE') + + stub.restore() done() }) }) + + it('should error to publish if we receive a unexpected error putting to datastore', function (done) { + const stub = sinon.stub(node._ipns.publisher._repo.datastore, 'put').callsArgWith(2, 'error-unexpected') + + node.name.publish(ipfsRef, { resolve: false }, (err, res) => { + expect(err).to.exist() + expect(err.code).to.equal('ERR_STORING_IN_DATASTORE') + + stub.restore() + done() + }) + }) + + it('should error to resolve if the received name is not a string', function (done) { + node._ipns.resolver.resolve(false, (err) => { + expect(err).to.exist() + expect(err.code).to.equal('ERR_INVALID_PARAMETER') + done() + }) + }) + + it('should error to resolve if receives an invalid ipns path', function (done) { + node._ipns.resolver.resolve('ipns/', (err) => { + expect(err).to.exist() + expect(err.code).to.equal('ERR_INVALID_NAME_SYNTAX') + done() + }) + }) + + it('should publish and then fail to resolve if receive error getting from datastore', function (done) { + const stub = sinon.stub(node._ipns.resolver._routing, 'get').callsArgWith(1, 'error-unexpected') + + node.name.publish(ipfsRef, { resolve: false }, (err, res) => { + expect(err).to.not.exist() + expect(res).to.exist() + + node.name.resolve(nodeId, { nocache: true }, (err, res) => { + expect(err).to.exist() + expect(err.code).to.equal('ERR_NO_RECORD_FOUND') + stub.restore() + done() + }) + }) + }) + + it('should publish and then fail to resolve if does not get any data', function (done) { + const stub = sinon.stub(node._ipns.resolver._routing, 'get').callsArgWith(1, undefined, undefined) + + node.name.publish(ipfsRef, { resolve: false }, (err, res) => { + expect(err).to.not.exist() + expect(res).to.exist() + + node.name.resolve(nodeId, { nocache: true }, (err, res) => { + expect(err).to.exist() + expect(err.code).to.equal('ERR_EMPTY_RECORD_FOUND') + stub.restore() + done() + }) + }) + }) + + it('should publish and then fail to resolve if does not receive a buffer', function (done) { + const stub = sinon.stub(node._ipns.resolver._routing, 'get').callsArgWith(1, undefined, 'data') + + node.name.publish(ipfsRef, { resolve: false }, (err, res) => { + expect(err).to.not.exist() + expect(res).to.exist() + + node.name.resolve(nodeId, { nocache: true }, (err, res) => { + expect(err).to.exist() + expect(err.code).to.equal('ERR_INVALID_RECORD_RECEIVED') + stub.restore() + done() + }) + }) + }) }) - it('should resolve an ipns path correctly', function (done) { - node.files.add(fixture, (err, res) => { - expect(err).to.not.exist() - node.name.publish(`/ipfs/${res[0].hash}`, (err, res) => { + describe('ipns.path', function () { + const path = 'test/fixtures/planets/solar-system.md' + const fixture = { + path, + content: fs.readFileSync(path) + } + + let node + let ipfsd + let nodeId + + if (!isNode) { + return + } + + before(function (done) { + this.timeout(40 * 1000) + df.spawn({ + exec: IPFS, + args: [`--pass ${hat()}`], + config: { Bootstrap: [] } + }, (err, _ipfsd) => { + expect(err).to.not.exist() + node = _ipfsd.api + ipfsd = _ipfsd + + node.id().then((res) => { + expect(res.id).to.exist() + + nodeId = res.id + done() + }) + }) + }) + + after((done) => ipfsd.stop(done)) + + it('should resolve an ipfs path correctly', function (done) { + node.files.add(fixture, (err, res) => { expect(err).to.not.exist() - ipnsPath.resolvePath(node, `/ipns/${nodeId}`, (err, value) => { + ipnsPath.resolvePath(node, `/ipfs/${res[0].hash}`, (err, value) => { expect(err).to.not.exist() expect(value).to.exist() done() }) }) }) + + it('should resolve an ipns path correctly', function (done) { + node.files.add(fixture, (err, res) => { + expect(err).to.not.exist() + node.name.publish(`/ipfs/${res[0].hash}`, (err, res) => { + expect(err).to.not.exist() + ipnsPath.resolvePath(node, `/ipns/${nodeId}`, (err, value) => { + expect(err).to.not.exist() + expect(value).to.exist() + done() + }) + }) + }) + }) }) }) diff --git a/test/core/node.js b/test/core/node.js index be56f4b53c..3ddfaef962 100644 --- a/test/core/node.js +++ b/test/core/node.js @@ -1,6 +1,7 @@ 'use strict' require('./circuit-relay') +require('./name') require('./key-exchange') require('./pin') require('./pin-set')