diff --git a/README.md b/README.md index 316fa4442..57ad20721 100644 --- a/README.md +++ b/README.md @@ -52,6 +52,7 @@ Include this badge in your readme if you make a new module that implements inter ## Install In JavaScript land: + ```js npm install interface-ipfs-core ``` @@ -68,20 +69,81 @@ In Go land: Install `interface-ipfs-core` as one of the dependencies of your project and as a test file. Then, using `mocha` (for Node.js) or a test runner with compatible API, do: -``` -var test = require('interface-ipfs-core') - -var common = { - setup: function (cb) { - cb(null, IPFSFactory) +```js +const tests = require('interface-ipfs-core') + +// Create common setup and teardown +const createCommon = () => ({ + // Do some setup common to all tests + setup (cb) { + // Must call back with an "IPFS factory", an object with a `spawnNode` method + cb(null, { + // Use ipfsd-ctl or other to spawn an IPFS node for testing + spawnNode (cb) { /* ... */ } + }) }, - teardown: function (cb) { + // Dispose of nodes created by the IPFS factory and any other teardown + teardown (cb) { cb() } -} +}) + +tests.block(createCommon) +tests.config(createCommon) +tests.dag(createCommon) +// ...etc. (see js/src/index.js) +``` + +#### Running tests by command + +```js +tests.repo.version(createCommon) +``` + +#### Skipping tests + +```js +tests.repo.gc(createCommon, { skip: true }) // pass an options object to skip these tests + +// OR, at the subsystem level + +// skips ALL the repo.gc tests +tests.repo(createCommon, { skip: ['gc'] }) +// skips ALL the object.patch.addLink tests +tests.object(createCommon, { skip: ['patch.addLink'] }) +``` + +##### Skipping specific tests + +```js +tests.repo.gc(createCommon, { skip: ['should do a thing'] }) // named test(s) to skip + +// OR, at the subsystem level + +tests.repo(createCommon, { skip: ['should do a thing'] }) +``` + +#### Running only some tests + +```js +tests.repo.gc(createCommon, { only: true }) // pass an options object to run only these tests + +// OR, at the subsystem level + +// runs only ALL the repo.gc tests +tests.repo(createCommon, { only: ['gc'] }) +// runs only ALL the object.patch.addLink tests +tests.object(createCommon, { only: ['patch.addLink'] }) +``` + +##### Running only specific tests + +```js +tests.repo.gc(createCommon, { only: ['should do a thing'] }) // only run these named test(s) + +// OR, at the subsystem level -// use all of the test suits -test.all(common) +tests.repo(createCommon, { only: ['should do a thing'] }) ``` ### Go diff --git a/js/src/bitswap.js b/js/src/bitswap.js deleted file mode 100644 index 46febe6d7..000000000 --- a/js/src/bitswap.js +++ /dev/null @@ -1,144 +0,0 @@ -/* eslint-env mocha */ -'use strict' - -const chai = require('chai') -const dirtyChai = require('dirty-chai') -const series = require('async/series') -const expect = chai.expect -const statsTests = require('./utils/stats') -const spawn = require('./utils/spawn') -chai.use(dirtyChai) -const CID = require('cids') - -module.exports = (common) => { - describe('.bitswap online', () => { - let ipfsA - let ipfsB - let withGo - let ipfsBId - const key = 'QmUBdnXXPyoDFXj3Hj39dNJ5VkN3QFRskXxcGaYFBB8CNR' - - before(function (done) { - // CI takes longer to instantiate the daemon, so we need to increase the - // timeout for the before step - this.timeout(60 * 1000) - - common.setup((err, factory) => { - expect(err).to.not.exist() - series([ - (cb) => spawn.spawnNodeWithId(factory, (err, node) => { - expect(err).to.not.exist() - ipfsA = node - withGo = node.peerId.agentVersion.startsWith('go-ipfs') - cb() - }), - (cb) => spawn.spawnNodeWithId(factory, (err, node) => { - expect(err).to.not.exist() - ipfsB = node - ipfsBId = node.peerId - ipfsB.block.get(new CID(key)) - .then(() => {}) - .catch(() => {}) - ipfsA.swarm.connect(ipfsBId.addresses[0], (err) => { - expect(err).to.not.exist() - setTimeout(cb, 350) - }) - }) - ], done) - }) - }) - - after((done) => common.teardown(done)) - - it('.stat', (done) => { - ipfsB.bitswap.stat((err, stats) => { - expect(err).to.not.exist() - statsTests.expectIsBitswap(err, stats) - done() - }) - }) - - it('.wantlist', (done) => { - ipfsB.bitswap.wantlist((err, list) => { - expect(err).to.not.exist() - expect(list.Keys).to.have.length(1) - expect(list.Keys[0]['/']).to.equal(key) - done() - }) - }) - - it('.wantlist peerid', (done) => { - ipfsA.bitswap.wantlist(ipfsBId.id, (err, list) => { - expect(err).to.not.exist() - expect(list.Keys[0]['/']).to.equal(key) - done() - }) - }) - - it('.unwant', function (done) { - if (withGo) { - this.skip() - } - ipfsB.bitswap.unwant(key, (err) => { - expect(err).to.not.exist() - ipfsB.bitswap.wantlist((err, list) => { - expect(err).to.not.exist() - expect(list.Keys).to.be.empty() - done() - }) - }) - }) - }) - - describe('.bitswap offline', () => { - let ipfs - - before(function (done) { - // CI takes longer to instantiate the daemon, so we need to increase the - // timeout for the before step - this.timeout(60 * 1000) - - common.setup((err, factory) => { - expect(err).to.not.exist() - factory.spawnNode((err, node) => { - expect(err).to.not.exist() - ipfs = node - ipfs.id((err, id) => { - expect(err).to.not.exist() - ipfs.stop((err) => { - // TODO: go-ipfs returns an error, https://github.com/ipfs/go-ipfs/issues/4078 - if (!id.agentVersion.startsWith('go-ipfs')) { - expect(err).to.not.exist() - } - done() - }) - }) - }) - }) - }) - - it('.stat gives error while offline', (done) => { - ipfs.bitswap.stat((err, stats) => { - expect(err).to.exist() - expect(stats).to.not.exist() - done() - }) - }) - - it('.wantlist gives error if offline', (done) => { - ipfs.bitswap.wantlist((err, list) => { - expect(err).to.exist() - expect(list).to.not.exist() - done() - }) - }) - - it('.unwant gives error if offline', (done) => { - const key = 'QmUBdnXXPyoDFXj3Hj39dNJ5VkN3QFRskXxcGaYFBB8CNR' - ipfs.bitswap.unwant(key, (err) => { - expect(err).to.exist() - done() - }) - }) - }) -} diff --git a/js/src/bitswap/index.js b/js/src/bitswap/index.js new file mode 100644 index 000000000..3a5f5f373 --- /dev/null +++ b/js/src/bitswap/index.js @@ -0,0 +1,10 @@ +'use strict' +const { createSuite } = require('../utils/suite') + +const tests = { + stat: require('./stat'), + wantlist: require('./wantlist'), + unwant: require('./unwant') +} + +module.exports = createSuite(tests) diff --git a/js/src/bitswap/stat.js b/js/src/bitswap/stat.js new file mode 100644 index 000000000..024612474 --- /dev/null +++ b/js/src/bitswap/stat.js @@ -0,0 +1,62 @@ +/* eslint-env mocha */ +'use strict' + +const waterfall = require('async/waterfall') +const { getDescribe, getIt, expect } = require('../utils/mocha') +const { expectIsBitswap } = require('../stats/utils') + +module.exports = (createCommon, options) => { + const describe = getDescribe(options) + const it = getIt(options) + const common = createCommon() + + describe('.bitswap.stat', () => { + let ipfs + + before(function (done) { + // CI takes longer to instantiate the daemon, so we need to increase the + // timeout for the before step + this.timeout(60 * 1000) + + common.setup((err, factory) => { + expect(err).to.not.exist() + factory.spawnNode((err, node) => { + expect(err).to.not.exist() + ipfs = node + done() + }) + }) + }) + + after((done) => common.teardown(done)) + + it('should get bitswap stats', (done) => { + ipfs.bitswap.stat((err, res) => { + expectIsBitswap(err, res) + done() + }) + }) + + it('should get bitswap stats (promised)', () => { + return ipfs.bitswap.stat().then((res) => { + expectIsBitswap(null, res) + }) + }) + + it('should not get bitswap stats when offline', function (done) { + this.timeout(60 * 1000) + + waterfall([ + (cb) => createCommon().setup(cb), + (factory, cb) => factory.spawnNode(cb), + (node, cb) => node.stop((err) => cb(err, node)) + ], (err, node) => { + expect(err).to.not.exist() + node.bitswap.wantlist((err) => { + expect(err).to.exist() + done() + }) + }) + }) + }) +} diff --git a/js/src/bitswap/unwant.js b/js/src/bitswap/unwant.js new file mode 100644 index 000000000..b16be1d8a --- /dev/null +++ b/js/src/bitswap/unwant.js @@ -0,0 +1,75 @@ +/* eslint-env mocha */ +'use strict' + +const waterfall = require('async/waterfall') +const { spawnNodesWithId } = require('../utils/spawn') +const { getDescribe, getIt, expect } = require('../utils/mocha') +const { waitForWantlistKey } = require('./utils') + +module.exports = (createCommon, options) => { + const describe = getDescribe(options) + const it = getIt(options) + const common = createCommon() + + describe('.bitswap.unwant', () => { + let ipfsA + let ipfsB + const key = 'QmUBdnXXPyoDFXj3Hj39dNJ5VkN3QFRskXxcGaYFBB8CNR' + + before(function (done) { + // CI takes longer to instantiate the daemon, so we need to increase the + // timeout for the before step + this.timeout(60 * 1000) + + common.setup((err, factory) => { + expect(err).to.not.exist() + + spawnNodesWithId(2, factory, (err, nodes) => { + expect(err).to.not.exist() + + ipfsA = nodes[0] + ipfsB = nodes[1] + + // Add key to the wantlist for ipfsB + ipfsB.block.get(key, () => {}) + + ipfsA.swarm.connect(ipfsB.peerId.addresses[0], done) + }) + }) + }) + + after((done) => common.teardown(done)) + + it('should remove a key from the wantlist', (done) => { + waitForWantlistKey(ipfsB, key, (err) => { + expect(err).to.not.exist() + + ipfsB.bitswap.unwant(key, (err) => { + expect(err).to.not.exist() + + ipfsB.bitswap.wantlist((err, list) => { + expect(err).to.not.exist() + expect(list.Keys.every(k => k['/'] !== key)).to.equal(true) + done() + }) + }) + }) + }) + + it('should not remove a key from the wantlist when offline', function (done) { + this.timeout(60 * 1000) + + waterfall([ + (cb) => createCommon().setup(cb), + (factory, cb) => factory.spawnNode(cb), + (node, cb) => node.stop((err) => cb(err, node)) + ], (err, node) => { + expect(err).to.not.exist() + node.bitswap.wantlist((err) => { + expect(err).to.exist() + done() + }) + }) + }) + }) +} diff --git a/js/src/bitswap/utils.js b/js/src/bitswap/utils.js new file mode 100644 index 000000000..5a2c9c351 --- /dev/null +++ b/js/src/bitswap/utils.js @@ -0,0 +1,29 @@ +'use strict' + +const until = require('async/until') + +function waitForWantlistKey (ipfs, key, opts, cb) { + if (typeof opts === 'function') { + cb = opts + opts = {} + } + + opts = opts || {} + opts.timeout = opts.timeout || 1000 + + let list = { Keys: [] } + let timedOut = false + + setTimeout(() => { timedOut = true }, opts.timeout) + + const test = () => timedOut ? true : list.Keys.every(k => k['/'] === key) + const iteratee = (cb) => ipfs.bitswap.wantlist(opts.peerId, cb) + + until(test, iteratee, (err) => { + if (err) return cb(err) + if (timedOut) return cb(new Error(`Timed out waiting for ${key} in wantlist`)) + cb() + }) +} + +module.exports.waitForWantlistKey = waitForWantlistKey diff --git a/js/src/bitswap/wantlist.js b/js/src/bitswap/wantlist.js new file mode 100644 index 000000000..0606b175a --- /dev/null +++ b/js/src/bitswap/wantlist.js @@ -0,0 +1,73 @@ +/* eslint-env mocha */ +'use strict' + +const waterfall = require('async/waterfall') +const { spawnNodesWithId } = require('../utils/spawn') +const { getDescribe, getIt, expect } = require('../utils/mocha') +const { waitForWantlistKey } = require('./utils') + +module.exports = (createCommon, options) => { + const describe = getDescribe(options) + const it = getIt(options) + const common = createCommon() + + describe('.bitswap.wantlist', () => { + let ipfsA + let ipfsB + const key = 'QmUBdnXXPyoDFXj3Hj39dNJ5VkN3QFRskXxcGaYFBB8CNR' + + before(function (done) { + // CI takes longer to instantiate the daemon, so we need to increase the + // timeout for the before step + this.timeout(60 * 1000) + + common.setup((err, factory) => { + expect(err).to.not.exist() + + spawnNodesWithId(2, factory, (err, nodes) => { + expect(err).to.not.exist() + + ipfsA = nodes[0] + ipfsB = nodes[1] + + // Add key to the wantlist for ipfsB + ipfsB.block.get(key, () => {}) + + ipfsA.swarm.connect(ipfsB.peerId.addresses[0], done) + }) + }) + }) + + after(function (done) { + this.timeout(30 * 1000) + common.teardown(done) + }) + + it('should get the wantlist', (done) => { + waitForWantlistKey(ipfsB, key, done) + }) + + it('should get the wantlist by peer ID for a diffreent node', (done) => { + ipfsB.id((err, info) => { + expect(err).to.not.exist() + waitForWantlistKey(ipfsA, key, { peerId: info.id }, done) + }) + }) + + it('should not get the wantlist when offline', function (done) { + this.timeout(60 * 1000) + + waterfall([ + (cb) => createCommon().setup(cb), + (factory, cb) => factory.spawnNode(cb), + (node, cb) => node.stop((err) => cb(err, node)) + ], (err, node) => { + expect(err).to.not.exist() + node.bitswap.wantlist((err) => { + expect(err).to.exist() + done() + }) + }) + }) + }) +} diff --git a/js/src/block.js b/js/src/block.js deleted file mode 100644 index 38219db5b..000000000 --- a/js/src/block.js +++ /dev/null @@ -1,161 +0,0 @@ -/* eslint-env mocha */ -/* eslint max-nested-callbacks: ["error", 8] */ - -'use strict' - -const chai = require('chai') -const dirtyChai = require('dirty-chai') -const expect = chai.expect -chai.use(dirtyChai) -const Block = require('ipfs-block') -const multihash = require('multihashes') -const CID = require('cids') -const Buffer = require('safe-buffer').Buffer - -function expectKey (block, expected, callback) { - expect(block.cid.multihash).to.eql(expected) - callback() -} - -module.exports = (common) => { - describe('.block', () => { - let ipfs - - before(function (done) { - // CI takes longer to instantiate the daemon, so we need to increase the - // timeout for the before step - this.timeout(60 * 1000) - - common.setup((err, factory) => { - expect(err).to.not.exist() - factory.spawnNode((err, node) => { - expect(err).to.not.exist() - ipfs = node - done() - }) - }) - }) - - after((done) => common.teardown(done)) - - describe('.put', () => { - it('a buffer, using defaults', (done) => { - const expectedHash = 'QmPv52ekjS75L4JmHpXVeuJ5uX2ecSfSZo88NSyxwA3rAQ' - const blob = new Buffer('blorb') - - ipfs.block.put(blob, (err, block) => { - expect(err).to.not.exist() - expect(block.data).to.be.eql(blob) - expectKey(block, multihash.fromB58String(expectedHash), done) - }) - }) - - it('a buffer, using CID', (done) => { - const expectedHash = 'QmPv52ekjS75L4JmHpXVeuJ5uX2ecSfSZo88NSyxwA3rAQ' - const cid = new CID(expectedHash) - const blob = new Buffer('blorb') - - ipfs.block.put(blob, { cid: cid }, (err, block) => { - expect(err).to.not.exist() - expect(block.data).to.be.eql(blob) - expectKey(block, multihash.fromB58String(expectedHash), done) - }) - }) - - it('a buffer, using options', (done) => { - const expectedHash = 'QmPv52ekjS75L4JmHpXVeuJ5uX2ecSfSZo88NSyxwA3rAQ' - const blob = new Buffer('blorb') - - ipfs.block.put(blob, { - format: 'dag-pb', - mhtype: 'sha2-256', - version: 0 - }, (err, block) => { - expect(err).to.not.exist() - expect(block.data).to.be.eql(blob) - expectKey(block, multihash.fromB58String(expectedHash), done) - }) - }) - - it('a Block instance', (done) => { - const expectedHash = 'QmPv52ekjS75L4JmHpXVeuJ5uX2ecSfSZo88NSyxwA3rAQ' - const cid = new CID(expectedHash) - const b = new Block(new Buffer('blorb'), cid) - - ipfs.block.put(b, (err, block) => { - expect(err).to.not.exist() - expect(block.data).to.eql(new Buffer('blorb')) - expectKey(block, multihash.fromB58String(expectedHash), done) - }) - }) - - it('error with array of blocks', (done) => { - const blob = Buffer('blorb') - - ipfs.block.put([blob, blob], (err) => { - expect(err).to.be.an.instanceof(Error) - done() - }) - }) - - // TODO it.skip('Promises support', (done) => {}) - }) - - describe('.get', () => { - it('by CID object', (done) => { - const hash = 'QmPv52ekjS75L4JmHpXVeuJ5uX2ecSfSZo88NSyxwA3rAQ' - const cid = new CID(hash) - - ipfs.block.get(cid, (err, block) => { - expect(err).to.not.exist() - expect(block.data).to.eql(new Buffer('blorb')) - expectKey(block, cid.multihash, done) - }) - }) - - it('by CID in Str', (done) => { - const hash = 'QmPv52ekjS75L4JmHpXVeuJ5uX2ecSfSZo88NSyxwA3rAQ' - - ipfs.block.get(hash, (err, block) => { - expect(err).to.not.exist() - expect(block.data).to.eql(new Buffer('blorb')) - expectKey(block, multihash.fromB58String(hash), done) - }) - }) - - it('should get an empty block', (done) => { - ipfs.block.put(Buffer.alloc(0), { - format: 'dag-pb', - mhtype: 'sha2-256', - version: 0 - }, (err, block) => { - expect(err).to.not.exist() - - ipfs.block.get(block.cid, (err, block) => { - expect(err).to.not.exist() - expect(block.data).to.eql(Buffer.alloc(0)) - done() - }) - }) - }) - - // TODO it.skip('Promises support', (done) => {}) - }) - - describe('.stat', () => { - it('by CID', (done) => { - const hash = 'QmPv52ekjS75L4JmHpXVeuJ5uX2ecSfSZo88NSyxwA3rAQ' - const cid = new CID(hash) - - ipfs.block.stat(cid, (err, stats) => { - expect(err).to.not.exist() - expect(stats).to.have.property('key') - expect(stats).to.have.property('size') - done() - }) - }) - - // TODO it.skip('Promises support', (done) => {}) - }) - }) -} diff --git a/js/src/block/get.js b/js/src/block/get.js new file mode 100644 index 000000000..49bac54fd --- /dev/null +++ b/js/src/block/get.js @@ -0,0 +1,75 @@ +/* eslint-env mocha */ +'use strict' + +const multihash = require('multihashes') +const CID = require('cids') +const auto = require('async/auto') +const { getDescribe, getIt, expect } = require('../utils/mocha') + +module.exports = (createCommon, options) => { + const describe = getDescribe(options) + const it = getIt(options) + const common = createCommon() + + describe('.block.get', function () { + const data = Buffer.from('blorb') + let ipfs, hash + + before(function (done) { + // CI takes longer to instantiate the daemon, so we need to increase the + // timeout for the before step + this.timeout(60 * 1000) + + auto({ + factory: (cb) => common.setup(cb), + ipfs: ['factory', (res, cb) => res.factory.spawnNode(cb)], + block: ['ipfs', (res, cb) => res.ipfs.block.put(data, cb)] + }, (err, res) => { + if (err) return done(err) + ipfs = res.ipfs + hash = res.block.cid.multihash + done() + }) + }) + + after((done) => common.teardown(done)) + + it('should get by CID object', (done) => { + const cid = new CID(hash) + + ipfs.block.get(cid, (err, block) => { + expect(err).to.not.exist() + expect(block.data).to.eql(Buffer.from('blorb')) + expect(block.cid.multihash).to.eql(cid.multihash) + done() + }) + }) + + it('should get by CID in string', (done) => { + ipfs.block.get(multihash.toB58String(hash), (err, block) => { + expect(err).to.not.exist() + expect(block.data).to.eql(Buffer.from('blorb')) + expect(block.cid.multihash).to.eql(hash) + done() + }) + }) + + it('should get an empty block', (done) => { + ipfs.block.put(Buffer.alloc(0), { + format: 'dag-pb', + mhtype: 'sha2-256', + version: 0 + }, (err, block) => { + expect(err).to.not.exist() + + ipfs.block.get(block.cid, (err, block) => { + expect(err).to.not.exist() + expect(block.data).to.eql(Buffer.alloc(0)) + done() + }) + }) + }) + + // TODO it.skip('Promises support', (done) => {}) + }) +} diff --git a/js/src/block/index.js b/js/src/block/index.js new file mode 100644 index 000000000..df97d4c72 --- /dev/null +++ b/js/src/block/index.js @@ -0,0 +1,10 @@ +'use strict' +const { createSuite } = require('../utils/suite') + +const tests = { + put: require('./put'), + get: require('./get'), + stat: require('./stat') +} + +module.exports = createSuite(tests) diff --git a/js/src/block/put.js b/js/src/block/put.js new file mode 100644 index 000000000..e3f5ed8ab --- /dev/null +++ b/js/src/block/put.js @@ -0,0 +1,99 @@ +/* eslint-env mocha */ +'use strict' + +const Block = require('ipfs-block') +const multihash = require('multihashes') +const CID = require('cids') +const { getDescribe, getIt, expect } = require('../utils/mocha') + +module.exports = (createCommon, options) => { + const describe = getDescribe(options) + const it = getIt(options) + const common = createCommon() + + describe('.block.put', () => { + let ipfs + + before(function (done) { + // CI takes longer to instantiate the daemon, so we need to increase the + // timeout for the before step + this.timeout(60 * 1000) + + common.setup((err, factory) => { + expect(err).to.not.exist() + factory.spawnNode((err, node) => { + expect(err).to.not.exist() + ipfs = node + done() + }) + }) + }) + + after((done) => common.teardown(done)) + + it('should put a buffer, using defaults', (done) => { + const expectedHash = 'QmPv52ekjS75L4JmHpXVeuJ5uX2ecSfSZo88NSyxwA3rAQ' + const blob = Buffer.from('blorb') + + ipfs.block.put(blob, (err, block) => { + expect(err).to.not.exist() + expect(block.data).to.be.eql(blob) + expect(block.cid.multihash).to.eql(multihash.fromB58String(expectedHash)) + done() + }) + }) + + it('should put a buffer, using CID', (done) => { + const expectedHash = 'QmPv52ekjS75L4JmHpXVeuJ5uX2ecSfSZo88NSyxwA3rAQ' + const cid = new CID(expectedHash) + const blob = Buffer.from('blorb') + + ipfs.block.put(blob, { cid: cid }, (err, block) => { + expect(err).to.not.exist() + expect(block.data).to.be.eql(blob) + expect(block.cid.multihash).to.eql(multihash.fromB58String(expectedHash)) + done() + }) + }) + + it('should put a buffer, using options', (done) => { + const expectedHash = 'QmPv52ekjS75L4JmHpXVeuJ5uX2ecSfSZo88NSyxwA3rAQ' + const blob = Buffer.from('blorb') + + ipfs.block.put(blob, { + format: 'dag-pb', + mhtype: 'sha2-256', + version: 0 + }, (err, block) => { + expect(err).to.not.exist() + expect(block.data).to.be.eql(blob) + expect(block.cid.multihash).to.eql(multihash.fromB58String(expectedHash)) + done() + }) + }) + + it('should put a Block instance', (done) => { + const expectedHash = 'QmPv52ekjS75L4JmHpXVeuJ5uX2ecSfSZo88NSyxwA3rAQ' + const cid = new CID(expectedHash) + const b = new Block(Buffer.from('blorb'), cid) + + ipfs.block.put(b, (err, block) => { + expect(err).to.not.exist() + expect(block.data).to.eql(Buffer.from('blorb')) + expect(block.cid.multihash).to.eql(multihash.fromB58String(expectedHash)) + done() + }) + }) + + it('should error with array of blocks', (done) => { + const blob = Buffer.from('blorb') + + ipfs.block.put([blob, blob], (err) => { + expect(err).to.be.an.instanceof(Error) + done() + }) + }) + + // TODO it.skip('Promises support', (done) => {}) + }) +} diff --git a/js/src/block/stat.js b/js/src/block/stat.js new file mode 100644 index 000000000..a420dc286 --- /dev/null +++ b/js/src/block/stat.js @@ -0,0 +1,49 @@ +/* eslint-env mocha */ +'use strict' + +const CID = require('cids') +const auto = require('async/auto') +const { getDescribe, getIt, expect } = require('../utils/mocha') + +module.exports = (createCommon, options) => { + const describe = getDescribe(options) + const it = getIt(options) + const common = createCommon() + + describe('.block.stat', () => { + const data = Buffer.from('blorb') + let ipfs, hash + + before(function (done) { + // CI takes longer to instantiate the daemon, so we need to increase the + // timeout for the before step + this.timeout(60 * 1000) + + auto({ + factory: (cb) => common.setup(cb), + ipfs: ['factory', (res, cb) => res.factory.spawnNode(cb)], + block: ['ipfs', (res, cb) => res.ipfs.block.put(data, cb)] + }, (err, res) => { + if (err) return done(err) + ipfs = res.ipfs + hash = res.block.cid.multihash + done() + }) + }) + + after((done) => common.teardown(done)) + + it('should stat by CID', (done) => { + const cid = new CID(hash) + + ipfs.block.stat(cid, (err, stats) => { + expect(err).to.not.exist() + expect(stats).to.have.property('key') + expect(stats).to.have.property('size') + done() + }) + }) + + // TODO it.skip('Promises support', (done) => {}) + }) +} diff --git a/js/src/bootstrap.js b/js/src/bootstrap.js deleted file mode 100644 index cb81ddbf2..000000000 --- a/js/src/bootstrap.js +++ /dev/null @@ -1,116 +0,0 @@ -/* eslint-env mocha */ -/* eslint max-nested-callbacks: ["error", 8] */ -'use strict' - -const chai = require('chai') -const dirtyChai = require('dirty-chai') -const expect = chai.expect -chai.use(dirtyChai) - -const invalidArg = 'this/Is/So/Invalid/' -const validIp4 = '/ip4/104.236.176.52/tcp/4001/ipfs/QmSoLnSGccFuZQJzRadHn95W2CrSFmZuTdDWP8HXaHca9z' - -module.exports = (common) => { - describe('.bootstrap', function () { - this.timeout(100 * 1000) - - let ipfs - let peers - - before(function (done) { - // CI takes longer to instantiate the daemon, so we need to increase the - // timeout for the before step - this.timeout(60 * 1000) - - common.setup((err, factory) => { - expect(err).to.not.exist() - factory.spawnNode((err, node) => { - expect(err).to.not.exist() - ipfs = node - done() - }) - }) - }) - - after((done) => common.teardown(done)) - - describe('.add', () => { - it('returns an error when called with an invalid arg', (done) => { - ipfs.bootstrap.add(invalidArg, (err) => { - expect(err).to.be.an.instanceof(Error) - done() - }) - }) - - it('returns a list of containing the bootstrap peer when called with a valid arg (ip4)', (done) => { - ipfs.bootstrap.add(validIp4, (err, res) => { - expect(err).to.not.exist() - expect(res).to.be.eql({ Peers: [validIp4] }) - peers = res.Peers - expect(peers).to.exist() - expect(peers.length).to.eql(1) - done() - }) - }) - - it('returns a list of bootstrap peers when called with the default option', (done) => { - ipfs.bootstrap.add(null, { default: true }, (err, res) => { - expect(err).to.not.exist() - peers = res.Peers - expect(peers).to.exist() - expect(peers.length).to.above(1) - done() - }) - }) - }) - - describe('.list', () => { - it('returns a list of peers', (done) => { - ipfs.bootstrap.list((err, res) => { - expect(err).to.not.exist() - peers = res.Peers - expect(peers).to.exist() - done() - }) - }) - }) - - describe('.rm', () => { - it('returns an error when called with an invalid arg', (done) => { - ipfs.bootstrap.rm(invalidArg, (err) => { - expect(err).to.be.an.instanceof(Error) - done() - }) - }) - - it('returns empty list because no peers removed when called without an arg or options', (done) => { - ipfs.bootstrap.rm(null, (err, res) => { - expect(err).to.not.exist() - peers = res.Peers - expect(peers).to.exist() - expect(peers.length).to.eql(0) - done() - }) - }) - - it('returns list containing the peer removed when called with a valid arg (ip4)', (done) => { - ipfs.bootstrap.rm(null, (err, res) => { - expect(err).to.not.exist() - peers = res.Peers - expect(peers).to.exist() - expect(peers.length).to.eql(0) - done() - }) - }) - - it('returns list of all peers removed when all option is passed', (done) => { - ipfs.bootstrap.rm(null, { all: true }, (err, res) => { - expect(err).to.not.exist() - peers = res.Peers - expect(peers).to.exist() - done() - }) - }) - }) - }) -} diff --git a/js/src/bootstrap/add.js b/js/src/bootstrap/add.js new file mode 100644 index 000000000..96578d24d --- /dev/null +++ b/js/src/bootstrap/add.js @@ -0,0 +1,64 @@ +/* eslint-env mocha */ +'use strict' + +const { getDescribe, getIt, expect } = require('../utils/mocha') + +const invalidArg = 'this/Is/So/Invalid/' +const validIp4 = '/ip4/104.236.176.52/tcp/4001/ipfs/QmSoLnSGccFuZQJzRadHn95W2CrSFmZuTdDWP8HXaHca9z' + +module.exports = (createCommon, options) => { + const describe = getDescribe(options) + const it = getIt(options) + const common = createCommon() + + describe('.bootstrap.add', function () { + this.timeout(100 * 1000) + + let ipfs + + before(function (done) { + // CI takes longer to instantiate the daemon, so we need to increase the + // timeout for the before step + this.timeout(60 * 1000) + + common.setup((err, factory) => { + expect(err).to.not.exist() + factory.spawnNode((err, node) => { + expect(err).to.not.exist() + ipfs = node + done() + }) + }) + }) + + after((done) => common.teardown(done)) + + it('should return an error when called with an invalid arg', (done) => { + ipfs.bootstrap.add(invalidArg, (err) => { + expect(err).to.be.an.instanceof(Error) + done() + }) + }) + + it('should return a list containing the bootstrap peer when called with a valid arg (ip4)', (done) => { + ipfs.bootstrap.add(validIp4, (err, res) => { + expect(err).to.not.exist() + expect(res).to.be.eql({ Peers: [validIp4] }) + const peers = res.Peers + expect(peers).to.exist() + expect(peers.length).to.eql(1) + done() + }) + }) + + it('should return a list of bootstrap peers when called with the default option', (done) => { + ipfs.bootstrap.add(null, { default: true }, (err, res) => { + expect(err).to.not.exist() + const peers = res.Peers + expect(peers).to.exist() + expect(peers.length).to.above(1) + done() + }) + }) + }) +} diff --git a/js/src/bootstrap/index.js b/js/src/bootstrap/index.js new file mode 100644 index 000000000..858d97747 --- /dev/null +++ b/js/src/bootstrap/index.js @@ -0,0 +1,10 @@ +'use strict' +const { createSuite } = require('../utils/suite') + +const tests = { + add: require('./add'), + list: require('./list'), + rm: require('./rm') +} + +module.exports = createSuite(tests) diff --git a/js/src/bootstrap/list.js b/js/src/bootstrap/list.js new file mode 100644 index 000000000..31bfe3f64 --- /dev/null +++ b/js/src/bootstrap/list.js @@ -0,0 +1,42 @@ +/* eslint-env mocha */ +'use strict' + +const { getDescribe, getIt, expect } = require('../utils/mocha') + +module.exports = (createCommon, options) => { + const describe = getDescribe(options) + const it = getIt(options) + const common = createCommon() + + describe('.bootstrap.list', function () { + this.timeout(100 * 1000) + + let ipfs + + before(function (done) { + // CI takes longer to instantiate the daemon, so we need to increase the + // timeout for the before step + this.timeout(60 * 1000) + + common.setup((err, factory) => { + expect(err).to.not.exist() + factory.spawnNode((err, node) => { + expect(err).to.not.exist() + ipfs = node + done() + }) + }) + }) + + after((done) => common.teardown(done)) + + it('should return a list of peers', (done) => { + ipfs.bootstrap.list((err, res) => { + expect(err).to.not.exist() + const peers = res.Peers + expect(peers).to.exist() + done() + }) + }) + }) +} diff --git a/js/src/bootstrap/rm.js b/js/src/bootstrap/rm.js new file mode 100644 index 000000000..24a77b6c6 --- /dev/null +++ b/js/src/bootstrap/rm.js @@ -0,0 +1,71 @@ +/* eslint-env mocha */ +'use strict' + +const { getDescribe, getIt, expect } = require('../utils/mocha') + +module.exports = (createCommon, options) => { + const describe = getDescribe(options) + const it = getIt(options) + const common = createCommon() + + const invalidArg = 'this/Is/So/Invalid/' + + describe('.bootstrap.rm', function () { + this.timeout(100 * 1000) + + let ipfs + + before(function (done) { + // CI takes longer to instantiate the daemon, so we need to increase the + // timeout for the before step + this.timeout(60 * 1000) + + common.setup((err, factory) => { + expect(err).to.not.exist() + factory.spawnNode((err, node) => { + expect(err).to.not.exist() + ipfs = node + done() + }) + }) + }) + + after((done) => common.teardown(done)) + + it('should return an error when called with an invalid arg', (done) => { + ipfs.bootstrap.rm(invalidArg, (err) => { + expect(err).to.be.an.instanceof(Error) + done() + }) + }) + + it('should return an empty list because no peers removed when called without an arg or options', (done) => { + ipfs.bootstrap.rm(null, (err, res) => { + expect(err).to.not.exist() + const peers = res.Peers + expect(peers).to.exist() + expect(peers.length).to.eql(0) + done() + }) + }) + + it('should return a list containing the peer removed when called with a valid arg (ip4)', (done) => { + ipfs.bootstrap.rm(null, (err, res) => { + expect(err).to.not.exist() + const peers = res.Peers + expect(peers).to.exist() + expect(peers.length).to.eql(0) + done() + }) + }) + + it('should return a list of all peers removed when all option is passed', (done) => { + ipfs.bootstrap.rm(null, { all: true }, (err, res) => { + expect(err).to.not.exist() + const peers = res.Peers + expect(peers).to.exist() + done() + }) + }) + }) +} diff --git a/js/src/config.js b/js/src/config.js deleted file mode 100644 index 1cff56c81..000000000 --- a/js/src/config.js +++ /dev/null @@ -1,168 +0,0 @@ -/* eslint-env mocha */ -/* eslint max-nested-callbacks: ["error", 8] */ - -'use strict' - -const chai = require('chai') -const dirtyChai = require('dirty-chai') -const expect = chai.expect -chai.use(dirtyChai) - -module.exports = (common) => { - describe('.config', function () { - this.timeout(30 * 1000) - let ipfs - - before(function (done) { - // CI takes longer to instantiate the daemon, so we need to increase the - // timeout for the before step - this.timeout(60 * 1000) - - common.setup((err, factory) => { - expect(err).to.not.exist() - factory.spawnNode((err, node) => { - expect(err).to.not.exist() - ipfs = node - done() - }) - }) - }) - - after((done) => common.teardown(done)) - - describe('.get', () => { - it('retrieve the whole config', (done) => { - ipfs.config.get((err, config) => { - expect(err).to.not.exist() - expect(config).to.exist() - done() - }) - }) - - it('retrieve a value through a key', (done) => { - ipfs.config.get('Identity.PeerID', (err, peerId) => { - expect(err).to.not.exist() - expect(peerId).to.exist() - done() - }) - }) - - it('retrieve a value through a nested key', (done) => { - ipfs.config.get('Addresses.Swarm', (err, swarmAddrs) => { - expect(err).to.not.exist() - expect(swarmAddrs).to.exist() - done() - }) - }) - - it('fail on non valid key', (done) => { - ipfs.config.get(1234, (err, peerId) => { - expect(err).to.exist() - done() - }) - }) - - it('fail on non exist()ent key', (done) => { - ipfs.config.get('Bananas', (err, peerId) => { - expect(err).to.exist() - done() - }) - }) - - it('Promises support', () => { - return ipfs.config.get() - .then((config) => { - expect(config).to.exist() - }) - }) - }) - - describe('.set', () => { - it('set a new key', (done) => { - ipfs.config.set('Fruit', 'banana', (err) => { - expect(err).to.not.exist() - ipfs.config.get('Fruit', (err, fruit) => { - expect(err).to.not.exist() - expect(fruit).to.equal('banana') - done() - }) - }) - }) - - it('set an already exist()ing key', (done) => { - ipfs.config.set('Fruit', 'morango', (err) => { - expect(err).to.not.exist() - ipfs.config.get('Fruit', (err, fruit) => { - expect(err).to.not.exist() - expect(fruit).to.equal('morango') - done() - }) - }) - }) - - it('set a JSON object', (done) => { - const key = 'API.HTTPHeaders.Access-Control-Allow-Origin' - const val = ['http://example.io'] - ipfs.config.set(key, val, function (err) { - expect(err).to.not.exist() - ipfs.config.get(key, function (err, result) { - expect(err).to.not.exist() - expect(result).to.deep.equal(val) - done() - }) - }) - }) - - it('fail on non valid key', (done) => { - ipfs.config.set(Buffer.from('heeey'), '', (err) => { - expect(err).to.exist() - done() - }) - }) - - it('fail on non valid value', (done) => { - ipfs.config.set('Fruit', Buffer.from('abc'), (err) => { - expect(err).to.exist() - done() - }) - }) - - it('Promises support', () => { - return ipfs.config.set('Fruit', 'banana') - .then(() => ipfs.config.get('Fruit')) - .then((fruit) => { - expect(fruit).to.equal('banana') - }) - }) - }) - - // Waiting for fix on go-ipfs - // - https://github.com/ipfs/js-ipfs-api/pull/307#discussion_r69281789 - // - https://github.com/ipfs/go-ipfs/issues/2927 - describe.skip('.replace', () => { - const config = { - Fruit: 'Bananas' - } - - it('replace the whole config', (done) => { - ipfs.config.replace(config, (err) => { - expect(err).to.not.exist() - ipfs.config.get((err, _config) => { - expect(err).to.not.exist() - expect(_config).to.deep.equal(config) - }) - }) - }) - - it('replace to empty config', (done) => { - ipfs.config.replace({}, (err) => { - expect(err).to.not.exist() - ipfs.config.get((err, _config) => { - expect(err).to.not.exist() - expect(_config).to.deep.equal(config) - }) - }) - }) - }) - }) -} diff --git a/js/src/config/get.js b/js/src/config/get.js new file mode 100644 index 000000000..b6730af3d --- /dev/null +++ b/js/src/config/get.js @@ -0,0 +1,77 @@ +/* eslint-env mocha */ +'use strict' + +const { getDescribe, getIt, expect } = require('../utils/mocha') + +module.exports = (createCommon, options) => { + const describe = getDescribe(options) + const it = getIt(options) + const common = createCommon() + + describe('.config.get', function () { + this.timeout(30 * 1000) + let ipfs + + before(function (done) { + // CI takes longer to instantiate the daemon, so we need to increase the + // timeout for the before step + this.timeout(60 * 1000) + + common.setup((err, factory) => { + expect(err).to.not.exist() + factory.spawnNode((err, node) => { + expect(err).to.not.exist() + ipfs = node + done() + }) + }) + }) + + after((done) => common.teardown(done)) + + it('should retrieve the whole config', (done) => { + ipfs.config.get((err, config) => { + expect(err).to.not.exist() + expect(config).to.exist() + done() + }) + }) + + it('should retrieve the whole config (promised)', () => { + return ipfs.config.get() + .then((config) => { + expect(config).to.exist() + }) + }) + + it('should retrieve a value through a key', (done) => { + ipfs.config.get('Identity.PeerID', (err, peerId) => { + expect(err).to.not.exist() + expect(peerId).to.exist() + done() + }) + }) + + it('should retrieve a value through a nested key', (done) => { + ipfs.config.get('Addresses.Swarm', (err, swarmAddrs) => { + expect(err).to.not.exist() + expect(swarmAddrs).to.exist() + done() + }) + }) + + it('should fail on non valid key', (done) => { + ipfs.config.get(1234, (err, peerId) => { + expect(err).to.exist() + done() + }) + }) + + it('should fail on non existent key', (done) => { + ipfs.config.get('Bananas', (err, peerId) => { + expect(err).to.exist() + done() + }) + }) + }) +} diff --git a/js/src/config/index.js b/js/src/config/index.js new file mode 100644 index 000000000..d17d34238 --- /dev/null +++ b/js/src/config/index.js @@ -0,0 +1,10 @@ +'use strict' +const { createSuite } = require('../utils/suite') + +const tests = { + get: require('./get'), + set: require('./set'), + replace: require('./replace') +} + +module.exports = createSuite(tests) diff --git a/js/src/config/replace.js b/js/src/config/replace.js new file mode 100644 index 000000000..8620ea099 --- /dev/null +++ b/js/src/config/replace.js @@ -0,0 +1,58 @@ +/* eslint-env mocha */ +'use strict' + +const { getDescribe, getIt, expect } = require('../utils/mocha') + +module.exports = (createCommon, options) => { + const describe = getDescribe(options) + const it = getIt(options) + const common = createCommon() + + describe('.config.replace', function () { + this.timeout(30 * 1000) + let ipfs + + before(function (done) { + // CI takes longer to instantiate the daemon, so we need to increase the + // timeout for the before step + this.timeout(60 * 1000) + + common.setup((err, factory) => { + expect(err).to.not.exist() + factory.spawnNode((err, node) => { + expect(err).to.not.exist() + ipfs = node + done() + }) + }) + }) + + after((done) => common.teardown(done)) + + const config = { + Fruit: 'Bananas' + } + + it('should replace the whole config', (done) => { + ipfs.config.replace(config, (err) => { + expect(err).to.not.exist() + ipfs.config.get((err, _config) => { + expect(err).to.not.exist() + expect(_config).to.deep.equal(config) + done() + }) + }) + }) + + it('should replace to empty config', (done) => { + ipfs.config.replace({}, (err) => { + expect(err).to.not.exist() + ipfs.config.get((err, _config) => { + expect(err).to.not.exist() + expect(_config).to.deep.equal({}) + done() + }) + }) + }) + }) +} diff --git a/js/src/config/set.js b/js/src/config/set.js new file mode 100644 index 000000000..6570413a9 --- /dev/null +++ b/js/src/config/set.js @@ -0,0 +1,89 @@ +/* eslint-env mocha */ +'use strict' + +const { getDescribe, getIt, expect } = require('../utils/mocha') + +module.exports = (createCommon, options) => { + const describe = getDescribe(options) + const it = getIt(options) + const common = createCommon() + + describe('.config.set', function () { + this.timeout(30 * 1000) + let ipfs + + before(function (done) { + // CI takes longer to instantiate the daemon, so we need to increase the + // timeout for the before step + this.timeout(60 * 1000) + + common.setup((err, factory) => { + expect(err).to.not.exist() + factory.spawnNode((err, node) => { + expect(err).to.not.exist() + ipfs = node + done() + }) + }) + }) + + after((done) => common.teardown(done)) + + it('should set a new key', (done) => { + ipfs.config.set('Fruit', 'banana', (err) => { + expect(err).to.not.exist() + ipfs.config.get('Fruit', (err, fruit) => { + expect(err).to.not.exist() + expect(fruit).to.equal('banana') + done() + }) + }) + }) + + it('should set a new key (promised)', () => { + return ipfs.config.set('Fruit', 'banana') + .then(() => ipfs.config.get('Fruit')) + .then((fruit) => { + expect(fruit).to.equal('banana') + }) + }) + + it('should set an already existing key', (done) => { + ipfs.config.set('Fruit', 'morango', (err) => { + expect(err).to.not.exist() + ipfs.config.get('Fruit', (err, fruit) => { + expect(err).to.not.exist() + expect(fruit).to.equal('morango') + done() + }) + }) + }) + + it('should set a JSON object', (done) => { + const key = 'API.HTTPHeaders.Access-Control-Allow-Origin' + const val = ['http://example.io'] + ipfs.config.set(key, val, function (err) { + expect(err).to.not.exist() + ipfs.config.get(key, function (err, result) { + expect(err).to.not.exist() + expect(result).to.deep.equal(val) + done() + }) + }) + }) + + it('should fail on non valid key', (done) => { + ipfs.config.set(Buffer.from('heeey'), '', (err) => { + expect(err).to.exist() + done() + }) + }) + + it('should fail on non valid value', (done) => { + ipfs.config.set('Fruit', Buffer.from('abc'), (err) => { + expect(err).to.exist() + done() + }) + }) + }) +} diff --git a/js/src/dag.js b/js/src/dag.js deleted file mode 100644 index d337bd045..000000000 --- a/js/src/dag.js +++ /dev/null @@ -1,450 +0,0 @@ -/* eslint-env mocha */ -/* eslint max-nested-callbacks: ["error", 8] */ - -'use strict' - -const chai = require('chai') -const dirtyChai = require('dirty-chai') -const expect = chai.expect -chai.use(dirtyChai) -const series = require('async/series') -const pull = require('pull-stream') -const dagPB = require('ipld-dag-pb') -const DAGNode = dagPB.DAGNode -const dagCBOR = require('ipld-dag-cbor') -const CID = require('cids') -const { spawnNodeWithId } = require('./utils/spawn') - -module.exports = (common) => { - describe('.dag', () => { - let ipfs - let withGo - - before(function (done) { - // CI takes longer to instantiate the daemon, so we need to increase the - // timeout for the before step - this.timeout(60 * 1000) - - common.setup((err, factory) => { - expect(err).to.not.exist() - - spawnNodeWithId(factory, (err, node) => { - expect(err).to.not.exist() - ipfs = node - withGo = node.peerId.agentVersion.startsWith('go-ipfs') - done() - }) - }) - }) - - after((done) => common.teardown(done)) - - let pbNode - let cborNode - - before((done) => { - const someData = Buffer.from('some data') - - pbNode = DAGNode.create(someData, (err, node) => { - expect(err).to.not.exist() - pbNode = node - done() - }) - - cborNode = { - data: someData - } - }) - - describe('.put', () => { - it('dag-pb with default hash func (sha2-256)', (done) => { - ipfs.dag.put(pbNode, { - format: 'dag-pb', - hashAlg: 'sha2-256' - }, done) - }) - - it('dag-pb with custom hash func (sha3-512)', (done) => { - ipfs.dag.put(pbNode, { - format: 'dag-pb', - hashAlg: 'sha3-512' - }, done) - }) - - // This works because dag-cbor will just treat pbNode as a regular object - it.skip('dag-pb node with wrong multicodec', (done) => { - ipfs.dag.put(pbNode, 'dag-cbor', 'sha3-512', (err) => { - expect(err).to.exist() - done() - }) - }) - - it('dag-cbor with default hash func (sha2-256)', (done) => { - ipfs.dag.put(cborNode, { - format: 'dag-cbor', - hashAlg: 'sha2-256' - }, done) - }) - - it('dag-cbor with custom hash func (sha3-512)', (done) => { - ipfs.dag.put(cborNode, { - format: 'dag-cbor', - hashAlg: 'sha3-512' - }, done) - }) - - it('dag-cbor node with wrong multicodec', function (done) { - // This works in go-ipfs because dag-pb will serialize any object. If - // the object has neither a `data` nor `links` field it's serialized - // as an empty object - if (withGo) { - this.skip() - } - ipfs.dag.put(cborNode, { - format: 'dag-pb', - hashAlg: 'sha3-512' - }, (err) => { - expect(err).to.exist() - done() - }) - }) - - it('returns the cid', (done) => { - ipfs.dag.put(cborNode, { - format: 'dag-cbor', - hashAlg: 'sha2-256' - }, (err, cid) => { - expect(err).to.not.exist() - expect(cid).to.exist() - expect(CID.isCID(cid)).to.equal(true) - dagCBOR.util.cid(cborNode, (err, _cid) => { - expect(err).to.not.exist() - expect(cid.buffer).to.eql(_cid.buffer) - done() - }) - }) - }) - - it.skip('pass the cid instead of format and hashAlg', (done) => {}) - - // TODO it.skip('Promises support', (done) => {}) - }) - - describe('.get', () => { - let pbNode - let cborNode - - let nodePb - let nodeCbor - let cidPb - let cidCbor - - before((done) => { - series([ - (cb) => { - const someData = Buffer.from('some other data') - - pbNode = DAGNode.create(someData, (err, node) => { - expect(err).to.not.exist() - pbNode = node - cb() - }) - - cborNode = { - data: someData - } - }, - (cb) => { - dagPB.DAGNode.create(Buffer.from('I am inside a Protobuf'), (err, node) => { - expect(err).to.not.exist() - nodePb = node - cb() - }) - }, - (cb) => { - dagPB.util.cid(nodePb, (err, cid) => { - expect(err).to.not.exist() - cidPb = cid - cb() - }) - }, - (cb) => { - nodeCbor = { - someData: 'I am inside a Cbor object', - pb: { '/': cidPb.toBaseEncodedString() } - } - - dagCBOR.util.cid(nodeCbor, (err, cid) => { - expect(err).to.not.exist() - cidCbor = cid - cb() - }) - } - ], store) - - function store () { - pull( - pull.values([ - { node: nodePb, multicodec: 'dag-pb', hashAlg: 'sha2-256' }, - { node: nodeCbor, multicodec: 'dag-cbor', hashAlg: 'sha2-256' } - ]), - pull.asyncMap((el, cb) => { - ipfs.dag.put(el.node, { - format: el.multicodec, - hashAlg: el.hashAlg - }, cb) - }), - pull.onEnd(done) - ) - } - }) - - it('dag-pb node', (done) => { - ipfs.dag.put(pbNode, { - format: 'dag-pb', - hashAlg: 'sha2-256' - }, (err, cid) => { - expect(err).to.not.exist() - ipfs.dag.get(cid, (err, result) => { - expect(err).to.not.exist() - const node = result.value - expect(pbNode.toJSON()).to.eql(node.toJSON()) - done() - }) - }) - }) - - it('dag-cbor node', (done) => { - ipfs.dag.put(cborNode, { - format: 'dag-cbor', - hashAlg: 'sha2-256' - }, (err, cid) => { - expect(err).to.not.exist() - ipfs.dag.get(cid, (err, result) => { - expect(err).to.not.exist() - - const node = result.value - expect(cborNode).to.eql(node) - done() - }) - }) - }) - - describe('with path', () => { - it('dag-pb get the node', (done) => { - ipfs.dag.get(cidPb, '/', (err, result) => { - expect(err).to.not.exist() - - const node = result.value - - dagPB.util.cid(node, (err, cid) => { - expect(err).to.not.exist() - expect(cid).to.eql(cidPb) - done() - }) - }) - }) - - it('dag-pb local scope', function (done) { - // TODO vmx 2018-02-22: Currently not supported in go-ipfs, it might - // be possible once https://github.com/ipfs/go-ipfs/issues/4728 is - // done - if (withGo) { - this.skip() - } - ipfs.dag.get(cidPb, 'Data', (err, result) => { - expect(err).to.not.exist() - expect(result.value).to.eql(Buffer.from('I am inside a Protobuf')) - done() - }) - }) - - it.skip('dag-pb one level', (done) => {}) - it.skip('dag-pb two levels', (done) => {}) - - it('dag-cbor get the node', (done) => { - ipfs.dag.get(cidCbor, '/', (err, result) => { - expect(err).to.not.exist() - - const node = result.value - - dagCBOR.util.cid(node, (err, cid) => { - expect(err).to.not.exist() - expect(cid).to.eql(cidCbor) - done() - }) - }) - }) - - it('dag-cbor local scope', (done) => { - ipfs.dag.get(cidCbor, 'someData', (err, result) => { - expect(err).to.not.exist() - expect(result.value).to.eql('I am inside a Cbor object') - done() - }) - }) - - it.skip('dag-cbor one level', (done) => {}) - it.skip('dag-cbor two levels', (done) => {}) - it.skip('from dag-pb to dag-cbor', (done) => {}) - - it('from dag-cbor to dag-pb', function (done) { - // TODO vmx 2018-02-22: Currently not supported in go-ipfs, it might - // be possible once https://github.com/ipfs/go-ipfs/issues/4728 is - // done - if (withGo) { - this.skip() - } - ipfs.dag.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('CID String', (done) => { - const cidCborStr = cidCbor.toBaseEncodedString() - - ipfs.dag.get(cidCborStr, (err, result) => { - expect(err).to.not.exist() - - const node = result.value - - dagCBOR.util.cid(node, (err, cid) => { - expect(err).to.not.exist() - expect(cid).to.eql(cidCbor) - done() - }) - }) - }) - - it('CID String + path', function (done) { - // TODO vmx 2018-02-22: Currently not supported in go-ipfs, it might - // be possible once https://github.com/ipfs/go-ipfs/issues/4728 is - // done - if (withGo) { - this.skip() - } - const cidCborStr = cidCbor.toBaseEncodedString() - - ipfs.dag.get(cidCborStr + '/pb/Data', (err, result) => { - expect(err).to.not.exist() - expect(result.value).to.eql(Buffer.from('I am inside a Protobuf')) - done() - }) - }) - }) - }) - - describe('.tree', function () { - let nodePb - let nodeCbor - let cidPb - let cidCbor - - before(function (done) { - // TODO vmx 2018-02-22: Currently the tree API is not exposed in go-ipfs - if (withGo) { - this.skip() - } - series([ - (cb) => { - dagPB.DAGNode.create(Buffer.from('I am inside a Protobuf'), (err, node) => { - expect(err).to.not.exist() - nodePb = node - cb() - }) - }, - (cb) => { - dagPB.util.cid(nodePb, (err, cid) => { - expect(err).to.not.exist() - cidPb = cid - cb() - }) - }, - (cb) => { - nodeCbor = { - someData: 'I am inside a Cbor object', - pb: { '/': cidPb.toBaseEncodedString() } - } - - dagCBOR.util.cid(nodeCbor, (err, cid) => { - expect(err).to.not.exist() - cidCbor = cid - cb() - }) - } - ], store) - - function store () { - pull( - pull.values([ - { node: nodePb, multicodec: 'dag-pb', hashAlg: 'sha2-256' }, - { node: nodeCbor, multicodec: 'dag-cbor', hashAlg: 'sha2-256' } - ]), - pull.asyncMap((el, cb) => { - ipfs.dag.put(el.node, { - format: el.multicodec, - hashAlg: el.hashAlg - }, cb) - }), - pull.onEnd(done) - ) - } - }) - - it('.tree with CID', (done) => { - ipfs.dag.tree(cidCbor, (err, paths) => { - expect(err).to.not.exist() - expect(paths).to.eql([ - 'pb', - 'someData' - ]) - done() - }) - }) - - it('.tree with CID and path', (done) => { - ipfs.dag.tree(cidCbor, 'someData', (err, paths) => { - expect(err).to.not.exist() - expect(paths).to.eql([]) - done() - }) - }) - - it('.tree with CID and path as String', (done) => { - const cidCborStr = cidCbor.toBaseEncodedString() - - ipfs.dag.tree(cidCborStr + '/someData', (err, paths) => { - expect(err).to.not.exist() - expect(paths).to.eql([]) - done() - }) - }) - - it('.tree with CID recursive (accross different formats)', (done) => { - ipfs.dag.tree(cidCbor, { recursive: true }, (err, paths) => { - expect(err).to.not.exist() - expect(paths).to.eql([ - 'pb', - 'someData', - 'pb/Links', - 'pb/Data' - ]) - done() - }) - }) - - it('.tree with CID and path recursive', (done) => { - ipfs.dag.tree(cidCbor, 'pb', { recursive: true }, (err, paths) => { - expect(err).to.not.exist() - expect(paths).to.eql([ - 'Links', - 'Data' - ]) - done() - }) - }) - }) - }) -} diff --git a/js/src/dag/get.js b/js/src/dag/get.js new file mode 100644 index 000000000..399c64107 --- /dev/null +++ b/js/src/dag/get.js @@ -0,0 +1,215 @@ +/* eslint-env mocha */ +'use strict' + +const { series, eachSeries } = require('async') +const dagPB = require('ipld-dag-pb') +const DAGNode = dagPB.DAGNode +const dagCBOR = require('ipld-dag-cbor') +const { spawnNodeWithId } = require('../utils/spawn') +const { getDescribe, getIt, expect } = require('../utils/mocha') + +module.exports = (createCommon, options) => { + const describe = getDescribe(options) + const it = getIt(options) + const common = createCommon() + + describe('.dag.get', () => { + let ipfs + + before(function (done) { + // CI takes longer to instantiate the daemon, so we need to increase the + // timeout for the before step + this.timeout(60 * 1000) + + common.setup((err, factory) => { + expect(err).to.not.exist() + + spawnNodeWithId(factory, (err, node) => { + expect(err).to.not.exist() + ipfs = node + done() + }) + }) + }) + + after((done) => common.teardown(done)) + + let pbNode + let cborNode + let nodePb + let nodeCbor + let cidPb + let cidCbor + + before((done) => { + series([ + (cb) => { + const someData = Buffer.from('some other data') + + pbNode = DAGNode.create(someData, (err, node) => { + expect(err).to.not.exist() + pbNode = node + cb() + }) + + cborNode = { + data: someData + } + }, + (cb) => { + dagPB.DAGNode.create(Buffer.from('I am inside a Protobuf'), (err, node) => { + expect(err).to.not.exist() + nodePb = node + cb() + }) + }, + (cb) => { + dagPB.util.cid(nodePb, (err, cid) => { + expect(err).to.not.exist() + cidPb = cid + cb() + }) + }, + (cb) => { + nodeCbor = { + someData: 'I am inside a Cbor object', + pb: { '/': cidPb.toBaseEncodedString() } + } + + dagCBOR.util.cid(nodeCbor, (err, cid) => { + expect(err).to.not.exist() + cidCbor = cid + cb() + }) + }, + (cb) => { + eachSeries([ + { node: nodePb, multicodec: 'dag-pb', hashAlg: 'sha2-256' }, + { node: nodeCbor, multicodec: 'dag-cbor', hashAlg: 'sha2-256' } + ], (el, cb) => { + ipfs.dag.put(el.node, { + format: el.multicodec, + hashAlg: el.hashAlg + }, cb) + }, cb) + } + ], done) + }) + + it('should get a dag-pb node', (done) => { + ipfs.dag.put(pbNode, { + format: 'dag-pb', + hashAlg: 'sha2-256' + }, (err, cid) => { + expect(err).to.not.exist() + ipfs.dag.get(cid, (err, result) => { + expect(err).to.not.exist() + const node = result.value + expect(pbNode.toJSON()).to.eql(node.toJSON()) + done() + }) + }) + }) + + it('should get a dag-cbor node', (done) => { + ipfs.dag.put(cborNode, { + format: 'dag-cbor', + hashAlg: 'sha2-256' + }, (err, cid) => { + expect(err).to.not.exist() + ipfs.dag.get(cid, (err, result) => { + expect(err).to.not.exist() + + const node = result.value + expect(cborNode).to.eql(node) + done() + }) + }) + }) + + it('should get a dag-pb node with path', (done) => { + ipfs.dag.get(cidPb, '/', (err, result) => { + expect(err).to.not.exist() + + const node = result.value + + dagPB.util.cid(node, (err, cid) => { + expect(err).to.not.exist() + expect(cid).to.eql(cidPb) + done() + }) + }) + }) + + it('should get a dag-pb node local value', function (done) { + ipfs.dag.get(cidPb, 'Data', (err, result) => { + expect(err).to.not.exist() + expect(result.value).to.eql(Buffer.from('I am inside a Protobuf')) + done() + }) + }) + + it.skip('should get a dag-pb node value one level deep', (done) => {}) + it.skip('should get a dag-pb node value two levels deep', (done) => {}) + + it('should get a dag-cbor node with path', (done) => { + ipfs.dag.get(cidCbor, '/', (err, result) => { + expect(err).to.not.exist() + + const node = result.value + + dagCBOR.util.cid(node, (err, cid) => { + expect(err).to.not.exist() + expect(cid).to.eql(cidCbor) + done() + }) + }) + }) + + it('should get a dag-cbor node local value', (done) => { + ipfs.dag.get(cidCbor, 'someData', (err, result) => { + expect(err).to.not.exist() + expect(result.value).to.eql('I am inside a Cbor object') + done() + }) + }) + + it.skip('should get dag-cbor node value one level deep', (done) => {}) + it.skip('should get dag-cbor node value two levels deep', (done) => {}) + it.skip('should get dag-cbor value via dag-pb node', (done) => {}) + + it('should get dag-pb value via dag-cbor node', function (done) { + ipfs.dag.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('should get by CID string', (done) => { + const cidCborStr = cidCbor.toBaseEncodedString() + + ipfs.dag.get(cidCborStr, (err, result) => { + expect(err).to.not.exist() + + const node = result.value + + dagCBOR.util.cid(node, (err, cid) => { + expect(err).to.not.exist() + expect(cid).to.eql(cidCbor) + done() + }) + }) + }) + + it('should get by CID string + path', function (done) { + const cidCborStr = cidCbor.toBaseEncodedString() + + ipfs.dag.get(cidCborStr + '/pb/Data', (err, result) => { + expect(err).to.not.exist() + expect(result.value).to.eql(Buffer.from('I am inside a Protobuf')) + done() + }) + }) + }) +} diff --git a/js/src/dag/index.js b/js/src/dag/index.js new file mode 100644 index 000000000..217ee5370 --- /dev/null +++ b/js/src/dag/index.js @@ -0,0 +1,10 @@ +'use strict' +const { createSuite } = require('../utils/suite') + +const tests = { + get: require('./get'), + put: require('./put'), + tree: require('./tree') +} + +module.exports = createSuite(tests) diff --git a/js/src/dag/put.js b/js/src/dag/put.js new file mode 100644 index 000000000..dadcf6afa --- /dev/null +++ b/js/src/dag/put.js @@ -0,0 +1,120 @@ +/* eslint-env mocha */ +'use strict' + +const dagPB = require('ipld-dag-pb') +const DAGNode = dagPB.DAGNode +const dagCBOR = require('ipld-dag-cbor') +const CID = require('cids') +const { spawnNodeWithId } = require('../utils/spawn') +const { getDescribe, getIt, expect } = require('../utils/mocha') + +module.exports = (createCommon, options) => { + const describe = getDescribe(options) + const it = getIt(options) + const common = createCommon() + + describe('.dag.put', () => { + let ipfs + + before(function (done) { + // CI takes longer to instantiate the daemon, so we need to increase the + // timeout for the before step + this.timeout(60 * 1000) + + common.setup((err, factory) => { + expect(err).to.not.exist() + + spawnNodeWithId(factory, (err, node) => { + expect(err).to.not.exist() + ipfs = node + done() + }) + }) + }) + + after((done) => common.teardown(done)) + + let pbNode + let cborNode + + before((done) => { + const someData = Buffer.from('some data') + + pbNode = DAGNode.create(someData, (err, node) => { + expect(err).to.not.exist() + pbNode = node + done() + }) + + cborNode = { + data: someData + } + }) + + it('should put dag-pb with default hash func (sha2-256)', (done) => { + ipfs.dag.put(pbNode, { + format: 'dag-pb', + hashAlg: 'sha2-256' + }, done) + }) + + it('should put dag-pb with custom hash func (sha3-512)', (done) => { + ipfs.dag.put(pbNode, { + format: 'dag-pb', + hashAlg: 'sha3-512' + }, done) + }) + + // This works because dag-cbor will just treat pbNode as a regular object + it.skip('should not put dag-pb node with wrong multicodec', (done) => { + ipfs.dag.put(pbNode, 'dag-cbor', 'sha3-512', (err) => { + expect(err).to.exist() + done() + }) + }) + + it('should put dag-cbor with default hash func (sha2-256)', (done) => { + ipfs.dag.put(cborNode, { + format: 'dag-cbor', + hashAlg: 'sha2-256' + }, done) + }) + + it('should put dag-cbor with custom hash func (sha3-512)', (done) => { + ipfs.dag.put(cborNode, { + format: 'dag-cbor', + hashAlg: 'sha3-512' + }, done) + }) + + it('should not put dag-cbor node with wrong multicodec', (done) => { + ipfs.dag.put(cborNode, { + format: 'dag-pb', + hashAlg: 'sha3-512' + }, (err) => { + expect(err).to.exist() + done() + }) + }) + + it('should return the cid', (done) => { + ipfs.dag.put(cborNode, { + format: 'dag-cbor', + hashAlg: 'sha2-256' + }, (err, cid) => { + expect(err).to.not.exist() + expect(cid).to.exist() + expect(CID.isCID(cid)).to.equal(true) + dagCBOR.util.cid(cborNode, (err, _cid) => { + expect(err).to.not.exist() + expect(cid.buffer).to.eql(_cid.buffer) + done() + }) + }) + }) + + it.skip('should put by passing the cid instead of format and hashAlg', (done) => {}) + + // TODO it.skip('Promises support', (done) => {}) + }) +} diff --git a/js/src/dag/tree.js b/js/src/dag/tree.js new file mode 100644 index 000000000..efbf4a92a --- /dev/null +++ b/js/src/dag/tree.js @@ -0,0 +1,137 @@ +/* eslint-env mocha */ +'use strict' + +const series = require('async/series') +const eachSeries = require('async/eachSeries') +const dagPB = require('ipld-dag-pb') +const dagCBOR = require('ipld-dag-cbor') +const { spawnNodeWithId } = require('../utils/spawn') +const { getDescribe, getIt, expect } = require('../utils/mocha') + +module.exports = (createCommon, options) => { + const describe = getDescribe(options) + const it = getIt(options) + const common = createCommon() + + describe('.dag.tree', () => { + let ipfs + + before(function (done) { + // CI takes longer to instantiate the daemon, so we need to increase the + // timeout for the before step + this.timeout(60 * 1000) + + common.setup((err, factory) => { + expect(err).to.not.exist() + + spawnNodeWithId(factory, (err, node) => { + expect(err).to.not.exist() + ipfs = node + done() + }) + }) + }) + + after((done) => common.teardown(done)) + + let nodePb + let nodeCbor + let cidPb + let cidCbor + + before(function (done) { + series([ + (cb) => { + dagPB.DAGNode.create(Buffer.from('I am inside a Protobuf'), (err, node) => { + expect(err).to.not.exist() + nodePb = node + cb() + }) + }, + (cb) => { + dagPB.util.cid(nodePb, (err, cid) => { + expect(err).to.not.exist() + cidPb = cid + cb() + }) + }, + (cb) => { + nodeCbor = { + someData: 'I am inside a Cbor object', + pb: { '/': cidPb.toBaseEncodedString() } + } + + dagCBOR.util.cid(nodeCbor, (err, cid) => { + expect(err).to.not.exist() + cidCbor = cid + cb() + }) + }, + (cb) => { + eachSeries([ + { node: nodePb, multicodec: 'dag-pb', hashAlg: 'sha2-256' }, + { node: nodeCbor, multicodec: 'dag-cbor', hashAlg: 'sha2-256' } + ], (el, cb) => { + ipfs.dag.put(el.node, { + format: el.multicodec, + hashAlg: el.hashAlg + }, cb) + }, cb) + } + ], done) + }) + + it('should get tree with CID', (done) => { + ipfs.dag.tree(cidCbor, (err, paths) => { + expect(err).to.not.exist() + expect(paths).to.eql([ + 'pb', + 'someData' + ]) + done() + }) + }) + + it('should get tree with CID and path', (done) => { + ipfs.dag.tree(cidCbor, 'someData', (err, paths) => { + expect(err).to.not.exist() + expect(paths).to.eql([]) + done() + }) + }) + + it('should get tree with CID and path as String', (done) => { + const cidCborStr = cidCbor.toBaseEncodedString() + + ipfs.dag.tree(cidCborStr + '/someData', (err, paths) => { + expect(err).to.not.exist() + expect(paths).to.eql([]) + done() + }) + }) + + it('should get tree with CID recursive (accross different formats)', (done) => { + ipfs.dag.tree(cidCbor, { recursive: true }, (err, paths) => { + expect(err).to.not.exist() + expect(paths).to.eql([ + 'pb', + 'someData', + 'pb/Links', + 'pb/Data' + ]) + done() + }) + }) + + it('should get tree with CID and path recursive', (done) => { + ipfs.dag.tree(cidCbor, 'pb', { recursive: true }, (err, paths) => { + expect(err).to.not.exist() + expect(paths).to.eql([ + 'Links', + 'Data' + ]) + done() + }) + }) + }) +} diff --git a/js/src/dht.js b/js/src/dht.js deleted file mode 100644 index 6771cf7aa..000000000 --- a/js/src/dht.js +++ /dev/null @@ -1,234 +0,0 @@ -/* eslint-env mocha */ -'use strict' - -const chai = require('chai') -const dirtyChai = require('dirty-chai') -const expect = chai.expect -chai.use(dirtyChai) -const waterfall = require('async/waterfall') -const parallel = require('async/parallel') -const CID = require('cids') -const { spawnNodesWithId } = require('./utils/spawn') - -module.exports = (common) => { - describe('.dht', function () { - this.timeout(80 * 1000) - - let withGo - let nodeA - let nodeB - let nodeC - let nodeD - let nodeE - - before(function (done) { - // CI takes longer to instantiate the daemon, so we need to increase the - // timeout for the before step - this.timeout(60 * 1000) - - common.setup((err, factory) => { - expect(err).to.not.exist() - - spawnNodesWithId(5, factory, (err, nodes) => { - expect(err).to.not.exist() - - nodeA = nodes[0] - nodeB = nodes[1] - nodeC = nodes[2] - nodeD = nodes[3] - nodeE = nodes[4] - - parallel([ - (cb) => nodeA.swarm.connect(nodeB.peerId.addresses[0], cb), - (cb) => nodeB.swarm.connect(nodeC.peerId.addresses[0], cb), - (cb) => nodeC.swarm.connect(nodeA.peerId.addresses[0], cb), - (cb) => nodeD.swarm.connect(nodeA.peerId.addresses[0], cb), - (cb) => nodeE.swarm.connect(nodeA.peerId.addresses[0], cb), - (cb) => nodeD.swarm.connect(nodeB.peerId.addresses[0], cb), - (cb) => nodeE.swarm.connect(nodeB.peerId.addresses[0], cb), - (cb) => nodeD.swarm.connect(nodeC.peerId.addresses[0], cb), - (cb) => nodeE.swarm.connect(nodeC.peerId.addresses[0], cb), - (cb) => nodeD.swarm.connect(nodeE.peerId.addresses[0], cb), - (cb) => nodeA.id((err, id) => { - expect(err).to.not.exist() - withGo = id.agentVersion.startsWith('go-ipfs') - cb() - }) - ], done) - }) - }) - }) - - after((done) => common.teardown(done)) - - describe('.get and .put', () => { - it('errors when getting a non-existent key from the DHT', (done) => { - nodeA.dht.get('non-existing', { timeout: '100ms' }, (err, value) => { - expect(err).to.be.an.instanceof(Error) - done() - }) - }) - - it('fetches value after it was put on another node', function (done) { - this.timeout(80 * 1000) - - if (withGo) { - // go-ipfs errors with Error: key was not found (type 6) - // https://github.com/ipfs/go-ipfs/issues/3862 - this.skip() - } - - // TODO - this test needs to keep tryingl instead of the setTimeout - waterfall([ - (cb) => nodeB.object.new('unixfs-dir', cb), - (dagNode, cb) => setTimeout(() => cb(null, dagNode), 20000), - (dagNode, cb) => { - const multihash = dagNode.toJSON().multihash - - nodeA.dht.get(multihash, cb) - }, - (result, cb) => { - expect(result).to.eql('') - cb() - } - ], done) - }) - }) - - describe('.findpeer', () => { - it('finds other peers', (done) => { - nodeA.dht.findpeer(nodeC.peerId.id, (err, peer) => { - expect(err).to.not.exist() - // TODO upgrade the answer, format is weird - expect(peer[0].Responses[0].ID).to.be.equal(nodeC.peerId.id) - done() - }) - }) - - it('fails to find other peer, if peer does not exist', function (done) { - if (withGo) { - // TODO checking what is exactly go-ipfs returning - // https://github.com/ipfs/go-ipfs/issues/3862#issuecomment-294168090 - this.skip() - } - - nodeA.dht.findpeer('Qmd7qZS4T7xXtsNFdRoK1trfMs5zU94EpokQ9WFtxdPxsZ', (err, peer) => { - expect(err).to.not.exist() - expect(peer).to.be.equal(null) - done() - }) - }) - }) - - describe('.provide', () => { - it('regular', (done) => { - nodeC.files.add(Buffer.from('test'), (err, res) => { - if (err) return done(err) - - nodeC.dht.provide(new CID(res[0].hash), (err) => { - expect(err).to.not.exist() - done() - }) - }) - }) - - it('should not provide if block not found locally', (done) => { - const cid = new CID('Qmd7qZS4T7xXtsNFdRoK1trfMs5zU94EpokQ9WFtxdPxsZ') - - nodeC.dht.provide(cid, (err) => { - expect(err).to.exist() - expect(err.message).to.include('not found locally') - done() - }) - }) - - it('allows multiple CIDs to be passed', (done) => { - nodeC.files.add([Buffer.from('t0'), Buffer.from('t1')], (err, res) => { - if (err) return done(err) - - nodeC.dht.provide([ - new CID(res[0].hash), - new CID(res[1].hash) - ], (err) => { - expect(err).to.not.exist() - done() - }) - }) - }) - - it('should provide a CIDv1', (done) => { - nodeC.files.add(Buffer.from('test'), { 'cid-version': 1 }, (err, res) => { - if (err) return done(err) - - const cid = new CID(res[0].hash) - - nodeC.dht.provide(cid, (err) => { - expect(err).to.not.exist() - done() - }) - }) - }) - - it('errors on non CID arg', (done) => { - nodeC.dht.provide({}, (err) => { - expect(err).to.exist() - done() - }) - }) - - it('errors on array containing non CID arg', (done) => { - nodeC.dht.provide([{}], (err) => { - expect(err).to.exist() - done() - }) - }) - - it.skip('recursive', () => {}) - }) - - describe('findprovs', () => { - it('provide from one node and find it through another node', function (done) { - if (withGo) { - // TODO go-ipfs endpoint doesn't conform with the others - // https://github.com/ipfs/go-ipfs/issues/5047 - this.skip() - } - - waterfall([ - (cb) => nodeE.object.new('unixfs-dir', cb), - (dagNode, cb) => { - const cidV0 = new CID(dagNode.toJSON().multihash) - nodeE.dht.provide(cidV0, (err) => cb(err, cidV0)) - }, - (cidV0, cb) => nodeC.dht.findprovs(cidV0, cb), - (provs, cb) => { - expect(provs.map((p) => p.toB58String())) - .to.eql([nodeE.peerId.id]) - cb() - } - ], done) - }) - }) - - describe('.query', () => { - it('returns the other node in the query', function (done) { - const timeout = 150 * 1000 - this.timeout(timeout) - - // This test is meh. DHT works best with >= 20 nodes. Therefore a - // failure might happen, but we don't want to report it as such. - // Hence skip the test before the timeout is reached - const timeoutId = setTimeout(function () { - this.skip() - }.bind(this), timeout - 1000) - - nodeA.dht.query(nodeC.peerId.id, (err, peers) => { - clearTimeout(timeoutId) - expect(err).to.not.exist() - expect(peers.map((p) => p.ID)).to.include(nodeC.peerId.id) - done() - }) - }) - }) - }) -} diff --git a/js/src/dht/findpeer.js b/js/src/dht/findpeer.js new file mode 100644 index 000000000..daae871ac --- /dev/null +++ b/js/src/dht/findpeer.js @@ -0,0 +1,56 @@ +/* eslint-env mocha */ +'use strict' + +const { spawnNodesWithId } = require('../utils/spawn') +const { getDescribe, getIt, expect } = require('../utils/mocha') + +module.exports = (createCommon, options) => { + const describe = getDescribe(options) + const it = getIt(options) + const common = createCommon() + + describe('.dht.findpeer', function () { + this.timeout(80 * 1000) + + let nodeA + let nodeB + + before(function (done) { + // CI takes longer to instantiate the daemon, so we need to increase the + // timeout for the before step + this.timeout(60 * 1000) + + common.setup((err, factory) => { + expect(err).to.not.exist() + + spawnNodesWithId(2, factory, (err, nodes) => { + expect(err).to.not.exist() + + nodeA = nodes[0] + nodeB = nodes[1] + + nodeB.swarm.connect(nodeA.peerId.addresses[0], done) + }) + }) + }) + + after((done) => common.teardown(done)) + + it('should find other peers', (done) => { + nodeA.dht.findpeer(nodeB.peerId.id, (err, peer) => { + expect(err).to.not.exist() + // TODO upgrade the answer, format is weird + expect(peer[0].Responses[0].ID).to.be.equal(nodeB.peerId.id) + done() + }) + }) + + it('should fail to find other peer if peer does not exist', (done) => { + nodeA.dht.findpeer('Qmd7qZS4T7xXtsNFdRoK1trfMs5zU94EpokQ9WFtxdPxsZ', (err, peer) => { + expect(err).to.not.exist() + expect(peer).to.not.exist() + done() + }) + }) + }) +} diff --git a/js/src/dht/findprovs.js b/js/src/dht/findprovs.js new file mode 100644 index 000000000..98f962afd --- /dev/null +++ b/js/src/dht/findprovs.js @@ -0,0 +1,57 @@ +/* eslint-env mocha */ +'use strict' + +const waterfall = require('async/waterfall') +const CID = require('cids') +const { spawnNodesWithId } = require('../utils/spawn') +const { getDescribe, getIt, expect } = require('../utils/mocha') + +module.exports = (createCommon, options) => { + const describe = getDescribe(options) + const it = getIt(options) + const common = createCommon() + + describe('.dht.findprovs', function () { + let nodeA + let nodeB + + before(function (done) { + // CI takes longer to instantiate the daemon, so we need to increase the + // timeout for the before step + this.timeout(60 * 1000) + + common.setup((err, factory) => { + expect(err).to.not.exist() + + spawnNodesWithId(2, factory, (err, nodes) => { + expect(err).to.not.exist() + + nodeA = nodes[0] + nodeB = nodes[1] + + nodeB.swarm.connect(nodeA.peerId.addresses[0], done) + }) + }) + }) + + after((done) => common.teardown(done)) + + it('should provide from one node and find it through another node', function (done) { + this.timeout(80 * 1000) + + waterfall([ + (cb) => nodeB.object.new('unixfs-dir', cb), + (dagNode, cb) => { + const cidV0 = new CID(dagNode.toJSON().multihash) + nodeB.dht.provide(cidV0, (err) => cb(err, cidV0)) + }, + (cidV0, cb) => nodeA.dht.findprovs(cidV0, cb), + (provs, cb) => { + expect(provs.map((p) => p.toB58String())) + .to.eql([nodeB.peerId.id]) + cb() + } + ], done) + }) + }) +} diff --git a/js/src/dht/get.js b/js/src/dht/get.js new file mode 100644 index 000000000..317ace930 --- /dev/null +++ b/js/src/dht/get.js @@ -0,0 +1,66 @@ +/* eslint-env mocha */ +'use strict' + +const waterfall = require('async/waterfall') +const { spawnNodesWithId } = require('../utils/spawn') +const { getDescribe, getIt, expect } = require('../utils/mocha') + +module.exports = (createCommon, options) => { + const describe = getDescribe(options) + const it = getIt(options) + const common = createCommon() + + describe('.dht.get', function () { + this.timeout(80 * 1000) + + let nodeA + let nodeB + + before(function (done) { + // CI takes longer to instantiate the daemon, so we need to increase the + // timeout for the before step + this.timeout(60 * 1000) + + common.setup((err, factory) => { + expect(err).to.not.exist() + + spawnNodesWithId(2, factory, (err, nodes) => { + expect(err).to.not.exist() + + nodeA = nodes[0] + nodeB = nodes[1] + + nodeA.swarm.connect(nodeB.peerId.addresses[0], done) + }) + }) + }) + + after((done) => common.teardown(done)) + + it('should error when getting a non-existent key from the DHT', (done) => { + nodeA.dht.get('non-existing', { timeout: '100ms' }, (err, value) => { + expect(err).to.be.an.instanceof(Error) + done() + }) + }) + + it('should get a value after it was put on another node', function (done) { + this.timeout(80 * 1000) + + // TODO - this test needs to keep tryingl instead of the setTimeout + waterfall([ + (cb) => nodeB.object.new('unixfs-dir', cb), + (dagNode, cb) => setTimeout(() => cb(null, dagNode), 20000), + (dagNode, cb) => { + const multihash = dagNode.toJSON().multihash + + nodeA.dht.get(multihash, cb) + }, + (result, cb) => { + expect(result).to.eql('') + cb() + } + ], done) + }) + }) +} diff --git a/js/src/dht/index.js b/js/src/dht/index.js new file mode 100644 index 000000000..07f5f68d9 --- /dev/null +++ b/js/src/dht/index.js @@ -0,0 +1,13 @@ +'use strict' +const { createSuite } = require('../utils/suite') + +const tests = { + get: require('./get'), + put: require('./put'), + findpeer: require('./findpeer'), + provide: require('./provide'), + findprovs: require('./findprovs'), + query: require('./query') +} + +module.exports = createSuite(tests) diff --git a/js/src/dht/provide.js b/js/src/dht/provide.js new file mode 100644 index 000000000..91c71f8e6 --- /dev/null +++ b/js/src/dht/provide.js @@ -0,0 +1,98 @@ +/* eslint-env mocha */ +'use strict' + +const CID = require('cids') +const { spawnNodesWithId } = require('../utils/spawn') +const { getDescribe, getIt, expect } = require('../utils/mocha') + +module.exports = (createCommon, options) => { + const describe = getDescribe(options) + const it = getIt(options) + const common = createCommon() + + describe('.dht.provide', function () { + this.timeout(80 * 1000) + + let ipfs + + before(function (done) { + // CI takes longer to instantiate the daemon, so we need to increase the + // timeout for the before step + this.timeout(60 * 1000) + + common.setup((err, factory) => { + expect(err).to.not.exist() + + spawnNodesWithId(2, factory, (err, nodes) => { + expect(err).to.not.exist() + ipfs = nodes[0] + ipfs.swarm.connect(nodes[1].peerId.addresses[0], done) + }) + }) + }) + + after((done) => common.teardown(done)) + + it('should provide local CID', (done) => { + ipfs.files.add(Buffer.from('test'), (err, res) => { + if (err) return done(err) + + ipfs.dht.provide(new CID(res[0].hash), (err) => { + expect(err).to.not.exist() + done() + }) + }) + }) + + it('should not provide if block not found locally', (done) => { + const cid = new CID('Qmd7qZS4T7xXtsNFdRoK1trfMs5zU94EpokQ9WFtxdPxsZ') + + ipfs.dht.provide(cid, (err) => { + expect(err).to.exist() + expect(err.message).to.include('not found locally') + done() + }) + }) + + it('should allow multiple CIDs to be passed', (done) => { + ipfs.files.add([Buffer.from('t0'), Buffer.from('t1')], (err, res) => { + if (err) return done(err) + + ipfs.dht.provide([ + new CID(res[0].hash), + new CID(res[1].hash) + ], (err) => { + expect(err).to.not.exist() + done() + }) + }) + }) + + it('should provide a CIDv1', (done) => { + ipfs.files.add(Buffer.from('test'), { 'cid-version': 1 }, (err, res) => { + if (err) return done(err) + + const cid = new CID(res[0].hash) + + ipfs.dht.provide(cid, (err) => { + expect(err).to.not.exist() + done() + }) + }) + }) + + it('should error on non CID arg', (done) => { + ipfs.dht.provide({}, (err) => { + expect(err).to.exist() + done() + }) + }) + + it('should error on array containing non CID arg', (done) => { + ipfs.dht.provide([{}], (err) => { + expect(err).to.exist() + done() + }) + }) + }) +} diff --git a/js/src/dht/put.js b/js/src/dht/put.js new file mode 100644 index 000000000..df07c2310 --- /dev/null +++ b/js/src/dht/put.js @@ -0,0 +1,29 @@ +/* eslint-env mocha */ +'use strict' + +const { getDescribe, getIt, expect } = require('../utils/mocha') + +module.exports = (createCommon, options) => { + const describe = getDescribe(options) + const it = getIt(options) + const common = createCommon() + + describe('.dht.put', function () { + before(function (done) { + // CI takes longer to instantiate the daemon, so we need to increase the + // timeout for the before step + this.timeout(60 * 1000) + + common.setup((err, factory) => { + expect(err).to.not.exist() + done() + }) + }) + + after((done) => common.teardown(done)) + + it.skip('should put a value on the DHT', (done) => { + // TODO: implement me + }) + }) +} diff --git a/js/src/dht/query.js b/js/src/dht/query.js new file mode 100644 index 000000000..c0ee95c15 --- /dev/null +++ b/js/src/dht/query.js @@ -0,0 +1,62 @@ +/* eslint-env mocha */ +'use strict' + +const { spawnNodesWithId } = require('../utils/spawn') +const { getDescribe, getIt, expect } = require('../utils/mocha') + +module.exports = (createCommon, options) => { + const describe = getDescribe(options) + const it = getIt(options) + const common = createCommon() + + describe('.dht.query', function () { + this.timeout(80 * 1000) + + let nodeA + let nodeB + + before(function (done) { + // CI takes longer to instantiate the daemon, so we need to increase the + // timeout for the before step + this.timeout(60 * 1000) + + common.setup((err, factory) => { + expect(err).to.not.exist() + + spawnNodesWithId(2, factory, (err, nodes) => { + expect(err).to.not.exist() + + nodeA = nodes[0] + nodeB = nodes[1] + + nodeB.swarm.connect(nodeA.peerId.addresses[0], done) + }) + }) + }) + + after((done) => common.teardown(done)) + + it('should return the other node in the query', function (done) { + const timeout = 150 * 1000 + this.timeout(timeout) + + let skipped = false + + // This test is meh. DHT works best with >= 20 nodes. Therefore a + // failure might happen, but we don't want to report it as such. + // Hence skip the test before the timeout is reached + const timeoutId = setTimeout(function () { + skipped = true + this.skip() + }.bind(this), timeout - 1000) + + nodeA.dht.query(nodeB.peerId.id, (err, peers) => { + if (skipped) return + clearTimeout(timeoutId) + expect(err).to.not.exist() + expect(peers.map((p) => p.ID)).to.include(nodeB.peerId.id) + done() + }) + }) + }) +} diff --git a/js/src/files-mfs.js b/js/src/files-mfs.js deleted file mode 100644 index 2d8a07225..000000000 --- a/js/src/files-mfs.js +++ /dev/null @@ -1,508 +0,0 @@ -/* eslint-env mocha */ -/* eslint max-nested-callbacks: ["error", 8] */ - -'use strict' - -const chai = require('chai') -const dirtyChai = require('dirty-chai') -const loadFixture = require('aegir/fixtures') -const expect = chai.expect -chai.use(dirtyChai) - -module.exports = (common) => { - describe('.files (MFS Specific)', function () { - this.timeout(40 * 1000) - - let ipfs - let withGo - - before(function (done) { - // CI takes longer to instantiate the daemon, so we need to increase the - // timeout for the before step - this.timeout(60 * 1000) - - common.setup((err, factory) => { - expect(err).to.not.exist() - factory.spawnNode((err, node) => { - expect(err).to.not.exist() - ipfs = node - node.id((err, id) => { - expect(err).to.not.exist() - withGo = id.agentVersion.startsWith('go-ipfs') - done() - }) - }) - }) - }) - - after((done) => common.teardown(done)) - - describe('.mkdir', function () { - it('make directory on root', function (done) { - if (!withGo) { - console.log('Not supported in js-ipfs yet') - this.skip() - } - - ipfs.files.mkdir('/test', (err) => { - expect(err).to.not.exist() - done() - }) - }) - - it('make directory and its parents', function (done) { - if (!withGo) { - console.log('Not supported in js-ipfs yet') - this.skip() - } - - ipfs.files.mkdir('/test/lv1/lv2', { p: true }, (err) => { - expect(err).to.not.exist() - done() - }) - }) - - it('make already existent directory', function (done) { - if (!withGo) { - console.log('Not supported in js-ipfs yet') - this.skip() - } - - ipfs.files.mkdir('/', (err) => { - expect(err).to.exist() - done() - }) - }) - }) - - describe('.write', function () { - it('expect error', function (done) { - if (!withGo) { - console.log('Not supported in js-ipfs yet') - this.skip() - } - - ipfs.files.write('/test/a', Buffer.from('Hello, world!'), (err) => { - expect(err).to.exist() - done() - }) - }) - - it('expect no error', function (done) { - if (!withGo) { - console.log('Not supported in js-ipfs yet') - this.skip() - } - - ipfs.files.write('/test/a', Buffer.from('Hello, world!'), {create: true}, (err) => { - expect(err).to.not.exist() - done() - }) - }) - }) - - describe('.cp', function () { - it('copy file, expect error', function (done) { - if (!withGo) { - console.log('Not supported in js-ipfs yet') - this.skip() - } - - ipfs.files.cp(['/test/c', '/test/b'], (err) => { - expect(err).to.exist() - done() - }) - }) - - it('copy file, expect no error', function (done) { - if (!withGo) { - console.log('Not supported in js-ipfs yet') - this.skip() - } - - ipfs.files.cp(['/test/a', '/test/b'], (err) => { - expect(err).to.not.exist() - done() - }) - }) - - it('copy dir, expect error', function (done) { - if (!withGo) { - console.log('Not supported in js-ipfs yet') - this.skip() - } - - ipfs.files.cp(['/test/lv1/lv3', '/test/lv1/lv4'], (err) => { - expect(err).to.exist() - done() - }) - }) - - it('copy dir, expect no error', function (done) { - if (!withGo) { - console.log('Not supported in js-ipfs yet') - this.skip() - } - - ipfs.files.cp(['/test/lv1/lv2', '/test/lv1/lv3'], (err) => { - expect(err).to.not.exist() - done() - }) - }) - }) - - describe('.mv', function () { - it('move file, expect error', function (done) { - if (!withGo) { - console.log('Not supported in js-ipfs yet') - this.skip() - } - - ipfs.files.mv(['/test/404', '/test/a'], (err) => { - expect(err).to.exist() - done() - }) - }) - - it('move file, expect no error', function (done) { - if (!withGo) { - console.log('Not supported in js-ipfs yet') - this.skip() - } - - ipfs.files.mv(['/test/a', '/test/c'], (err) => { - expect(err).to.not.exist() - done() - }) - }) - - it('move dir, expect error', function (done) { - if (!withGo) { - console.log('Not supported in js-ipfs yet') - this.skip() - } - - ipfs.files.mv(['/test/lv1/404', '/test/lv1'], (err) => { - expect(err).to.exist() - done() - }) - }) - - it('move dir, expect no error', function (done) { - if (!withGo) { - console.log('Not supported in js-ipfs yet') - this.skip() - } - - ipfs.files.mv(['/test/lv1/lv2', '/test/lv1/lv4'], (err) => { - expect(err).to.not.exist() - done() - }) - }) - }) - - describe('.rm', function () { - it('remove file, expect error', function (done) { - if (!withGo) { - console.log('Not supported in js-ipfs yet') - this.skip() - } - - ipfs.files.rm('/test/a', (err) => { - expect(err).to.exist() - done() - }) - }) - - it('remove file, expect no error', function (done) { - if (!withGo) { - console.log('Not supported in js-ipfs yet') - this.skip() - } - - ipfs.files.rm('/test/c', (err) => { - expect(err).to.not.exist() - done() - }) - }) - - it('remove dir, expect error', function (done) { - if (!withGo) { - console.log('Not supported in js-ipfs yet') - this.skip() - } - - ipfs.files.rm('/test/lv1/lv4', (err) => { - expect(err).to.exist() - done() - }) - }) - - it('remove dir, expect no error', function (done) { - if (!withGo) { - console.log('Not supported in js-ipfs yet') - this.skip() - } - - ipfs.files.rm('/test/lv1/lv4', {recursive: true}, (err) => { - expect(err).to.not.exist() - done() - }) - }) - }) - - describe('.stat', function () { - it('stat not found, expect error', function (done) { - if (!withGo) { - console.log('Not supported in js-ipfs yet') - this.skip() - } - - ipfs.files.stat('/test/404', (err) => { - expect(err).to.exist() - done() - }) - }) - - it('stat file', function (done) { - if (!withGo) { - console.log('Not supported in js-ipfs yet') - this.skip() - } - - ipfs.files.stat('/test/b', (err, stat) => { - expect(err).to.not.exist() - expect(stat).to.eql({ - type: 'file', - blocks: 1, - size: 13, - hash: 'QmcZojhwragQr5qhTeFAmELik623Z21e3jBTpJXoQ9si1T', - cumulativeSize: 71, - withLocality: false, - local: undefined, - sizeLocal: undefined - }) - done() - }) - }) - - it('stat dir', function (done) { - if (!withGo) { - console.log('Not supported in js-ipfs yet') - this.skip() - } - - ipfs.files.stat('/test', (err, stat) => { - expect(err).to.not.exist() - expect(stat).to.eql({ - type: 'directory', - blocks: 2, - size: 0, - hash: 'QmVrkkNurBCeJvPRohW5JTvJG4AxGrFg7FnmsZZUS6nJto', - cumulativeSize: 216, - withLocality: false, - local: undefined, - sizeLocal: undefined - }) - done() - }) - }) - - // TODO enable this test when this feature gets released on go-ipfs - it.skip('stat withLocal file', function (done) { - if (!withGo) { - console.log('Not supported in js-ipfs yet') - this.skip() - } - - ipfs.files.stat('/test/b', {'withLocal': true}, (err, stat) => { - expect(err).to.not.exist() - expect(stat).to.eql({ - type: 'file', - blocks: 1, - size: 13, - hash: 'QmcZojhwragQr5qhTeFAmELik623Z21e3jBTpJXoQ9si1T', - cumulativeSize: 71, - withLocality: true, - local: true, - sizeLocal: 71 - }) - done() - }) - }) - - // TODO enable this test when this feature gets released on go-ipfs - it.skip('stat withLocal dir', function (done) { - if (!withGo) { - console.log('Not supported in js-ipfs yet') - this.skip() - } - - ipfs.files.stat('/test', {'withLocal': true}, (err, stat) => { - expect(err).to.not.exist() - expect(stat).to.eql({ - type: 'directory', - blocks: 2, - size: 0, - hash: 'QmVrkkNurBCeJvPRohW5JTvJG4AxGrFg7FnmsZZUS6nJto', - cumulativeSize: 216, - withLocality: true, - local: true, - sizeLocal: 216 - }) - done() - }) - }) - }) - - describe('.read', function () { - it('read not found, expect error', function (done) { - if (!withGo) { - console.log('Not supported in js-ipfs yet') - this.skip() - } - - ipfs.files.read('/test/404', (err, buf) => { - expect(err).to.exist() - expect(buf).to.not.exist() - done() - }) - }) - - it('read file', function (done) { - if (!withGo) { - console.log('Not supported in js-ipfs yet') - this.skip() - } - - ipfs.files.read('/test/b', (err, buf) => { - expect(err).to.not.exist() - expect(buf).to.eql(Buffer.from('Hello, world!')) - done() - }) - }) - }) - - describe('.ls', function () { - it('ls not found, expect error', function (done) { - if (!withGo) { - console.log('Not supported in js-ipfs yet') - this.skip() - } - - ipfs.files.ls('/test/404', (err, info) => { - expect(err).to.exist() - expect(info).to.not.exist() - done() - }) - }) - - it('ls directory', function (done) { - if (!withGo) { - console.log('Not supported in js-ipfs yet') - this.skip() - } - - ipfs.files.ls('/test', (err, info) => { - expect(err).to.not.exist() - expect(info).to.eql([ - { name: 'b', type: 0, size: 0, hash: '' }, - { name: 'lv1', type: 0, size: 0, hash: '' } - ]) - done() - }) - }) - - it('ls -l directory', function (done) { - if (!withGo) { - console.log('Not supported in js-ipfs yet') - this.skip() - } - - ipfs.files.ls('/test', { l: true }, (err, info) => { - expect(err).to.not.exist() - expect(info).to.eql([ - { - name: 'b', - type: 0, - size: 13, - hash: 'QmcZojhwragQr5qhTeFAmELik623Z21e3jBTpJXoQ9si1T' - }, - { - name: 'lv1', - type: 1, - size: 0, - hash: 'QmaSPtNHYKPjNjQnYX9pdu5ocpKUQEL3itSz8LuZcoW6J5' - } - ]) - done() - }) - }) - }) - - describe('.flush', function () { - it('flush not found, expect error', function (done) { - if (!withGo) { - console.log('Not supported in js-ipfs yet') - this.skip() - } - - ipfs.files.flush('/test/404', (err) => { - expect(err).to.exist() - done() - }) - }) - - it('flush root', function (done) { - if (!withGo) { - console.log('Not supported in js-ipfs yet') - this.skip() - } - - ipfs.files.flush((err) => { - expect(err).to.not.exist() - done() - }) - }) - - it('flush specific dir', function (done) { - if (!withGo) { - console.log('Not supported in js-ipfs yet') - this.skip() - } - - ipfs.files.flush('/test', (err) => { - expect(err).to.not.exist() - done() - }) - }) - }) - - // TODO: (achingbrain) - Not yet supported in js-ipfs or go-ipfs yet') - describe.skip('.stat', () => { - const smallFile = { - cid: 'Qma4hjFTnCasJ8PVp3mZbZK5g2vGDT4LByLJ7m8ciyRFZP', - data: loadFixture('js/test/fixtures/testfile.txt', 'interface-ipfs-core') - } - - before((done) => ipfs.files.add(smallFile.data, done)) - - it.skip('stat outside of mfs', function (done) { - ipfs.files.stat('/ipfs/' + smallFile.cid, (err, stat) => { - expect(err).to.not.exist() - expect(stat).to.eql({ - type: 'file', - blocks: 0, - size: 12, - hash: 'Qma4hjFTnCasJ8PVp3mZbZK5g2vGDT4LByLJ7m8ciyRFZP', - cumulativeSize: 20, - withLocality: false, - local: undefined, - sizeLocal: undefined - }) - done() - }) - }) - }) - }) -} diff --git a/js/src/files.js b/js/src/files.js deleted file mode 100644 index ff8f3a961..000000000 --- a/js/src/files.js +++ /dev/null @@ -1,1170 +0,0 @@ -/* eslint-env mocha */ -/* eslint max-nested-callbacks: ["error", 8] */ - -'use strict' - -const chai = require('chai') -const dirtyChai = require('dirty-chai') -const expect = chai.expect -chai.use(dirtyChai) -const loadFixture = require('aegir/fixtures') -const bs58 = require('bs58') -const parallel = require('async/parallel') -const series = require('async/series') -const Readable = require('readable-stream').Readable -const pull = require('pull-stream') -const concat = require('concat-stream') -const through = require('through2') -const path = require('path') -const bl = require('bl') -const isNode = require('detect-node') -const CID = require('cids') -const expectTimeout = require('./utils/expect-timeout') - -module.exports = (common) => { - describe('.files', function () { - this.timeout(40 * 1000) - - let ipfs - let withGo - - function fixture (path) { - return loadFixture(path, 'interface-ipfs-core') - } - - const smallFile = { - cid: 'Qma4hjFTnCasJ8PVp3mZbZK5g2vGDT4LByLJ7m8ciyRFZP', - data: fixture('js/test/fixtures/testfile.txt') - } - - const bigFile = { - cid: 'Qme79tX2bViL26vNjPsF3DP1R9rMKMvnPYJiKTTKPrXJjq', - data: fixture('js/test/fixtures/15mb.random') - } - - const directory = { - cid: 'QmVvjDy7yF7hdnqE8Hrf4MHo5ABDtb5AbX6hWbD3Y42bXP', - files: { - 'pp.txt': fixture('js/test/fixtures/test-folder/pp.txt'), - 'holmes.txt': fixture('js/test/fixtures/test-folder/holmes.txt'), - 'jungle.txt': fixture('js/test/fixtures/test-folder/jungle.txt'), - 'alice.txt': fixture('js/test/fixtures/test-folder/alice.txt'), - 'files/hello.txt': fixture('js/test/fixtures/test-folder/files/hello.txt'), - 'files/ipfs.txt': fixture('js/test/fixtures/test-folder/files/ipfs.txt') - } - } - - before(function (done) { - // CI takes longer to instantiate the daemon, so we need to increase the - // timeout for the before step - this.timeout(60 * 1000) - - common.setup((err, factory) => { - expect(err).to.not.exist() - factory.spawnNode((err, node) => { - expect(err).to.not.exist() - ipfs = node - node.id((err, id) => { - expect(err).to.not.exist() - withGo = id.agentVersion.startsWith('go-ipfs') - done() - }) - }) - }) - }) - - after((done) => common.teardown(done)) - - describe('.add', () => { - it('a Buffer', (done) => { - ipfs.files.add(smallFile.data, (err, filesAdded) => { - expect(err).to.not.exist() - - expect(filesAdded).to.have.length(1) - const file = filesAdded[0] - expect(file.hash).to.equal(smallFile.cid) - expect(file.path).to.equal(smallFile.cid) - // file.size counts the overhead by IPLD nodes and unixfs protobuf - expect(file.size).greaterThan(smallFile.data.length) - done() - }) - }) - - it('a BIG buffer', (done) => { - ipfs.files.add(bigFile.data, (err, filesAdded) => { - expect(err).to.not.exist() - - expect(filesAdded).to.have.length(1) - const file = filesAdded[0] - expect(file.hash).to.equal(bigFile.cid) - expect(file.path).to.equal(bigFile.cid) - // file.size counts the overhead by IPLD nodes and unixfs protobuf - expect(file.size).greaterThan(bigFile.data.length) - done() - }) - }) - - it('a BIG buffer with progress enabled', (done) => { - let progCalled = false - let accumProgress = 0 - function handler (p) { - progCalled = true - accumProgress = p - } - - ipfs.files.add(bigFile.data, { progress: handler }, (err, filesAdded) => { - expect(err).to.not.exist() - - expect(filesAdded).to.have.length(1) - const file = filesAdded[0] - expect(file.hash).to.equal(bigFile.cid) - expect(file.path).to.equal(bigFile.cid) - - expect(progCalled).to.be.true() - expect(accumProgress).to.equal(bigFile.data.length) - done() - }) - }) - - it('a Buffer as tuple', (done) => { - const tuple = { path: 'testfile.txt', content: smallFile.data } - - ipfs.files.add([ - tuple - ], (err, filesAdded) => { - expect(err).to.not.exist() - - expect(filesAdded).to.have.length(1) - const file = filesAdded[0] - expect(file.hash).to.equal(smallFile.cid) - expect(file.path).to.equal('testfile.txt') - - done() - }) - }) - - it('add by path fails', (done) => { - const validPath = path.join(process.cwd() + '/package.json') - - ipfs.files.add(validPath, (err, res) => { - expect(err).to.exist() - done() - }) - }) - - it('adds from readable stream', (done) => { - const expectedCid = 'QmVv4Wz46JaZJeH5PMV4LGbRiiMKEmszPYY3g6fjGnVXBS' - - const rs = new Readable() - rs.push(Buffer.from('some data')) - rs.push(null) - - ipfs.files.add(rs, (err, filesAdded) => { - expect(err).to.not.exist() - - expect(filesAdded).to.be.length(1) - const file = filesAdded[0] - expect(file.path).to.equal(expectedCid) - expect(file.size).to.equal(17) - expect(file.hash).to.equal(expectedCid) - done() - }) - }) - - it('adds from array of objects with readable stream content', (done) => { - const expectedCid = 'QmVv4Wz46JaZJeH5PMV4LGbRiiMKEmszPYY3g6fjGnVXBS' - - const rs = new Readable() - rs.push(Buffer.from('some data')) - rs.push(null) - - const tuple = { path: 'data.txt', content: rs } - - ipfs.files.add([tuple], (err, filesAdded) => { - expect(err).to.not.exist() - - expect(filesAdded).to.be.length(1) - const file = filesAdded[0] - expect(file.path).to.equal('data.txt') - expect(file.size).to.equal(17) - expect(file.hash).to.equal(expectedCid) - done() - }) - }) - - it('adds from pull stream (callback)', (done) => { - const expectedCid = 'QmRf22bZar3WKmojipms22PkXH1MZGmvsqzQtuSvQE3uhm' - - ipfs.files.add(pull.values([Buffer.from('test')]), (err, res) => { - if (err) return done(err) - expect(res).to.have.length(1) - expect(res[0]).to.deep.equal({ path: expectedCid, hash: expectedCid, size: 12 }) - done() - }) - }) - - it('adds from pull stream (promise)', () => { - const expectedCid = 'QmRf22bZar3WKmojipms22PkXH1MZGmvsqzQtuSvQE3uhm' - - return ipfs.files.add(pull.values([Buffer.from('test')])) - .then((res) => { - expect(res).to.have.length(1) - expect(res[0]).to.deep.equal({ path: expectedCid, hash: expectedCid, size: 12 }) - }) - }) - - it('adds from array of objects with pull stream content', () => { - const expectedCid = 'QmRf22bZar3WKmojipms22PkXH1MZGmvsqzQtuSvQE3uhm' - - return ipfs.files.add([{ content: pull.values([Buffer.from('test')]) }]) - .then((res) => { - expect(res).to.have.length(1) - expect(res[0]).to.deep.equal({ path: expectedCid, hash: expectedCid, size: 12 }) - }) - }) - - it('add a nested directory as array of tupples', function (done) { - // TODO: https://github.com/ipfs/js-ipfs-api/issues/339 - if (!isNode) { this.skip() } - - const content = (name) => ({ - path: `test-folder/${name}`, - content: directory.files[name] - }) - - const emptyDir = (name) => ({ path: `test-folder/${name}` }) - - const dirs = [ - content('pp.txt'), - content('holmes.txt'), - content('jungle.txt'), - content('alice.txt'), - emptyDir('empty-folder'), - content('files/hello.txt'), - content('files/ipfs.txt'), - emptyDir('files/empty') - ] - - ipfs.files.add(dirs, (err, res) => { - expect(err).to.not.exist() - const root = res[res.length - 1] - - expect(root.path).to.equal('test-folder') - expect(root.hash).to.equal(directory.cid) - done() - }) - }) - - it('add a nested directory as array of tuppled with progress', function (done) { - // TODO: https://github.com/ipfs/js-ipfs-api/issues/339 - if (!isNode) { this.skip() } - - const content = (name) => ({ - path: `test-folder/${name}`, - content: directory.files[name] - }) - - const emptyDir = (name) => ({ path: `test-folder/${name}` }) - - const dirs = [ - content('pp.txt'), - content('holmes.txt'), - content('jungle.txt'), - content('alice.txt'), - emptyDir('empty-folder'), - content('files/hello.txt'), - content('files/ipfs.txt'), - emptyDir('files/empty') - ] - - const total = dirs.reduce((i, entry) => { - return i + (entry.content ? entry.content.length : 0) - }, 0) - - let progCalled = false - let accumProgress = 0 - const handler = (p) => { - progCalled = true - accumProgress += p - } - - ipfs.files.add(dirs, { progress: handler }, (err, filesAdded) => { - expect(err).to.not.exist() - const root = filesAdded[filesAdded.length - 1] - - expect(progCalled).to.be.true() - expect(accumProgress).to.be.at.least(total) - expect(root.path).to.equal('test-folder') - expect(root.hash).to.equal(directory.cid) - done() - }) - }) - - it('fails in invalid input', (done) => { - const nonValid = 'sfdasfasfs' - - ipfs.files.add(nonValid, (err, result) => { - expect(err).to.exist() - done() - }) - }) - - it('wrapWithDirectory', (done) => { - return ipfs.files.add({ path: 'testfile.txt', content: smallFile.data }, { wrapWithDirectory: true }, (err, filesAdded) => { - expect(err).to.not.exist() - expect(filesAdded).to.have.length(2) - const file = filesAdded[0] - const wrapped = filesAdded[1] - expect(file.hash).to.equal(smallFile.cid) - expect(file.path).to.equal('testfile.txt') - expect(wrapped.path).to.equal('') - done() - }) - }) - - it('Promise test', () => { - return ipfs.files.add(smallFile.data) - .then((filesAdded) => { - const file = filesAdded[0] - expect(file.hash).to.equal(smallFile.cid) - expect(file.path).to.equal(smallFile.cid) - }) - }) - - it('files.add with only-hash=true', () => { - this.slow(10 * 1000) - const content = String(Math.random() + Date.now()) - - return ipfs.files.add(Buffer.from(content), { onlyHash: true }) - .then(files => { - expect(files).to.have.length(1) - - // 'ipfs.object.get()' should timeout because content wasn't actually added - return expectTimeout(ipfs.object.get(files[0].hash), 4000) - }) - }) - }) - - describe('.addReadableStream', () => { - it('stream of valid files and dirs', function (done) { - // TODO: https://github.com/ipfs/js-ipfs-api/issues/339 - if (!isNode) { this.skip() } - - const content = (name) => ({ - path: `test-folder/${name}`, - content: directory.files[name] - }) - - const emptyDir = (name) => ({ path: `test-folder/${name}` }) - - const files = [ - content('pp.txt'), - content('holmes.txt'), - content('jungle.txt'), - content('alice.txt'), - emptyDir('empty-folder'), - content('files/hello.txt'), - content('files/ipfs.txt'), - emptyDir('files/empty') - ] - - const stream = ipfs.files.addReadableStream() - - stream.on('error', (err) => { - expect(err).to.not.exist() - }) - - stream.on('data', (file) => { - if (file.path === 'test-folder') { - expect(file.hash).to.equal(directory.cid) - done() - } - }) - - files.forEach((file) => stream.write(file)) - stream.end() - }) - }) - - describe('.addPullStream', () => { - it('stream of valid files and dirs', function (done) { - // TODO: https://github.com/ipfs/js-ipfs-api/issues/339 - if (!isNode) { this.skip() } - - const content = (name) => ({ - path: `test-folder/${name}`, - content: directory.files[name] - }) - - const emptyDir = (name) => ({ path: `test-folder/${name}` }) - - const files = [ - content('pp.txt'), - content('holmes.txt'), - content('jungle.txt'), - content('alice.txt'), - emptyDir('empty-folder'), - content('files/hello.txt'), - content('files/ipfs.txt'), - emptyDir('files/empty') - ] - - const stream = ipfs.files.addPullStream() - - pull( - pull.values(files), - stream, - pull.collect((err, filesAdded) => { - expect(err).to.not.exist() - - filesAdded.forEach((file) => { - if (file.path === 'test-folder') { - expect(file.hash).to.equal(directory.cid) - done() - } - }) - }) - ) - }) - - it('adds with object chunks and pull stream content', (done) => { - const expectedCid = 'QmRf22bZar3WKmojipms22PkXH1MZGmvsqzQtuSvQE3uhm' - - pull( - pull.values([{ content: pull.values([Buffer.from('test')]) }]), - ipfs.files.addPullStream(), - pull.collect((err, res) => { - if (err) return done(err) - expect(res).to.have.length(1) - expect(res[0]).to.deep.equal({ path: expectedCid, hash: expectedCid, size: 12 }) - done() - }) - ) - }) - }) - - describe('.cat', () => { - before((done) => { - parallel([ - (cb) => ipfs.files.add(smallFile.data, cb), - (cb) => ipfs.files.add(bigFile.data, cb) - ], done) - }) - - it('with a base58 string encoded multihash', (done) => { - ipfs.files.cat(smallFile.cid, (err, data) => { - expect(err).to.not.exist() - expect(data.toString()).to.contain('Plz add me!') - done() - }) - }) - - it('with a multihash', (done) => { - const cid = Buffer.from(bs58.decode(smallFile.cid)) - - ipfs.files.cat(cid, (err, data) => { - expect(err).to.not.exist() - expect(data.toString()).to.contain('Plz add me!') - done() - }) - }) - - it('with a cid object', (done) => { - const cid = new CID(smallFile.cid) - - ipfs.files.cat(cid, (err, data) => { - expect(err).to.not.exist() - expect(data.toString()).to.contain('Plz add me!') - done() - }) - }) - - it('streams a large file', (done) => { - ipfs.files.cat(bigFile.cid, (err, data) => { - expect(err).to.not.exist() - expect(data.length).to.equal(bigFile.data.length) - expect(data).to.eql(bigFile.data) - done() - }) - }) - - it('with ipfs path', (done) => { - const ipfsPath = '/ipfs/' + smallFile.cid - - ipfs.files.cat(ipfsPath, (err, data) => { - expect(err).to.not.exist() - expect(data.toString()).to.contain('Plz add me!') - done() - }) - }) - - it('with ipfs path, nested value', (done) => { - const file = { path: 'a/testfile.txt', content: smallFile.data } - - ipfs.files.add([file], (err, filesAdded) => { - expect(err).to.not.exist() - - filesAdded.forEach((file) => { - if (file.path === 'a') { - ipfs.files.cat(`/ipfs/${file.hash}/testfile.txt`, (err, data) => { - expect(err).to.not.exist() - expect(data.toString()).to.contain('Plz add me!') - done() - }) - } - }) - }) - }) - - it('Promise test', () => { - return ipfs.files.cat(smallFile.cid) - .then((data) => { - expect(data.toString()).to.contain('Plz add me!') - }) - }) - - it('errors on invalid key', () => { - const invalidCid = 'somethingNotMultihash' - - return ipfs.files.cat(invalidCid) - .catch((err) => { - expect(err).to.exist() - - const errString = err.toString() - if (errString === 'Error: invalid ipfs ref path') { - expect(err.toString()).to.contain('Error: invalid ipfs ref path') - } - - if (errString === 'Error: Invalid Key') { - expect(err.toString()).to.contain('Error: Invalid Key') - } - }) - }) - - it('errors on unknown path', () => { - return ipfs.files.cat(smallFile.cid + '/does-not-exist') - .catch((err) => { - expect(err).to.exist() - expect(err.message).to.oneOf([ - 'No such file', - 'no link named "does-not-exist" under Qma4hjFTnCasJ8PVp3mZbZK5g2vGDT4LByLJ7m8ciyRFZP']) - }) - }) - - it('errors on dir path', () => { - const file = { path: 'dir/testfile.txt', content: smallFile.data } - - return ipfs.files.add([file]) - .then((filesAdded) => { - expect(filesAdded.length).to.equal(2) - const files = filesAdded.filter((file) => file.path === 'dir') - expect(files.length).to.equal(1) - const dir = files[0] - return ipfs.files.cat(dir.hash) - .catch((err) => { - expect(err).to.exist() - expect(err.message).to.contain('this dag node is a directory') - }) - }) - }) - - it('exports a chunk of a file', function (done) { - if (withGo) { this.skip() } - - const offset = 1 - const length = 3 - - ipfs.files.cat(smallFile.cid, { - offset, - length - }, (err, data) => { - expect(err).to.not.exist() - expect(data.toString()).to.equal('lz ') - done() - }) - }) - }) - - describe('.catReadableStream', () => { - before((done) => ipfs.files.add(bigFile.data, done)) - - it('returns a Readable Stream for a cid', (done) => { - const stream = ipfs.files.catReadableStream(bigFile.cid) - - stream.pipe(bl((err, data) => { - expect(err).to.not.exist() - expect(data).to.eql(bigFile.data) - done() - })) - }) - - it('exports a chunk of a file in a ReadableStream', function (done) { - if (withGo) { this.skip() } - - const offset = 1 - const length = 3 - - const stream = ipfs.files.catReadableStream(smallFile.cid, { - offset, - length - }) - - stream.pipe(bl((err, data) => { - expect(err).to.not.exist() - expect(data.toString()).to.equal('lz ') - done() - })) - }) - }) - - describe('.catPullStream', () => { - before((done) => ipfs.files.add(smallFile.data, done)) - - it('returns a Pull Stream for a cid', (done) => { - const stream = ipfs.files.catPullStream(smallFile.cid) - - pull( - stream, - pull.concat((err, data) => { - expect(err).to.not.exist() - expect(data.length).to.equal(smallFile.data.length) - expect(data).to.eql(smallFile.data.toString()) - done() - }) - ) - }) - - it('exports a chunk of a file in a PullStream', function (done) { - if (withGo) { this.skip() } - - const offset = 1 - const length = 3 - - const stream = ipfs.files.catPullStream(smallFile.cid, { - offset, - length - }) - - pull( - stream, - pull.concat((err, data) => { - expect(err).to.not.exist() - expect(data.toString()).to.equal('lz ') - done() - }) - ) - }) - }) - - describe('.get', () => { - before((done) => { - parallel([ - (cb) => ipfs.files.add(smallFile.data, cb), - (cb) => ipfs.files.add(bigFile.data, cb) - ], done) - }) - - it('with a base58 encoded multihash', (done) => { - ipfs.files.get(smallFile.cid, (err, files) => { - expect(err).to.not.exist() - - expect(files).to.be.length(1) - expect(files[0].path).to.eql(smallFile.cid) - expect(files[0].content.toString('utf8')).to.contain('Plz add me!') - done() - }) - }) - - it('with a multihash', (done) => { - const cidBuf = Buffer.from(bs58.decode(smallFile.cid)) - ipfs.files.get(cidBuf, (err, files) => { - expect(err).to.not.exist() - - expect(files).to.be.length(1) - expect(files[0].path).to.eql(smallFile.cid) - expect(files[0].content.toString('utf8')).to.contain('Plz add me!') - done() - }) - }) - - it('large file', (done) => { - ipfs.files.get(bigFile.cid, (err, files) => { - expect(err).to.not.exist() - - expect(files.length).to.equal(1) - expect(files[0].path).to.equal(bigFile.cid) - expect(files[0].content.length).to.eql(bigFile.data.length) - expect(files[0].content).to.eql(bigFile.data) - done() - }) - }) - - it('directory', function (done) { - // TODO: https://github.com/ipfs/js-ipfs-api/issues/339 - if (!isNode) { this.skip() } - - series([ - (cb) => { - const content = (name) => ({ - path: `test-folder/${name}`, - content: directory.files[name] - }) - - const emptyDir = (name) => ({ path: `test-folder/${name}` }) - - const dirs = [ - content('pp.txt'), - content('holmes.txt'), - content('jungle.txt'), - content('alice.txt'), - emptyDir('empty-folder'), - content('files/hello.txt'), - content('files/ipfs.txt'), - emptyDir('files/empty') - ] - - ipfs.files.add(dirs, (err, res) => { - expect(err).to.not.exist() - const root = res[res.length - 1] - - expect(root.path).to.equal('test-folder') - expect(root.hash).to.equal(directory.cid) - cb() - }) - }, - (cb) => { - ipfs.files.get(directory.cid, (err, files) => { - expect(err).to.not.exist() - - files = files.sort((a, b) => { - if (a.path > b.path) return 1 - if (a.path < b.path) return -1 - return 0 - }) - - // Check paths - const paths = files.map((file) => { return file.path }) - expect(paths).to.include.members([ - 'QmVvjDy7yF7hdnqE8Hrf4MHo5ABDtb5AbX6hWbD3Y42bXP', - 'QmVvjDy7yF7hdnqE8Hrf4MHo5ABDtb5AbX6hWbD3Y42bXP/alice.txt', - 'QmVvjDy7yF7hdnqE8Hrf4MHo5ABDtb5AbX6hWbD3Y42bXP/empty-folder', - 'QmVvjDy7yF7hdnqE8Hrf4MHo5ABDtb5AbX6hWbD3Y42bXP/files', - 'QmVvjDy7yF7hdnqE8Hrf4MHo5ABDtb5AbX6hWbD3Y42bXP/files/empty', - 'QmVvjDy7yF7hdnqE8Hrf4MHo5ABDtb5AbX6hWbD3Y42bXP/files/hello.txt', - 'QmVvjDy7yF7hdnqE8Hrf4MHo5ABDtb5AbX6hWbD3Y42bXP/files/ipfs.txt', - 'QmVvjDy7yF7hdnqE8Hrf4MHo5ABDtb5AbX6hWbD3Y42bXP/holmes.txt', - 'QmVvjDy7yF7hdnqE8Hrf4MHo5ABDtb5AbX6hWbD3Y42bXP/jungle.txt', - 'QmVvjDy7yF7hdnqE8Hrf4MHo5ABDtb5AbX6hWbD3Y42bXP/pp.txt' - ]) - - // Check contents - const contents = files.map((file) => { - return file.content - ? file.content.toString() - : null - }) - - expect(contents).to.include.members([ - directory.files['alice.txt'].toString(), - directory.files['files/hello.txt'].toString(), - directory.files['files/ipfs.txt'].toString(), - directory.files['holmes.txt'].toString(), - directory.files['jungle.txt'].toString(), - directory.files['pp.txt'].toString() - ]) - cb() - }) - } - ], done) - }) - - it('with ipfs path, as object and nested value', (done) => { - const file = { - path: 'a/testfile.txt', - content: smallFile.data - } - - ipfs.files.add(file, (err, filesAdded) => { - expect(err).to.not.exist() - - filesAdded.forEach((file) => { - if (file.path === 'a') { - ipfs.files.get(`/ipfs/${file.hash}/testfile.txt`, (err, files) => { - expect(err).to.not.exist() - expect(files).to.be.length(1) - expect(files[0].content.toString('utf8')).to.contain('Plz add me!') - done() - }) - } - }) - }) - }) - - it('with ipfs path, as array and nested value', (done) => { - const file = { - path: 'a/testfile.txt', - content: smallFile.data - } - - ipfs.files.add([file], (err, filesAdded) => { - expect(err).to.not.exist() - - filesAdded.forEach((file) => { - if (file.path === 'a') { - ipfs.files.get(`/ipfs/${file.hash}/testfile.txt`, (err, files) => { - expect(err).to.not.exist() - expect(files).to.be.length(1) - expect(files[0].content.toString('utf8')).to.contain('Plz add me!') - done() - }) - } - }) - }) - }) - - it('Promise test', () => { - return ipfs.files.get(smallFile.cid) - .then((files) => { - expect(files).to.be.length(1) - expect(files[0].path).to.equal(smallFile.cid) - expect(files[0].content.toString()).to.contain('Plz add me!') - }) - }) - - it('errors on invalid key', () => { - const invalidCid = 'somethingNotMultihash' - - return ipfs.files.get(invalidCid) - .catch((err) => { - expect(err).to.exist() - const errString = err.toString() - if (errString === 'Error: invalid ipfs ref path') { - expect(err.toString()).to.contain('Error: invalid ipfs ref path') - } - if (errString === 'Error: Invalid Key') { - expect(err.toString()).to.contain('Error: Invalid Key') - } - }) - }) - }) - - describe('.getReadableStream', () => { - before((done) => ipfs.files.add(smallFile.data, done)) - - it('returns a Readable Stream of Readable Streams', (done) => { - const stream = ipfs.files.getReadableStream(smallFile.cid) - - let files = [] - stream.pipe(through.obj((file, enc, next) => { - file.content.pipe(concat((content) => { - files.push({ path: file.path, content: content }) - next() - })) - }, () => { - expect(files).to.be.length(1) - expect(files[0].path).to.eql(smallFile.cid) - expect(files[0].content.toString()).to.contain('Plz add me!') - done() - })) - }) - }) - - describe('.getPullStream', () => { - before((done) => ipfs.files.add(smallFile.data, done)) - - it('returns a Pull Stream of Pull Streams', (done) => { - const stream = ipfs.files.getPullStream(smallFile.cid) - - pull( - stream, - pull.collect((err, files) => { - expect(err).to.not.exist() - expect(files).to.be.length(1) - expect(files[0].path).to.eql(smallFile.cid) - pull( - files[0].content, - pull.concat((err, data) => { - expect(err).to.not.exist() - expect(data.toString()).to.contain('Plz add me!') - done() - }) - ) - }) - ) - }) - }) - - describe('.ls', () => { - before(function (done) { - // TODO: https://github.com/ipfs/js-ipfs-api/issues/339 - if (!isNode) { this.skip() } - - const content = (name) => ({ - path: `test-folder/${name}`, - content: directory.files[name] - }) - - const emptyDir = (name) => ({ path: `test-folder/${name}` }) - - const dirs = [ - content('pp.txt'), - content('holmes.txt'), - content('jungle.txt'), - content('alice.txt'), - emptyDir('empty-folder'), - content('files/hello.txt'), - content('files/ipfs.txt'), - emptyDir('files/empty') - ] - - ipfs.files.add(dirs, (err, res) => { - expect(err).to.not.exist() - const root = res[res.length - 1] - - expect(root.path).to.equal('test-folder') - expect(root.hash).to.equal(directory.cid) - done() - }) - }) - - it('with a base58 encoded CID', function (done) { - // TODO: https://github.com/ipfs/js-ipfs-api/issues/339 - if (!isNode) { this.skip() } - - const cid = 'QmVvjDy7yF7hdnqE8Hrf4MHo5ABDtb5AbX6hWbD3Y42bXP' - ipfs.ls(cid, (err, files) => { - expect(err).to.not.exist() - - expect(files).to.eql([ - { depth: 1, - name: 'alice.txt', - path: 'QmVvjDy7yF7hdnqE8Hrf4MHo5ABDtb5AbX6hWbD3Y42bXP/alice.txt', - size: 11696, - hash: 'QmZyUEQVuRK3XV7L9Dk26pg6RVSgaYkiSTEdnT2kZZdwoi', - type: 'file' }, - { depth: 1, - name: 'empty-folder', - path: 'QmVvjDy7yF7hdnqE8Hrf4MHo5ABDtb5AbX6hWbD3Y42bXP/empty-folder', - size: 4, - hash: 'QmUNLLsPACCz1vLxQVkXqqLX5R1X345qqfHbsf67hvA3Nn', - type: 'dir' }, - { depth: 1, - name: 'files', - path: 'QmVvjDy7yF7hdnqE8Hrf4MHo5ABDtb5AbX6hWbD3Y42bXP/files', - size: 183, - hash: 'QmZ25UfTqXGz9RsEJFg7HUAuBcmfx5dQZDXQd2QEZ8Kj74', - type: 'dir' }, - { depth: 1, - name: 'holmes.txt', - path: 'QmVvjDy7yF7hdnqE8Hrf4MHo5ABDtb5AbX6hWbD3Y42bXP/holmes.txt', - size: 582072, - hash: 'QmR4nFjTu18TyANgC65ArNWp5Yaab1gPzQ4D8zp7Kx3vhr', - type: 'file' }, - { depth: 1, - name: 'jungle.txt', - path: 'QmVvjDy7yF7hdnqE8Hrf4MHo5ABDtb5AbX6hWbD3Y42bXP/jungle.txt', - size: 2305, - hash: 'QmT6orWioMiSqXXPGsUi71CKRRUmJ8YkuueV2DPV34E9y9', - type: 'file' }, - { depth: 1, - name: 'pp.txt', - path: 'QmVvjDy7yF7hdnqE8Hrf4MHo5ABDtb5AbX6hWbD3Y42bXP/pp.txt', - size: 4551, - hash: 'QmVwdDCY4SPGVFnNCiZnX5CtzwWDn6kAM98JXzKxE3kCmn', - type: 'file' } - ]) - done() - }) - }) - - it('should correctly handle a non existing hash', (done) => { - ipfs.ls('surelynotavalidhashheh?', (err, res) => { - expect(err).to.exist() - expect(res).to.not.exist() - done() - }) - }) - - it('should correctly handle a non exiting path', (done) => { - ipfs.ls('QmRNjDeKStKGTQXnJ2NFqeQ9oW/folder_that_isnt_there', (err, res) => { - expect(err).to.exist() - expect(res).to.not.exist() - done() - }) - }) - }) - - describe('.lsReadableStream', () => { - before(function (done) { - // TODO: https://github.com/ipfs/js-ipfs-api/issues/339 - if (!isNode) { this.skip() } - - const content = (name) => ({ - path: `test-folder/${name}`, - content: directory.files[name] - }) - - const emptyDir = (name) => ({ path: `test-folder/${name}` }) - - const dirs = [ - content('pp.txt'), - content('holmes.txt'), - content('jungle.txt'), - content('alice.txt'), - emptyDir('empty-folder'), - content('files/hello.txt'), - content('files/ipfs.txt'), - emptyDir('files/empty') - ] - - ipfs.files.add(dirs, (err, res) => { - expect(err).to.not.exist() - const root = res[res.length - 1] - - expect(root.path).to.equal('test-folder') - expect(root.hash).to.equal(directory.cid) - done() - }) - }) - - it('with a base58 encoded CID', function (done) { - // TODO: https://github.com/ipfs/js-ipfs-api/issues/339 - if (!isNode) { this.skip() } - - const cid = 'QmVvjDy7yF7hdnqE8Hrf4MHo5ABDtb5AbX6hWbD3Y42bXP' - const stream = ipfs.lsReadableStream(cid) - - stream.pipe(concat((files) => { - expect(files).to.eql([ - { depth: 1, - name: 'alice.txt', - path: 'QmVvjDy7yF7hdnqE8Hrf4MHo5ABDtb5AbX6hWbD3Y42bXP/alice.txt', - size: 11696, - hash: 'QmZyUEQVuRK3XV7L9Dk26pg6RVSgaYkiSTEdnT2kZZdwoi', - type: 'file' }, - { depth: 1, - name: 'empty-folder', - path: 'QmVvjDy7yF7hdnqE8Hrf4MHo5ABDtb5AbX6hWbD3Y42bXP/empty-folder', - size: 4, - hash: 'QmUNLLsPACCz1vLxQVkXqqLX5R1X345qqfHbsf67hvA3Nn', - type: 'dir' }, - { depth: 1, - name: 'files', - path: 'QmVvjDy7yF7hdnqE8Hrf4MHo5ABDtb5AbX6hWbD3Y42bXP/files', - size: 183, - hash: 'QmZ25UfTqXGz9RsEJFg7HUAuBcmfx5dQZDXQd2QEZ8Kj74', - type: 'dir' }, - { depth: 1, - name: 'holmes.txt', - path: 'QmVvjDy7yF7hdnqE8Hrf4MHo5ABDtb5AbX6hWbD3Y42bXP/holmes.txt', - size: 582072, - hash: 'QmR4nFjTu18TyANgC65ArNWp5Yaab1gPzQ4D8zp7Kx3vhr', - type: 'file' }, - { depth: 1, - name: 'jungle.txt', - path: 'QmVvjDy7yF7hdnqE8Hrf4MHo5ABDtb5AbX6hWbD3Y42bXP/jungle.txt', - size: 2305, - hash: 'QmT6orWioMiSqXXPGsUi71CKRRUmJ8YkuueV2DPV34E9y9', - type: 'file' }, - { depth: 1, - name: 'pp.txt', - path: 'QmVvjDy7yF7hdnqE8Hrf4MHo5ABDtb5AbX6hWbD3Y42bXP/pp.txt', - size: 4551, - hash: 'QmVwdDCY4SPGVFnNCiZnX5CtzwWDn6kAM98JXzKxE3kCmn', - type: 'file' } - ]) - done() - })) - }) - }) - - describe('.lsPullStream', () => { - before(function (done) { - // TODO: https://github.com/ipfs/js-ipfs-api/issues/339 - if (!isNode) { this.skip() } - - const content = (name) => ({ - path: `test-folder/${name}`, - content: directory.files[name] - }) - - const emptyDir = (name) => ({ path: `test-folder/${name}` }) - - const dirs = [ - content('pp.txt'), - content('holmes.txt'), - content('jungle.txt'), - content('alice.txt'), - emptyDir('empty-folder'), - content('files/hello.txt'), - content('files/ipfs.txt'), - emptyDir('files/empty') - ] - - ipfs.files.add(dirs, (err, res) => { - expect(err).to.not.exist() - const root = res[res.length - 1] - - expect(root.path).to.equal('test-folder') - expect(root.hash).to.equal(directory.cid) - done() - }) - }) - - it('with a base58 encoded CID', function (done) { - // TODO: https://github.com/ipfs/js-ipfs-api/issues/339 - if (!isNode) { this.skip() } - - const cid = 'QmVvjDy7yF7hdnqE8Hrf4MHo5ABDtb5AbX6hWbD3Y42bXP' - const stream = ipfs.lsPullStream(cid) - - pull( - stream, - pull.collect((err, files) => { - expect(err).to.not.exist() - - expect(files).to.eql([ - { depth: 1, - name: 'alice.txt', - path: 'QmVvjDy7yF7hdnqE8Hrf4MHo5ABDtb5AbX6hWbD3Y42bXP/alice.txt', - size: 11696, - hash: 'QmZyUEQVuRK3XV7L9Dk26pg6RVSgaYkiSTEdnT2kZZdwoi', - type: 'file' }, - { depth: 1, - name: 'empty-folder', - path: 'QmVvjDy7yF7hdnqE8Hrf4MHo5ABDtb5AbX6hWbD3Y42bXP/empty-folder', - size: 4, - hash: 'QmUNLLsPACCz1vLxQVkXqqLX5R1X345qqfHbsf67hvA3Nn', - type: 'dir' }, - { depth: 1, - name: 'files', - path: 'QmVvjDy7yF7hdnqE8Hrf4MHo5ABDtb5AbX6hWbD3Y42bXP/files', - size: 183, - hash: 'QmZ25UfTqXGz9RsEJFg7HUAuBcmfx5dQZDXQd2QEZ8Kj74', - type: 'dir' }, - { depth: 1, - name: 'holmes.txt', - path: 'QmVvjDy7yF7hdnqE8Hrf4MHo5ABDtb5AbX6hWbD3Y42bXP/holmes.txt', - size: 582072, - hash: 'QmR4nFjTu18TyANgC65ArNWp5Yaab1gPzQ4D8zp7Kx3vhr', - type: 'file' }, - { depth: 1, - name: 'jungle.txt', - path: 'QmVvjDy7yF7hdnqE8Hrf4MHo5ABDtb5AbX6hWbD3Y42bXP/jungle.txt', - size: 2305, - hash: 'QmT6orWioMiSqXXPGsUi71CKRRUmJ8YkuueV2DPV34E9y9', - type: 'file' }, - { depth: 1, - name: 'pp.txt', - path: 'QmVvjDy7yF7hdnqE8Hrf4MHo5ABDtb5AbX6hWbD3Y42bXP/pp.txt', - size: 4551, - hash: 'QmVwdDCY4SPGVFnNCiZnX5CtzwWDn6kAM98JXzKxE3kCmn', - type: 'file' } - ]) - done() - }) - ) - }) - }) - }) -} diff --git a/js/src/files/add-pull-stream.js b/js/src/files/add-pull-stream.js new file mode 100644 index 000000000..e6a3c2611 --- /dev/null +++ b/js/src/files/add-pull-stream.js @@ -0,0 +1,87 @@ +/* eslint-env mocha */ +'use strict' + +const { fixtures } = require('./utils') +const pull = require('pull-stream') +const { getDescribe, getIt, expect } = require('../utils/mocha') + +module.exports = (createCommon, options) => { + const describe = getDescribe(options) + const it = getIt(options) + const common = createCommon() + + describe('.files.addPullStream', function () { + this.timeout(40 * 1000) + + let ipfs + + before(function (done) { + // CI takes longer to instantiate the daemon, so we need to increase the + // timeout for the before step + this.timeout(60 * 1000) + + common.setup((err, factory) => { + expect(err).to.not.exist() + factory.spawnNode((err, node) => { + expect(err).to.not.exist() + ipfs = node + done() + }) + }) + }) + + after((done) => common.teardown(done)) + + it('should add pull stream of valid files and dirs', function (done) { + const content = (name) => ({ + path: `test-folder/${name}`, + content: fixtures.directory.files[name] + }) + + const emptyDir = (name) => ({ path: `test-folder/${name}` }) + + const files = [ + content('pp.txt'), + content('holmes.txt'), + content('jungle.txt'), + content('alice.txt'), + emptyDir('empty-folder'), + content('files/hello.txt'), + content('files/ipfs.txt'), + emptyDir('files/empty') + ] + + const stream = ipfs.files.addPullStream() + + pull( + pull.values(files), + stream, + pull.collect((err, filesAdded) => { + expect(err).to.not.exist() + + filesAdded.forEach((file) => { + if (file.path === 'test-folder') { + expect(file.hash).to.equal(fixtures.directory.cid) + done() + } + }) + }) + ) + }) + + it('should add with object chunks and pull stream content', (done) => { + const expectedCid = 'QmRf22bZar3WKmojipms22PkXH1MZGmvsqzQtuSvQE3uhm' + + pull( + pull.values([{ content: pull.values([Buffer.from('test')]) }]), + ipfs.files.addPullStream(), + pull.collect((err, res) => { + if (err) return done(err) + expect(res).to.have.length(1) + expect(res[0]).to.deep.equal({ path: expectedCid, hash: expectedCid, size: 12 }) + done() + }) + ) + }) + }) +} diff --git a/js/src/files/add-readable-stream.js b/js/src/files/add-readable-stream.js new file mode 100644 index 000000000..a3279ee42 --- /dev/null +++ b/js/src/files/add-readable-stream.js @@ -0,0 +1,70 @@ +/* eslint-env mocha */ +'use strict' + +const { fixtures } = require('./utils') +const { getDescribe, getIt, expect } = require('../utils/mocha') + +module.exports = (createCommon, options) => { + const describe = getDescribe(options) + const it = getIt(options) + const common = createCommon() + + describe('.files.addReadableStream', function () { + this.timeout(40 * 1000) + + let ipfs + + before(function (done) { + // CI takes longer to instantiate the daemon, so we need to increase the + // timeout for the before step + this.timeout(60 * 1000) + + common.setup((err, factory) => { + expect(err).to.not.exist() + factory.spawnNode((err, node) => { + expect(err).to.not.exist() + ipfs = node + done() + }) + }) + }) + + after((done) => common.teardown(done)) + + it('should add readable stream of valid files and dirs', function (done) { + const content = (name) => ({ + path: `test-folder/${name}`, + content: fixtures.directory.files[name] + }) + + const emptyDir = (name) => ({ path: `test-folder/${name}` }) + + const files = [ + content('pp.txt'), + content('holmes.txt'), + content('jungle.txt'), + content('alice.txt'), + emptyDir('empty-folder'), + content('files/hello.txt'), + content('files/ipfs.txt'), + emptyDir('files/empty') + ] + + const stream = ipfs.files.addReadableStream() + + stream.on('error', (err) => { + expect(err).to.not.exist() + }) + + stream.on('data', (file) => { + if (file.path === 'test-folder') { + expect(file.hash).to.equal(fixtures.directory.cid) + done() + } + }) + + files.forEach((file) => stream.write(file)) + stream.end() + }) + }) +} diff --git a/js/src/files/add.js b/js/src/files/add.js new file mode 100644 index 000000000..56c2aed34 --- /dev/null +++ b/js/src/files/add.js @@ -0,0 +1,302 @@ +/* eslint-env mocha */ +'use strict' + +const { fixtures } = require('./utils') +const Readable = require('readable-stream').Readable +const pull = require('pull-stream') +const path = require('path') +const expectTimeout = require('../utils/expect-timeout') +const { getDescribe, getIt, expect } = require('../utils/mocha') + +module.exports = (createCommon, options) => { + const describe = getDescribe(options) + const it = getIt(options) + const common = createCommon() + + describe('.files.add', function () { + this.timeout(40 * 1000) + + let ipfs + + before(function (done) { + // CI takes longer to instantiate the daemon, so we need to increase the + // timeout for the before step + this.timeout(60 * 1000) + + common.setup((err, factory) => { + expect(err).to.not.exist() + factory.spawnNode((err, node) => { + expect(err).to.not.exist() + ipfs = node + done() + }) + }) + }) + + after((done) => common.teardown(done)) + + it('should add a Buffer', (done) => { + ipfs.files.add(fixtures.smallFile.data, (err, filesAdded) => { + expect(err).to.not.exist() + + expect(filesAdded).to.have.length(1) + const file = filesAdded[0] + expect(file.hash).to.equal(fixtures.smallFile.cid) + expect(file.path).to.equal(fixtures.smallFile.cid) + // file.size counts the overhead by IPLD nodes and unixfs protobuf + expect(file.size).greaterThan(fixtures.smallFile.data.length) + done() + }) + }) + + it('should add a Buffer (promised)', () => { + return ipfs.files.add(fixtures.smallFile.data) + .then((filesAdded) => { + const file = filesAdded[0] + expect(file.hash).to.equal(fixtures.smallFile.cid) + expect(file.path).to.equal(fixtures.smallFile.cid) + }) + }) + + it('should add a BIG Buffer', (done) => { + ipfs.files.add(fixtures.bigFile.data, (err, filesAdded) => { + expect(err).to.not.exist() + + expect(filesAdded).to.have.length(1) + const file = filesAdded[0] + expect(file.hash).to.equal(fixtures.bigFile.cid) + expect(file.path).to.equal(fixtures.bigFile.cid) + // file.size counts the overhead by IPLD nodes and unixfs protobuf + expect(file.size).greaterThan(fixtures.bigFile.data.length) + done() + }) + }) + + it('should add a BIG Buffer with progress enabled', (done) => { + let progCalled = false + let accumProgress = 0 + function handler (p) { + progCalled = true + accumProgress = p + } + + ipfs.files.add(fixtures.bigFile.data, { progress: handler }, (err, filesAdded) => { + expect(err).to.not.exist() + + expect(filesAdded).to.have.length(1) + const file = filesAdded[0] + expect(file.hash).to.equal(fixtures.bigFile.cid) + expect(file.path).to.equal(fixtures.bigFile.cid) + + expect(progCalled).to.be.true() + expect(accumProgress).to.equal(fixtures.bigFile.data.length) + done() + }) + }) + + it('should add a Buffer as tuple', (done) => { + const tuple = { path: 'testfile.txt', content: fixtures.smallFile.data } + + ipfs.files.add([ + tuple + ], (err, filesAdded) => { + expect(err).to.not.exist() + + expect(filesAdded).to.have.length(1) + const file = filesAdded[0] + expect(file.hash).to.equal(fixtures.smallFile.cid) + expect(file.path).to.equal('testfile.txt') + + done() + }) + }) + + it('should not be able to add by path', (done) => { + const validPath = path.join(process.cwd() + '/package.json') + + ipfs.files.add(validPath, (err, res) => { + expect(err).to.exist() + done() + }) + }) + + it('should add readable stream', (done) => { + const expectedCid = 'QmVv4Wz46JaZJeH5PMV4LGbRiiMKEmszPYY3g6fjGnVXBS' + + const rs = new Readable() + rs.push(Buffer.from('some data')) + rs.push(null) + + ipfs.files.add(rs, (err, filesAdded) => { + expect(err).to.not.exist() + + expect(filesAdded).to.be.length(1) + const file = filesAdded[0] + expect(file.path).to.equal(expectedCid) + expect(file.size).to.equal(17) + expect(file.hash).to.equal(expectedCid) + done() + }) + }) + + it('should add array of objects with readable stream content', (done) => { + const expectedCid = 'QmVv4Wz46JaZJeH5PMV4LGbRiiMKEmszPYY3g6fjGnVXBS' + + const rs = new Readable() + rs.push(Buffer.from('some data')) + rs.push(null) + + const tuple = { path: 'data.txt', content: rs } + + ipfs.files.add([tuple], (err, filesAdded) => { + expect(err).to.not.exist() + + expect(filesAdded).to.be.length(1) + const file = filesAdded[0] + expect(file.path).to.equal('data.txt') + expect(file.size).to.equal(17) + expect(file.hash).to.equal(expectedCid) + done() + }) + }) + + it('should add pull stream', (done) => { + const expectedCid = 'QmRf22bZar3WKmojipms22PkXH1MZGmvsqzQtuSvQE3uhm' + + ipfs.files.add(pull.values([Buffer.from('test')]), (err, res) => { + if (err) return done(err) + expect(res).to.have.length(1) + expect(res[0]).to.deep.equal({ path: expectedCid, hash: expectedCid, size: 12 }) + done() + }) + }) + + it('should add pull stream (promised)', () => { + const expectedCid = 'QmRf22bZar3WKmojipms22PkXH1MZGmvsqzQtuSvQE3uhm' + + return ipfs.files.add(pull.values([Buffer.from('test')])) + .then((res) => { + expect(res).to.have.length(1) + expect(res[0]).to.deep.equal({ path: expectedCid, hash: expectedCid, size: 12 }) + }) + }) + + it('should add array of objects with pull stream content (promised)', () => { + const expectedCid = 'QmRf22bZar3WKmojipms22PkXH1MZGmvsqzQtuSvQE3uhm' + + return ipfs.files.add([{ content: pull.values([Buffer.from('test')]) }]) + .then((res) => { + expect(res).to.have.length(1) + expect(res[0]).to.deep.equal({ path: expectedCid, hash: expectedCid, size: 12 }) + }) + }) + + it('should add a nested directory as array of tupples', function (done) { + const content = (name) => ({ + path: `test-folder/${name}`, + content: fixtures.directory.files[name] + }) + + const emptyDir = (name) => ({ path: `test-folder/${name}` }) + + const dirs = [ + content('pp.txt'), + content('holmes.txt'), + content('jungle.txt'), + content('alice.txt'), + emptyDir('empty-folder'), + content('files/hello.txt'), + content('files/ipfs.txt'), + emptyDir('files/empty') + ] + + ipfs.files.add(dirs, (err, res) => { + expect(err).to.not.exist() + const root = res[res.length - 1] + + expect(root.path).to.equal('test-folder') + expect(root.hash).to.equal(fixtures.directory.cid) + done() + }) + }) + + it('should add a nested directory as array of tupples with progress', function (done) { + const content = (name) => ({ + path: `test-folder/${name}`, + content: fixtures.directory.files[name] + }) + + const emptyDir = (name) => ({ path: `test-folder/${name}` }) + + const dirs = [ + content('pp.txt'), + content('holmes.txt'), + content('jungle.txt'), + content('alice.txt'), + emptyDir('empty-folder'), + content('files/hello.txt'), + content('files/ipfs.txt'), + emptyDir('files/empty') + ] + + const total = dirs.reduce((i, entry) => { + return i + (entry.content ? entry.content.length : 0) + }, 0) + + let progCalled = false + let accumProgress = 0 + const handler = (p) => { + progCalled = true + accumProgress += p + } + + ipfs.files.add(dirs, { progress: handler }, (err, filesAdded) => { + expect(err).to.not.exist() + const root = filesAdded[filesAdded.length - 1] + + expect(progCalled).to.be.true() + expect(accumProgress).to.be.at.least(total) + expect(root.path).to.equal('test-folder') + expect(root.hash).to.equal(fixtures.directory.cid) + done() + }) + }) + + it('should fail when passed invalid input', (done) => { + const nonValid = 'sfdasfasfs' + + ipfs.files.add(nonValid, (err, result) => { + expect(err).to.exist() + done() + }) + }) + + it('should wrap content in a directory', (done) => { + const data = { path: 'testfile.txt', content: fixtures.smallFile.data } + + ipfs.files.add(data, { wrapWithDirectory: true }, (err, filesAdded) => { + expect(err).to.not.exist() + expect(filesAdded).to.have.length(2) + const file = filesAdded[0] + const wrapped = filesAdded[1] + expect(file.hash).to.equal(fixtures.smallFile.cid) + expect(file.path).to.equal('testfile.txt') + expect(wrapped.path).to.equal('') + done() + }) + }) + + it('should add with only-hash=true (promised)', function () { + this.slow(10 * 1000) + const content = String(Math.random() + Date.now()) + + return ipfs.files.add(Buffer.from(content), { onlyHash: true }) + .then(files => { + expect(files).to.have.length(1) + + // 'ipfs.object.get()' should timeout because content wasn't actually added + return expectTimeout(ipfs.object.get(files[0].hash), 4000) + }) + }) + }) +} diff --git a/js/src/files/cat-pull-stream.js b/js/src/files/cat-pull-stream.js new file mode 100644 index 000000000..fb0e312f3 --- /dev/null +++ b/js/src/files/cat-pull-stream.js @@ -0,0 +1,70 @@ +/* eslint-env mocha */ +'use strict' + +const { fixtures } = require('./utils') +const pull = require('pull-stream') +const { getDescribe, getIt, expect } = require('../utils/mocha') + +module.exports = (createCommon, options) => { + const describe = getDescribe(options) + const it = getIt(options) + const common = createCommon() + + describe('.files.catPullStream', function () { + this.timeout(40 * 1000) + + let ipfs + + before(function (done) { + // CI takes longer to instantiate the daemon, so we need to increase the + // timeout for the before step + this.timeout(60 * 1000) + + common.setup((err, factory) => { + expect(err).to.not.exist() + factory.spawnNode((err, node) => { + expect(err).to.not.exist() + ipfs = node + done() + }) + }) + }) + + before((done) => ipfs.files.add(fixtures.smallFile.data, done)) + + after((done) => common.teardown(done)) + + it('should return a Pull Stream for a CID', (done) => { + const stream = ipfs.files.catPullStream(fixtures.smallFile.cid) + + pull( + stream, + pull.concat((err, data) => { + expect(err).to.not.exist() + expect(data.length).to.equal(fixtures.smallFile.data.length) + expect(data).to.eql(fixtures.smallFile.data.toString()) + done() + }) + ) + }) + + it('should export a chunk of a file in a Pull Stream', (done) => { + const offset = 1 + const length = 3 + + const stream = ipfs.files.catPullStream(fixtures.smallFile.cid, { + offset, + length + }) + + pull( + stream, + pull.concat((err, data) => { + expect(err).to.not.exist() + expect(data.toString()).to.equal('lz ') + done() + }) + ) + }) + }) +} diff --git a/js/src/files/cat-readable-stream.js b/js/src/files/cat-readable-stream.js new file mode 100644 index 000000000..25e49d5c8 --- /dev/null +++ b/js/src/files/cat-readable-stream.js @@ -0,0 +1,64 @@ +/* eslint-env mocha */ +'use strict' + +const { fixtures } = require('./utils') +const bl = require('bl') +const { getDescribe, getIt, expect } = require('../utils/mocha') + +module.exports = (createCommon, options) => { + const describe = getDescribe(options) + const it = getIt(options) + const common = createCommon() + + describe('.files.catReadableStream', function () { + this.timeout(40 * 1000) + + let ipfs + + before(function (done) { + // CI takes longer to instantiate the daemon, so we need to increase the + // timeout for the before step + this.timeout(60 * 1000) + + common.setup((err, factory) => { + expect(err).to.not.exist() + factory.spawnNode((err, node) => { + expect(err).to.not.exist() + ipfs = node + done() + }) + }) + }) + + before((done) => ipfs.files.add(fixtures.bigFile.data, done)) + before((done) => ipfs.files.add(fixtures.smallFile.data, done)) + + after((done) => common.teardown(done)) + + it('should return a Readable Stream for a CID', (done) => { + const stream = ipfs.files.catReadableStream(fixtures.bigFile.cid) + + stream.pipe(bl((err, data) => { + expect(err).to.not.exist() + expect(data).to.eql(fixtures.bigFile.data) + done() + })) + }) + + it('should export a chunk of a file in a Readable Stream', (done) => { + const offset = 1 + const length = 3 + + const stream = ipfs.files.catReadableStream(fixtures.smallFile.cid, { + offset, + length + }) + + stream.pipe(bl((err, data) => { + expect(err).to.not.exist() + expect(data.toString()).to.equal('lz ') + done() + })) + }) + }) +} diff --git a/js/src/files/cat.js b/js/src/files/cat.js new file mode 100644 index 000000000..1d54c5672 --- /dev/null +++ b/js/src/files/cat.js @@ -0,0 +1,174 @@ +/* eslint-env mocha */ +'use strict' + +const { fixtures } = require('./utils') +const bs58 = require('bs58') +const parallel = require('async/parallel') +const CID = require('cids') +const { getDescribe, getIt, expect } = require('../utils/mocha') + +module.exports = (createCommon, options) => { + const describe = getDescribe(options) + const it = getIt(options) + const common = createCommon() + + describe('.files.cat', function () { + this.timeout(40 * 1000) + + let ipfs + + before(function (done) { + // CI takes longer to instantiate the daemon, so we need to increase the + // timeout for the before step + this.timeout(60 * 1000) + + common.setup((err, factory) => { + expect(err).to.not.exist() + factory.spawnNode((err, node) => { + expect(err).to.not.exist() + ipfs = node + done() + }) + }) + }) + + after((done) => common.teardown(done)) + + before((done) => { + parallel([ + (cb) => ipfs.files.add(fixtures.smallFile.data, cb), + (cb) => ipfs.files.add(fixtures.bigFile.data, cb) + ], done) + }) + + it('should cat with a base58 string encoded multihash', (done) => { + ipfs.files.cat(fixtures.smallFile.cid, (err, data) => { + expect(err).to.not.exist() + expect(data.toString()).to.contain('Plz add me!') + done() + }) + }) + + it('should cat with a base58 string encoded multihash (promised)', () => { + return ipfs.files.cat(fixtures.smallFile.cid) + .then((data) => { + expect(data.toString()).to.contain('Plz add me!') + }) + }) + + it('should cat with a Buffer multihash', (done) => { + const cid = Buffer.from(bs58.decode(fixtures.smallFile.cid)) + + ipfs.files.cat(cid, (err, data) => { + expect(err).to.not.exist() + expect(data.toString()).to.contain('Plz add me!') + done() + }) + }) + + it('should cat with a CID object', (done) => { + const cid = new CID(fixtures.smallFile.cid) + + ipfs.files.cat(cid, (err, data) => { + expect(err).to.not.exist() + expect(data.toString()).to.contain('Plz add me!') + done() + }) + }) + + it('should cat a BIG file', (done) => { + ipfs.files.cat(fixtures.bigFile.cid, (err, data) => { + expect(err).to.not.exist() + expect(data.length).to.equal(fixtures.bigFile.data.length) + expect(data).to.eql(fixtures.bigFile.data) + done() + }) + }) + + it('should cat with IPFS path', (done) => { + const ipfsPath = '/ipfs/' + fixtures.smallFile.cid + + ipfs.files.cat(ipfsPath, (err, data) => { + expect(err).to.not.exist() + expect(data.toString()).to.contain('Plz add me!') + done() + }) + }) + + it('should cat with IPFS path, nested value', (done) => { + const file = { path: 'a/testfile.txt', content: fixtures.smallFile.data } + + ipfs.files.add([file], (err, filesAdded) => { + expect(err).to.not.exist() + + const file = filesAdded.find((f) => f.path === 'a') + expect(file).to.exist() + + ipfs.files.cat(`/ipfs/${file.hash}/testfile.txt`, (err, data) => { + expect(err).to.not.exist() + expect(data.toString()).to.contain('Plz add me!') + done() + }) + }) + }) + + it('should error on invalid key (promised)', () => { + const invalidCid = 'somethingNotMultihash' + + return ipfs.files.cat(invalidCid) + .catch((err) => { + expect(err).to.exist() + + const errString = err.toString() + if (errString === 'Error: invalid ipfs ref path') { + expect(err.toString()).to.contain('Error: invalid ipfs ref path') + } + + if (errString === 'Error: Invalid Key') { + expect(err.toString()).to.contain('Error: Invalid Key') + } + }) + }) + + it('should error on unknown path (promised)', () => { + return ipfs.files.cat(fixtures.smallFile.cid + '/does-not-exist') + .catch((err) => { + expect(err).to.exist() + expect(err.message).to.oneOf([ + 'No such file', + 'no link named "does-not-exist" under Qma4hjFTnCasJ8PVp3mZbZK5g2vGDT4LByLJ7m8ciyRFZP']) + }) + }) + + it('should error on dir path (promised)', () => { + const file = { path: 'dir/testfile.txt', content: fixtures.smallFile.data } + + return ipfs.files.add([file]) + .then((filesAdded) => { + expect(filesAdded.length).to.equal(2) + const files = filesAdded.filter((file) => file.path === 'dir') + expect(files.length).to.equal(1) + const dir = files[0] + return ipfs.files.cat(dir.hash) + .catch((err) => { + expect(err).to.exist() + expect(err.message).to.contain('this dag node is a directory') + }) + }) + }) + + it('should export a chunk of a file', (done) => { + const offset = 1 + const length = 3 + + ipfs.files.cat(fixtures.smallFile.cid, { + offset, + length + }, (err, data) => { + expect(err).to.not.exist() + expect(data.toString()).to.equal('lz ') + done() + }) + }) + }) +} diff --git a/js/src/files/cp.js b/js/src/files/cp.js new file mode 100644 index 000000000..dabd764cb --- /dev/null +++ b/js/src/files/cp.js @@ -0,0 +1,78 @@ +/* eslint-env mocha */ +'use strict' + +const series = require('async/series') +const hat = require('hat') +const { getDescribe, getIt, expect } = require('../utils/mocha') + +module.exports = (createCommon, options) => { + const describe = getDescribe(options) + const it = getIt(options) + const common = createCommon() + + describe('.files.cp', function () { + this.timeout(40 * 1000) + + let ipfs + + before(function (done) { + // CI takes longer to instantiate the daemon, so we need to increase the + // timeout for the before step + this.timeout(60 * 1000) + + common.setup((err, factory) => { + expect(err).to.not.exist() + factory.spawnNode((err, node) => { + expect(err).to.not.exist() + ipfs = node + done() + }) + }) + }) + + after((done) => common.teardown(done)) + + it('should copy file, expect error', (done) => { + const testDir = `/test-${hat()}` + + ipfs.files.cp([`${testDir}/c`, `${testDir}/b`], (err) => { + expect(err).to.exist() + done() + }) + }) + + it('should copy file, expect no error', (done) => { + const testDir = `/test-${hat()}` + + series([ + (cb) => ipfs.files.mkdir(testDir, { p: true }, cb), + (cb) => ipfs.files.write(`${testDir}/a`, Buffer.from('TEST'), { create: true }, cb), + (cb) => ipfs.files.cp([`${testDir}/a`, `${testDir}/b`], cb) + ], (err) => { + expect(err).to.not.exist() + done() + }) + }) + + it('should copy dir, expect error', (done) => { + const testDir = `/test-${hat()}` + + ipfs.files.cp([`${testDir}/lv1/lv3`, `${testDir}/lv1/lv4`], (err) => { + expect(err).to.exist() + done() + }) + }) + + it('should copy dir, expect no error', (done) => { + const testDir = `/test-${hat()}` + + series([ + (cb) => ipfs.files.mkdir(`${testDir}/lv1/lv2`, { p: true }, cb), + (cb) => ipfs.files.cp([`${testDir}/lv1/lv2`, `${testDir}/lv1/lv3`], cb) + ], (err) => { + expect(err).to.not.exist() + done() + }) + }) + }) +} diff --git a/js/src/files/flush.js b/js/src/files/flush.js new file mode 100644 index 000000000..99ffb6c0d --- /dev/null +++ b/js/src/files/flush.js @@ -0,0 +1,63 @@ +/* eslint-env mocha */ +'use strict' + +const series = require('async/series') +const hat = require('hat') +const { getDescribe, getIt, expect } = require('../utils/mocha') + +module.exports = (createCommon, options) => { + const describe = getDescribe(options) + const it = getIt(options) + const common = createCommon() + + describe('.files.flush', function () { + this.timeout(40 * 1000) + + let ipfs + + before(function (done) { + // CI takes longer to instantiate the daemon, so we need to increase the + // timeout for the before step + this.timeout(60 * 1000) + + common.setup((err, factory) => { + expect(err).to.not.exist() + factory.spawnNode((err, node) => { + expect(err).to.not.exist() + ipfs = node + done() + }) + }) + }) + + after((done) => common.teardown(done)) + + it('should not flush not found file/dir, expect error', (done) => { + const testDir = `/test-${hat()}` + + ipfs.files.flush(`${testDir}/404`, (err) => { + expect(err).to.exist() + done() + }) + }) + + it('should flush root', (done) => { + ipfs.files.flush((err) => { + expect(err).to.not.exist() + done() + }) + }) + + it('should flush specific dir', (done) => { + const testDir = `/test-${hat()}` + + series([ + (cb) => ipfs.files.mkdir(testDir, { p: true }, cb), + (cb) => ipfs.files.flush(testDir, cb) + ], (err) => { + expect(err).to.not.exist() + done() + }) + }) + }) +} diff --git a/js/src/files/get-pull-stream.js b/js/src/files/get-pull-stream.js new file mode 100644 index 000000000..149dcd070 --- /dev/null +++ b/js/src/files/get-pull-stream.js @@ -0,0 +1,58 @@ +/* eslint-env mocha */ +'use strict' + +const { fixtures } = require('./utils') +const pull = require('pull-stream') +const { getDescribe, getIt, expect } = require('../utils/mocha') + +module.exports = (createCommon, options) => { + const describe = getDescribe(options) + const it = getIt(options) + const common = createCommon() + + describe('.files.getPullStream', function () { + this.timeout(40 * 1000) + + let ipfs + + before(function (done) { + // CI takes longer to instantiate the daemon, so we need to increase the + // timeout for the before step + this.timeout(60 * 1000) + + common.setup((err, factory) => { + expect(err).to.not.exist() + factory.spawnNode((err, node) => { + expect(err).to.not.exist() + ipfs = node + done() + }) + }) + }) + + before((done) => ipfs.files.add(fixtures.smallFile.data, done)) + + after((done) => common.teardown(done)) + + it('should return a Pull Stream of Pull Streams', (done) => { + const stream = ipfs.files.getPullStream(fixtures.smallFile.cid) + + pull( + stream, + pull.collect((err, files) => { + expect(err).to.not.exist() + expect(files).to.be.length(1) + expect(files[0].path).to.eql(fixtures.smallFile.cid) + pull( + files[0].content, + pull.concat((err, data) => { + expect(err).to.not.exist() + expect(data.toString()).to.contain('Plz add me!') + done() + }) + ) + }) + ) + }) + }) +} diff --git a/js/src/files/get-readable-stream.js b/js/src/files/get-readable-stream.js new file mode 100644 index 000000000..504586c90 --- /dev/null +++ b/js/src/files/get-readable-stream.js @@ -0,0 +1,55 @@ +/* eslint-env mocha */ +'use strict' + +const { fixtures } = require('./utils') +const concat = require('concat-stream') +const through = require('through2') +const { getDescribe, getIt, expect } = require('../utils/mocha') + +module.exports = (createCommon, options) => { + const describe = getDescribe(options) + const it = getIt(options) + const common = createCommon() + + describe('.files.getReadableStream', function () { + this.timeout(40 * 1000) + + let ipfs + + before(function (done) { + // CI takes longer to instantiate the daemon, so we need to increase the + // timeout for the before step + this.timeout(60 * 1000) + + common.setup((err, factory) => { + expect(err).to.not.exist() + factory.spawnNode((err, node) => { + expect(err).to.not.exist() + ipfs = node + done() + }) + }) + }) + + before((done) => ipfs.files.add(fixtures.smallFile.data, done)) + + after((done) => common.teardown(done)) + + it('should return a Readable Stream of Readable Streams', (done) => { + const stream = ipfs.files.getReadableStream(fixtures.smallFile.cid) + + let files = [] + stream.pipe(through.obj((file, enc, next) => { + file.content.pipe(concat((content) => { + files.push({ path: file.path, content: content }) + next() + })) + }, () => { + expect(files).to.be.length(1) + expect(files[0].path).to.eql(fixtures.smallFile.cid) + expect(files[0].content.toString()).to.contain('Plz add me!') + done() + })) + }) + }) +} diff --git a/js/src/files/get.js b/js/src/files/get.js new file mode 100644 index 000000000..ee58fd7c0 --- /dev/null +++ b/js/src/files/get.js @@ -0,0 +1,224 @@ +/* eslint-env mocha */ +'use strict' + +const { fixtures } = require('./utils') +const bs58 = require('bs58') +const parallel = require('async/parallel') +const series = require('async/series') +const { getDescribe, getIt, expect } = require('../utils/mocha') + +module.exports = (createCommon, options) => { + const describe = getDescribe(options) + const it = getIt(options) + const common = createCommon() + + describe('.files.get', function () { + this.timeout(40 * 1000) + + let ipfs + + before(function (done) { + // CI takes longer to instantiate the daemon, so we need to increase the + // timeout for the before step + this.timeout(60 * 1000) + + common.setup((err, factory) => { + expect(err).to.not.exist() + factory.spawnNode((err, node) => { + expect(err).to.not.exist() + ipfs = node + done() + }) + }) + }) + + before((done) => { + parallel([ + (cb) => ipfs.files.add(fixtures.smallFile.data, cb), + (cb) => ipfs.files.add(fixtures.bigFile.data, cb) + ], done) + }) + + after((done) => common.teardown(done)) + + it('should get with a base58 encoded multihash', (done) => { + ipfs.files.get(fixtures.smallFile.cid, (err, files) => { + expect(err).to.not.exist() + + expect(files).to.be.length(1) + expect(files[0].path).to.eql(fixtures.smallFile.cid) + expect(files[0].content.toString('utf8')).to.contain('Plz add me!') + done() + }) + }) + + it('should get with a base58 encoded multihash (promised)', () => { + return ipfs.files.get(fixtures.smallFile.cid) + .then((files) => { + expect(files).to.be.length(1) + expect(files[0].path).to.equal(fixtures.smallFile.cid) + expect(files[0].content.toString()).to.contain('Plz add me!') + }) + }) + + it('should get with a Buffer multihash', (done) => { + const cidBuf = Buffer.from(bs58.decode(fixtures.smallFile.cid)) + ipfs.files.get(cidBuf, (err, files) => { + expect(err).to.not.exist() + + expect(files).to.be.length(1) + expect(files[0].path).to.eql(fixtures.smallFile.cid) + expect(files[0].content.toString('utf8')).to.contain('Plz add me!') + done() + }) + }) + + it('should get a BIG file', (done) => { + ipfs.files.get(fixtures.bigFile.cid, (err, files) => { + expect(err).to.not.exist() + + expect(files.length).to.equal(1) + expect(files[0].path).to.equal(fixtures.bigFile.cid) + expect(files[0].content.length).to.eql(fixtures.bigFile.data.length) + expect(files[0].content).to.eql(fixtures.bigFile.data) + done() + }) + }) + + it('should get a directory', function (done) { + series([ + (cb) => { + const content = (name) => ({ + path: `test-folder/${name}`, + content: fixtures.directory.files[name] + }) + + const emptyDir = (name) => ({ path: `test-folder/${name}` }) + + const dirs = [ + content('pp.txt'), + content('holmes.txt'), + content('jungle.txt'), + content('alice.txt'), + emptyDir('empty-folder'), + content('files/hello.txt'), + content('files/ipfs.txt'), + emptyDir('files/empty') + ] + + ipfs.files.add(dirs, (err, res) => { + expect(err).to.not.exist() + const root = res[res.length - 1] + + expect(root.path).to.equal('test-folder') + expect(root.hash).to.equal(fixtures.directory.cid) + cb() + }) + }, + (cb) => { + ipfs.files.get(fixtures.directory.cid, (err, files) => { + expect(err).to.not.exist() + + files = files.sort((a, b) => { + if (a.path > b.path) return 1 + if (a.path < b.path) return -1 + return 0 + }) + + // Check paths + const paths = files.map((file) => { return file.path }) + expect(paths).to.include.members([ + 'QmVvjDy7yF7hdnqE8Hrf4MHo5ABDtb5AbX6hWbD3Y42bXP', + 'QmVvjDy7yF7hdnqE8Hrf4MHo5ABDtb5AbX6hWbD3Y42bXP/alice.txt', + 'QmVvjDy7yF7hdnqE8Hrf4MHo5ABDtb5AbX6hWbD3Y42bXP/empty-folder', + 'QmVvjDy7yF7hdnqE8Hrf4MHo5ABDtb5AbX6hWbD3Y42bXP/files', + 'QmVvjDy7yF7hdnqE8Hrf4MHo5ABDtb5AbX6hWbD3Y42bXP/files/empty', + 'QmVvjDy7yF7hdnqE8Hrf4MHo5ABDtb5AbX6hWbD3Y42bXP/files/hello.txt', + 'QmVvjDy7yF7hdnqE8Hrf4MHo5ABDtb5AbX6hWbD3Y42bXP/files/ipfs.txt', + 'QmVvjDy7yF7hdnqE8Hrf4MHo5ABDtb5AbX6hWbD3Y42bXP/holmes.txt', + 'QmVvjDy7yF7hdnqE8Hrf4MHo5ABDtb5AbX6hWbD3Y42bXP/jungle.txt', + 'QmVvjDy7yF7hdnqE8Hrf4MHo5ABDtb5AbX6hWbD3Y42bXP/pp.txt' + ]) + + // Check contents + const contents = files.map((file) => { + return file.content + ? file.content.toString() + : null + }) + + expect(contents).to.include.members([ + fixtures.directory.files['alice.txt'].toString(), + fixtures.directory.files['files/hello.txt'].toString(), + fixtures.directory.files['files/ipfs.txt'].toString(), + fixtures.directory.files['holmes.txt'].toString(), + fixtures.directory.files['jungle.txt'].toString(), + fixtures.directory.files['pp.txt'].toString() + ]) + cb() + }) + } + ], done) + }) + + it('should get with ipfs path, as object and nested value', (done) => { + const file = { + path: 'a/testfile.txt', + content: fixtures.smallFile.data + } + + ipfs.files.add(file, (err, filesAdded) => { + expect(err).to.not.exist() + + filesAdded.forEach((file) => { + if (file.path === 'a') { + ipfs.files.get(`/ipfs/${file.hash}/testfile.txt`, (err, files) => { + expect(err).to.not.exist() + expect(files).to.be.length(1) + expect(files[0].content.toString('utf8')).to.contain('Plz add me!') + done() + }) + } + }) + }) + }) + + it('should get with ipfs path, as array and nested value', (done) => { + const file = { + path: 'a/testfile.txt', + content: fixtures.smallFile.data + } + + ipfs.files.add([file], (err, filesAdded) => { + expect(err).to.not.exist() + + filesAdded.forEach((file) => { + if (file.path === 'a') { + ipfs.files.get(`/ipfs/${file.hash}/testfile.txt`, (err, files) => { + expect(err).to.not.exist() + expect(files).to.be.length(1) + expect(files[0].content.toString('utf8')).to.contain('Plz add me!') + done() + }) + } + }) + }) + }) + + it('should error on invalid key', () => { + const invalidCid = 'somethingNotMultihash' + + return ipfs.files.get(invalidCid) + .catch((err) => { + expect(err).to.exist() + const errString = err.toString() + if (errString === 'Error: invalid ipfs ref path') { + expect(err.toString()).to.contain('Error: invalid ipfs ref path') + } + if (errString === 'Error: Invalid Key') { + expect(err.toString()).to.contain('Error: Invalid Key') + } + }) + }) + }) +} diff --git a/js/src/files/index.js b/js/src/files/index.js new file mode 100644 index 000000000..11829cc14 --- /dev/null +++ b/js/src/files/index.js @@ -0,0 +1,25 @@ +'use strict' +const { createSuite } = require('../utils/suite') + +const tests = { + add: require('./add'), + addReadableStream: require('./add-readable-stream'), + addPullStream: require('./add-pull-stream'), + cat: require('./cat'), + catReadableStream: require('./cat-readable-stream'), + catPullStream: require('./cat-pull-stream'), + get: require('./get'), + getReadableStream: require('./get-readable-stream'), + getPullStream: require('./get-pull-stream'), + mkdir: require('./mkdir'), + write: require('./write'), + cp: require('./cp'), + mv: require('./mv'), + rm: require('./rm'), + stat: require('./stat'), + read: require('./read'), + ls: require('./ls'), + flush: require('./flush') +} + +module.exports = createSuite(tests) diff --git a/js/src/files/ls.js b/js/src/files/ls.js new file mode 100644 index 000000000..17618739c --- /dev/null +++ b/js/src/files/ls.js @@ -0,0 +1,95 @@ +/* eslint-env mocha */ +'use strict' + +const series = require('async/series') +const hat = require('hat') +const { getDescribe, getIt, expect } = require('../utils/mocha') + +module.exports = (createCommon, options) => { + const describe = getDescribe(options) + const it = getIt(options) + const common = createCommon() + + describe('.files.ls', function () { + this.timeout(40 * 1000) + + let ipfs + + before(function (done) { + // CI takes longer to instantiate the daemon, so we need to increase the + // timeout for the before step + this.timeout(60 * 1000) + + common.setup((err, factory) => { + expect(err).to.not.exist() + factory.spawnNode((err, node) => { + expect(err).to.not.exist() + ipfs = node + done() + }) + }) + }) + + after((done) => common.teardown(done)) + + it('should not ls not found file/dir, expect error', (done) => { + const testDir = `/test-${hat()}` + + ipfs.files.ls(`${testDir}/404`, (err, info) => { + expect(err).to.exist() + expect(info).to.not.exist() + done() + }) + }) + + it('should ls directory', (done) => { + const testDir = `/test-${hat()}` + + series([ + (cb) => ipfs.files.mkdir(`${testDir}/lv1`, { p: true }, cb), + (cb) => ipfs.files.write(`${testDir}/b`, Buffer.from('Hello, world!'), { create: true }, cb) + ], (err) => { + expect(err).to.not.exist() + + ipfs.files.ls(testDir, (err, info) => { + expect(err).to.not.exist() + expect(info).to.eql([ + { name: 'b', type: 0, size: 0, hash: '' }, + { name: 'lv1', type: 0, size: 0, hash: '' } + ]) + done() + }) + }) + }) + + it('should ls -l directory', (done) => { + const testDir = `/test-${hat()}` + + series([ + (cb) => ipfs.files.mkdir(`${testDir}/lv1`, { p: true }, cb), + (cb) => ipfs.files.write(`${testDir}/b`, Buffer.from('Hello, world!'), { create: true }, cb) + ], (err) => { + expect(err).to.not.exist() + + ipfs.files.ls(testDir, { l: true }, (err, info) => { + expect(err).to.not.exist() + expect(info).to.eql([ + { + name: 'lv1', + type: 1, + size: 0, + hash: 'QmUNLLsPACCz1vLxQVkXqqLX5R1X345qqfHbsf67hvA3Nn' + }, + { + name: 'b', + type: 0, + size: 13, + hash: 'QmcZojhwragQr5qhTeFAmELik623Z21e3jBTpJXoQ9si1T' + } + ]) + done() + }) + }) + }) + }) +} diff --git a/js/src/files/mkdir.js b/js/src/files/mkdir.js new file mode 100644 index 000000000..dc9fac8e5 --- /dev/null +++ b/js/src/files/mkdir.js @@ -0,0 +1,59 @@ +/* eslint-env mocha */ +'use strict' + +const hat = require('hat') +const { getDescribe, getIt, expect } = require('../utils/mocha') + +module.exports = (createCommon, options) => { + const describe = getDescribe(options) + const it = getIt(options) + const common = createCommon() + + describe('.files.mkdir', function () { + this.timeout(40 * 1000) + + let ipfs + + before(function (done) { + // CI takes longer to instantiate the daemon, so we need to increase the + // timeout for the before step + this.timeout(60 * 1000) + + common.setup((err, factory) => { + expect(err).to.not.exist() + factory.spawnNode((err, node) => { + expect(err).to.not.exist() + ipfs = node + done() + }) + }) + }) + + after((done) => common.teardown(done)) + + it('should make directory on root', (done) => { + const testDir = `/test-${hat()}` + + ipfs.files.mkdir(testDir, (err) => { + expect(err).to.not.exist() + done() + }) + }) + + it('should make directory and its parents', (done) => { + const testDir = `/test-${hat()}` + + ipfs.files.mkdir(`${testDir}/lv1/lv2`, { p: true }, (err) => { + expect(err).to.not.exist() + done() + }) + }) + + it('should not make already existent directory', (done) => { + ipfs.files.mkdir('/', (err) => { + expect(err).to.exist() + done() + }) + }) + }) +} diff --git a/js/src/files/mv.js b/js/src/files/mv.js new file mode 100644 index 000000000..2981bd344 --- /dev/null +++ b/js/src/files/mv.js @@ -0,0 +1,80 @@ +/* eslint-env mocha */ +'use strict' + +const series = require('async/series') +const hat = require('hat') +const { getDescribe, getIt, expect } = require('../utils/mocha') + +module.exports = (createCommon, options) => { + const describe = getDescribe(options) + const it = getIt(options) + const common = createCommon() + + describe('.files.mv', function () { + this.timeout(40 * 1000) + + let ipfs + + before(function (done) { + // CI takes longer to instantiate the daemon, so we need to increase the + // timeout for the before step + this.timeout(60 * 1000) + + common.setup((err, factory) => { + expect(err).to.not.exist() + factory.spawnNode((err, node) => { + expect(err).to.not.exist() + ipfs = node + done() + }) + }) + }) + + before((done) => { + series([ + (cb) => ipfs.files.mkdir('/test/lv1/lv2', { p: true }, cb), + (cb) => ipfs.files.write('/test/a', Buffer.from('Hello, world!'), { create: true }, cb) + ], done) + }) + + after((done) => common.teardown(done)) + + it('should not move not found file/dir, expect error', (done) => { + const testDir = `/test-${hat()}` + + ipfs.files.mv([`${testDir}/404`, `${testDir}/a`], (err) => { + expect(err).to.exist() + done() + }) + }) + + it('should move file, expect no error', (done) => { + const testDir = `/test-${hat()}` + + series([ + (cb) => ipfs.files.mkdir(`${testDir}/lv1/lv2`, { p: true }, cb), + (cb) => ipfs.files.write(`${testDir}/a`, Buffer.from('Hello, world!'), { create: true }, cb) + ], (err) => { + expect(err).to.not.exist() + + ipfs.files.mv([`${testDir}/a`, `${testDir}/c`], (err) => { + expect(err).to.not.exist() + done() + }) + }) + }) + + it('should move dir, expect no error', (done) => { + const testDir = `/test-${hat()}` + + ipfs.files.mkdir(`${testDir}/lv1/lv2`, { p: true }, (err) => { + expect(err).to.not.exist() + + ipfs.files.mv(['/test/lv1/lv2', '/test/lv1/lv4'], (err) => { + expect(err).to.not.exist() + done() + }) + }) + }) + }) +} diff --git a/js/src/files/read.js b/js/src/files/read.js new file mode 100644 index 000000000..ac1eb8ff9 --- /dev/null +++ b/js/src/files/read.js @@ -0,0 +1,62 @@ +/* eslint-env mocha */ +'use strict' + +const series = require('async/series') +const hat = require('hat') +const { getDescribe, getIt, expect } = require('../utils/mocha') + +module.exports = (createCommon, options) => { + const describe = getDescribe(options) + const it = getIt(options) + const common = createCommon() + + describe('.files.read', function () { + this.timeout(40 * 1000) + + let ipfs + + before(function (done) { + // CI takes longer to instantiate the daemon, so we need to increase the + // timeout for the before step + this.timeout(60 * 1000) + + common.setup((err, factory) => { + expect(err).to.not.exist() + factory.spawnNode((err, node) => { + expect(err).to.not.exist() + ipfs = node + done() + }) + }) + }) + + after((done) => common.teardown(done)) + + it('should not read not found, expect error', (done) => { + const testDir = `/test-${hat()}` + + ipfs.files.read(`${testDir}/404`, (err, buf) => { + expect(err).to.exist() + expect(buf).to.not.exist() + done() + }) + }) + + it('should read file', (done) => { + const testDir = `/test-${hat()}` + + series([ + (cb) => ipfs.files.mkdir(testDir, cb), + (cb) => ipfs.files.write(`${testDir}/a`, Buffer.from('Hello, world!'), { create: true }, cb) + ], (err) => { + expect(err).to.not.exist() + + ipfs.files.read(`${testDir}/a`, (err, buf) => { + expect(err).to.not.exist() + expect(buf).to.eql(Buffer.from('Hello, world!')) + done() + }) + }) + }) + }) +} diff --git a/js/src/files/rm.js b/js/src/files/rm.js new file mode 100644 index 000000000..79757faf8 --- /dev/null +++ b/js/src/files/rm.js @@ -0,0 +1,73 @@ +/* eslint-env mocha */ +'use strict' + +const series = require('async/series') +const hat = require('hat') +const { getDescribe, getIt, expect } = require('../utils/mocha') + +module.exports = (createCommon, options) => { + const describe = getDescribe(options) + const it = getIt(options) + const common = createCommon() + + describe('.files.rm', function () { + this.timeout(40 * 1000) + + let ipfs + + before(function (done) { + // CI takes longer to instantiate the daemon, so we need to increase the + // timeout for the before step + this.timeout(60 * 1000) + + common.setup((err, factory) => { + expect(err).to.not.exist() + factory.spawnNode((err, node) => { + expect(err).to.not.exist() + ipfs = node + done() + }) + }) + }) + + after((done) => common.teardown(done)) + + it('should not remove not found file/dir, expect error', (done) => { + const testDir = `/test-${hat()}` + + ipfs.files.rm(`${testDir}/a`, (err) => { + expect(err).to.exist() + done() + }) + }) + + it('should remove file, expect no error', (done) => { + const testDir = `/test-${hat()}` + + series([ + (cb) => ipfs.files.mkdir(testDir, { p: true }, cb), + (cb) => ipfs.files.write(`${testDir}/c`, Buffer.from('Hello, world!'), { create: true }, cb) + ], (err) => { + expect(err).to.not.exist() + + ipfs.files.rm(`${testDir}/c`, (err) => { + expect(err).to.not.exist() + done() + }) + }) + }) + + it('should remove dir, expect no error', (done) => { + const testDir = `/test-${hat()}` + + ipfs.files.mkdir(`${testDir}/lv1/lv2`, { p: true }, (err) => { + expect(err).to.not.exist() + + ipfs.files.rm(`${testDir}/lv1/lv2`, { recursive: true }, (err) => { + expect(err).to.not.exist() + done() + }) + }) + }) + }) +} diff --git a/js/src/files/stat.js b/js/src/files/stat.js new file mode 100644 index 000000000..4d59e9e91 --- /dev/null +++ b/js/src/files/stat.js @@ -0,0 +1,153 @@ +/* eslint-env mocha */ +'use strict' + +const series = require('async/series') +const hat = require('hat') +const { fixtures } = require('./utils') +const { getDescribe, getIt, expect } = require('../utils/mocha') + +module.exports = (createCommon, options) => { + const describe = getDescribe(options) + const it = getIt(options) + const common = createCommon() + + describe('.files.stat', function () { + this.timeout(40 * 1000) + + let ipfs + + before(function (done) { + // CI takes longer to instantiate the daemon, so we need to increase the + // timeout for the before step + this.timeout(60 * 1000) + + common.setup((err, factory) => { + expect(err).to.not.exist() + factory.spawnNode((err, node) => { + expect(err).to.not.exist() + ipfs = node + done() + }) + }) + }) + + before((done) => ipfs.files.add(fixtures.smallFile.data, done)) + + after((done) => common.teardown(done)) + + it('should not stat not found file/dir, expect error', function (done) { + const testDir = `/test-${hat()}` + + ipfs.files.stat(`${testDir}/404`, (err) => { + expect(err).to.exist() + done() + }) + }) + + it('should stat file', function (done) { + const testDir = `/test-${hat()}` + + series([ + (cb) => ipfs.files.mkdir(testDir, { p: true }, cb), + (cb) => ipfs.files.write(`${testDir}/b`, Buffer.from('Hello, world!'), { create: true }, cb) + ], (err) => { + expect(err).to.not.exist() + + ipfs.files.stat(`${testDir}/b`, (err, stat) => { + expect(err).to.not.exist() + expect(stat).to.eql({ + type: 'file', + blocks: 1, + size: 13, + hash: 'QmcZojhwragQr5qhTeFAmELik623Z21e3jBTpJXoQ9si1T', + cumulativeSize: 71, + withLocality: false, + local: undefined, + sizeLocal: undefined + }) + done() + }) + }) + }) + + it('should stat dir', function (done) { + const testDir = `/test-${hat()}` + + series([ + (cb) => ipfs.files.mkdir(testDir, { p: true }, cb), + (cb) => ipfs.files.write(`${testDir}/a`, Buffer.from('Hello, world!'), { create: true }, cb) + ], (err) => { + expect(err).to.not.exist() + + ipfs.files.stat(testDir, (err, stat) => { + expect(err).to.not.exist() + expect(stat).to.eql({ + type: 'directory', + blocks: 1, + size: 0, + hash: 'QmQGn7EvzJZRbhcwHrp4UeMeS56WsLmrey9JhfkymjzXQu', + cumulativeSize: 118, + withLocality: false, + local: undefined, + sizeLocal: undefined + }) + done() + }) + }) + }) + + // TODO enable this test when this feature gets released on go-ipfs + it.skip('should stat withLocal file', function (done) { + ipfs.files.stat('/test/b', {withLocal: true}, (err, stat) => { + expect(err).to.not.exist() + expect(stat).to.eql({ + type: 'file', + blocks: 1, + size: 13, + hash: 'QmcZojhwragQr5qhTeFAmELik623Z21e3jBTpJXoQ9si1T', + cumulativeSize: 71, + withLocality: true, + local: true, + sizeLocal: 71 + }) + done() + }) + }) + + // TODO enable this test when this feature gets released on go-ipfs + it.skip('should stat withLocal dir', function (done) { + ipfs.files.stat('/test', {withLocal: true}, (err, stat) => { + expect(err).to.not.exist() + expect(stat).to.eql({ + type: 'directory', + blocks: 2, + size: 0, + hash: 'QmVrkkNurBCeJvPRohW5JTvJG4AxGrFg7FnmsZZUS6nJto', + cumulativeSize: 216, + withLocality: true, + local: true, + sizeLocal: 216 + }) + done() + }) + }) + + // TODO: (achingbrain) - Not yet supported in js-ipfs or go-ipfs yet') + it.skip('should stat outside of mfs', function (done) { + ipfs.files.stat('/ipfs/' + fixtures.smallFile.cid, (err, stat) => { + expect(err).to.not.exist() + expect(stat).to.eql({ + type: 'file', + blocks: 0, + size: 12, + hash: 'Qma4hjFTnCasJ8PVp3mZbZK5g2vGDT4LByLJ7m8ciyRFZP', + cumulativeSize: 20, + withLocality: false, + local: undefined, + sizeLocal: undefined + }) + done() + }) + }) + }) +} diff --git a/js/src/files/utils.js b/js/src/files/utils.js new file mode 100644 index 000000000..d6dcd1ece --- /dev/null +++ b/js/src/files/utils.js @@ -0,0 +1,25 @@ +'use strict' + +const loadFixture = require('aegir/fixtures') + +exports.fixtures = Object.freeze({ + directory: Object.freeze({ + cid: 'QmVvjDy7yF7hdnqE8Hrf4MHo5ABDtb5AbX6hWbD3Y42bXP', + files: Object.freeze({ + 'pp.txt': loadFixture('js/test/fixtures/test-folder/pp.txt', 'interface-ipfs-core'), + 'holmes.txt': loadFixture('js/test/fixtures/test-folder/holmes.txt', 'interface-ipfs-core'), + 'jungle.txt': loadFixture('js/test/fixtures/test-folder/jungle.txt', 'interface-ipfs-core'), + 'alice.txt': loadFixture('js/test/fixtures/test-folder/alice.txt', 'interface-ipfs-core'), + 'files/hello.txt': loadFixture('js/test/fixtures/test-folder/files/hello.txt', 'interface-ipfs-core'), + 'files/ipfs.txt': loadFixture('js/test/fixtures/test-folder/files/ipfs.txt', 'interface-ipfs-core') + }) + }), + smallFile: Object.freeze({ + cid: 'Qma4hjFTnCasJ8PVp3mZbZK5g2vGDT4LByLJ7m8ciyRFZP', + data: loadFixture('js/test/fixtures/testfile.txt', 'interface-ipfs-core') + }), + bigFile: Object.freeze({ + cid: 'Qme79tX2bViL26vNjPsF3DP1R9rMKMvnPYJiKTTKPrXJjq', + data: loadFixture('js/test/fixtures/15mb.random', 'interface-ipfs-core') + }) +}) diff --git a/js/src/files/write.js b/js/src/files/write.js new file mode 100644 index 000000000..729d789f6 --- /dev/null +++ b/js/src/files/write.js @@ -0,0 +1,52 @@ +/* eslint-env mocha */ +'use strict' + +const hat = require('hat') +const { getDescribe, getIt, expect } = require('../utils/mocha') + +module.exports = (createCommon, options) => { + const describe = getDescribe(options) + const it = getIt(options) + const common = createCommon() + + describe('.files.write', function () { + this.timeout(40 * 1000) + + let ipfs + + before(function (done) { + // CI takes longer to instantiate the daemon, so we need to increase the + // timeout for the before step + this.timeout(60 * 1000) + + common.setup((err, factory) => { + expect(err).to.not.exist() + factory.spawnNode((err, node) => { + expect(err).to.not.exist() + ipfs = node + done() + }) + }) + }) + + after((done) => common.teardown(done)) + + it('should not write to non existent file, expect error', function (done) { + const testDir = `/test-${hat()}` + + ipfs.files.write(`${testDir}/a`, Buffer.from('Hello, world!'), (err) => { + expect(err).to.exist() + done() + }) + }) + + it('should write to non existent file with create flag, expect no error', function (done) { + const testDir = `/test-${hat()}` + + ipfs.files.write(testDir, Buffer.from('Hello, world!'), {create: true}, (err) => { + expect(err).to.not.exist() + done() + }) + }) + }) +} diff --git a/js/src/index.js b/js/src/index.js index 07904c431..9da38f20e 100644 --- a/js/src/index.js +++ b/js/src/index.js @@ -1,22 +1,22 @@ 'use strict' -exports.object = require('./object') -exports.files = require('./files') -exports.filesMFS = require('./files-mfs') -exports.config = require('./config') -exports.pin = require('./pin') -exports.generic = require('./miscellaneous') -exports.miscellaneous = require('./miscellaneous') -exports.swarm = require('./swarm') +exports.bitswap = require('./bitswap') exports.block = require('./block') -exports.dht = require('./dht') +exports.bootstrap = require('./bootstrap') +exports.config = require('./config') exports.dag = require('./dag') +exports.dht = require('./dht') +exports.files = require('./files') +exports.key = require('./key') +exports.ls = require('./ls') +exports.miscellaneous = require('./miscellaneous') +exports.object = require('./object') +exports.pin = require('./pin') exports.ping = require('./ping') exports.pubsub = require('./pubsub') -exports.key = require('./key') -exports.stats = require('./stats') exports.repo = require('./repo') -exports.bootstrap = require('./bootstrap') +exports.stats = require('./stats') +exports.swarm = require('./swarm') exports.types = require('./types') exports.util = require('./util') exports.bitswap = require('./bitswap') diff --git a/js/src/key.js b/js/src/key.js deleted file mode 100644 index 11c11993e..000000000 --- a/js/src/key.js +++ /dev/null @@ -1,187 +0,0 @@ -/* eslint-env mocha */ -/* eslint max-nested-callbacks: ["error", 8] */ - -'use strict' - -const chai = require('chai') -const dirtyChai = require('dirty-chai') -const expect = chai.expect -chai.use(dirtyChai) -const hat = require('hat') -const { spawnNodeWithId } = require('./utils/spawn') - -module.exports = (common) => { - describe('.key', () => { - const keyTypes = [ - {type: 'rsa', size: 2048} - ] - const keys = [] - let ipfs - let withGo - - before(function (done) { - // CI takes longer to instantiate the daemon, so we need to increase the - // timeout for the before step - this.timeout(60 * 1000) - - common.setup((err, factory) => { - expect(err).to.not.exist() - spawnNodeWithId(factory, (err, node) => { - expect(err).to.not.exist() - ipfs = node - withGo = node.peerId.agentVersion.startsWith('go-ipfs') - done() - }) - }) - }) - - after((done) => common.teardown(done)) - - describe('.gen', () => { - keyTypes.forEach((kt) => { - it(`creates a new ${kt.type} key`, function (done) { - this.timeout(20 * 1000) - const name = hat() - ipfs.key.gen(name, kt, (err, key) => { - expect(err).to.not.exist() - expect(key).to.exist() - expect(key).to.have.property('name', name) - expect(key).to.have.property('id') - keys.push(key) - done() - }) - }) - }) - }) - - describe('.list', () => { - let listedKeys - it('lists all the keys', (done) => { - ipfs.key.list((err, res) => { - expect(err).to.not.exist() - expect(res).to.exist() - expect(res).to.be.an('array') - expect(res.length).to.be.above(keys.length - 1) - listedKeys = res - done() - }) - }) - - it('contains the created keys', () => { - keys.forEach(ki => { - const found = listedKeys.filter(lk => ki.name === lk.name && ki.id === lk.id) - expect(found).to.have.length(1) - }) - }) - }) - - describe('.rename', () => { - let oldName - let newName - - before(() => { - oldName = keys[0].name - newName = 'x' + oldName - }) - - it('renames a key', (done) => { - ipfs.key.rename(oldName, newName, (err, res) => { - expect(err).to.not.exist() - expect(res).to.exist() - expect(res).to.have.property('was', oldName) - expect(res).to.have.property('now', newName) - expect(res).to.have.property('id', keys[0].id) - keys[0].name = newName - done() - }) - }) - - it('contains the new name', (done) => { - ipfs.key.list((err, res) => { - expect(err).to.not.exist() - const found = res.filter(k => k.name === newName) - expect(found).to.have.length(1) - done() - }) - }) - - it('does not contain the old name', (done) => { - ipfs.key.list((err, res) => { - expect(err).to.not.exist() - const found = res.filter(k => k.name === oldName) - expect(found).to.have.length(0) - done() - }) - }) - }) - - describe('.rm', () => { - let key - before(() => { - key = keys[0] - }) - - it('removes a key', function (done) { - ipfs.key.rm(key.name, (err, res) => { - expect(err).to.not.exist() - expect(res).to.exist() - expect(res).to.have.property('name', key.name) - expect(res).to.have.property('id', key.id) - done() - }) - }) - - it('does not contain the removed name', (done) => { - ipfs.key.list((err, res) => { - expect(err).to.not.exist() - const found = res.filter(k => k.name === key.name) - expect(found).to.have.length(0) - done() - }) - }) - }) - - describe('exchange', () => { - let selfPem - let passwordPem = hat() - - it('exports', function (done) { - if (withGo) { - console.log('Not supported by go-ipfs yet') - this.skip() - } - ipfs.key.export('self', passwordPem, (err, pem) => { - expect(err).to.not.exist() - expect(pem).to.exist() - selfPem = pem - done() - }) - }) - - it('imports', function (done) { - if (withGo) { - console.log('Not supported by go-ipfs yet') - this.skip() - } - ipfs.key.import('clone', selfPem, passwordPem, (err, key) => { - expect(err).to.not.exist() - expect(key).to.exist() - expect(key).to.have.property('name', 'clone') - expect(key).to.have.property('id') - done() - }) - }) - - it('removes', function (done) { - if (withGo) { - console.log('Not supported by go-ipfs yet') - this.skip() - } - ipfs.key.rm('clone', (err) => { - expect(err).to.not.exist() - done() - }) - }) - }) - }) -} diff --git a/js/src/key/export.js b/js/src/key/export.js new file mode 100644 index 000000000..557d323dc --- /dev/null +++ b/js/src/key/export.js @@ -0,0 +1,40 @@ +/* eslint-env mocha */ +'use strict' + +const hat = require('hat') +const { getDescribe, getIt, expect } = require('../utils/mocha') + +module.exports = (createCommon, options) => { + const describe = getDescribe(options) + const it = getIt(options) + const common = createCommon() + + describe('.key.export', () => { + let ipfs + + before(function (done) { + // CI takes longer to instantiate the daemon, so we need to increase the + // timeout for the before step + this.timeout(60 * 1000) + + common.setup((err, factory) => { + expect(err).to.not.exist() + factory.spawnNode((err, node) => { + expect(err).to.not.exist() + ipfs = node + done() + }) + }) + }) + + after((done) => common.teardown(done)) + + it('should export "self" key', function (done) { + ipfs.key.export('self', hat(), (err, pem) => { + expect(err).to.not.exist() + expect(pem).to.exist() + done() + }) + }) + }) +} diff --git a/js/src/key/gen.js b/js/src/key/gen.js new file mode 100644 index 000000000..355123b41 --- /dev/null +++ b/js/src/key/gen.js @@ -0,0 +1,50 @@ +/* eslint-env mocha */ +'use strict' + +const hat = require('hat') +const { getDescribe, getIt, expect } = require('../utils/mocha') + +module.exports = (createCommon, options) => { + const describe = getDescribe(options) + const it = getIt(options) + const common = createCommon() + + describe('.key.gen', () => { + const keyTypes = [ + { type: 'rsa', size: 2048 } + ] + + let ipfs + + before(function (done) { + // CI takes longer to instantiate the daemon, so we need to increase the + // timeout for the before step + this.timeout(60 * 1000) + + common.setup((err, factory) => { + expect(err).to.not.exist() + factory.spawnNode((err, node) => { + expect(err).to.not.exist() + ipfs = node + done() + }) + }) + }) + + after((done) => common.teardown(done)) + + keyTypes.forEach((kt) => { + it(`should generate a new ${kt.type} key`, function (done) { + this.timeout(20 * 1000) + const name = hat() + ipfs.key.gen(name, kt, (err, key) => { + expect(err).to.not.exist() + expect(key).to.exist() + expect(key).to.have.property('name', name) + expect(key).to.have.property('id') + done() + }) + }) + }) + }) +} diff --git a/js/src/key/import.js b/js/src/key/import.js new file mode 100644 index 000000000..60065d3e5 --- /dev/null +++ b/js/src/key/import.js @@ -0,0 +1,49 @@ +/* eslint-env mocha */ +'use strict' + +const hat = require('hat') +const { getDescribe, getIt, expect } = require('../utils/mocha') + +module.exports = (createCommon, options) => { + const describe = getDescribe(options) + const it = getIt(options) + const common = createCommon() + + describe('.key.import', () => { + let ipfs + + before(function (done) { + // CI takes longer to instantiate the daemon, so we need to increase the + // timeout for the before step + this.timeout(60 * 1000) + + common.setup((err, factory) => { + expect(err).to.not.exist() + factory.spawnNode((err, node) => { + expect(err).to.not.exist() + ipfs = node + done() + }) + }) + }) + + after((done) => common.teardown(done)) + + it('should import an exported key', (done) => { + const password = hat() + + ipfs.key.export('self', password, (err, pem) => { + expect(err).to.not.exist() + expect(pem).to.exist() + + ipfs.key.import('clone', pem, password, (err, key) => { + expect(err).to.not.exist() + expect(key).to.exist() + expect(key).to.have.property('name', 'clone') + expect(key).to.have.property('id') + done() + }) + }) + }) + }) +} diff --git a/js/src/key/index.js b/js/src/key/index.js new file mode 100644 index 000000000..cbe2aaa35 --- /dev/null +++ b/js/src/key/index.js @@ -0,0 +1,13 @@ +'use strict' +const { createSuite } = require('../utils/suite') + +const tests = { + gen: require('./gen'), + list: require('./list'), + rename: require('./rename'), + rm: require('./rm'), + export: require('./export'), + import: require('./import') +} + +module.exports = createSuite(tests) diff --git a/js/src/key/list.js b/js/src/key/list.js new file mode 100644 index 000000000..705576da4 --- /dev/null +++ b/js/src/key/list.js @@ -0,0 +1,57 @@ +/* eslint-env mocha */ +'use strict' + +const times = require('async/times') +const hat = require('hat') +const { getDescribe, getIt, expect } = require('../utils/mocha') + +module.exports = (createCommon, options) => { + const describe = getDescribe(options) + const it = getIt(options) + const common = createCommon() + + describe('.key.list', () => { + let ipfs + + before(function (done) { + // CI takes longer to instantiate the daemon, so we need to increase the + // timeout for the before step + this.timeout(60 * 1000) + + common.setup((err, factory) => { + expect(err).to.not.exist() + factory.spawnNode((err, node) => { + expect(err).to.not.exist() + ipfs = node + done() + }) + }) + }) + + after((done) => common.teardown(done)) + + it('should list all the keys', function (done) { + this.timeout(60 * 1000) + + times(3, (n, cb) => { + ipfs.key.gen(hat(), { type: 'rsa', size: 2048 }, cb) + }, (err, keys) => { + expect(err).to.not.exist() + + ipfs.key.list((err, res) => { + expect(err).to.not.exist() + expect(res).to.exist() + expect(res).to.be.an('array') + expect(res.length).to.be.above(keys.length - 1) + + keys.forEach(key => { + const found = res.find(({ id, name }) => name === key.name && id === key.id) + expect(found).to.exist() + }) + + done() + }) + }) + }) + }) +} diff --git a/js/src/key/rename.js b/js/src/key/rename.js new file mode 100644 index 000000000..f1f98c2d8 --- /dev/null +++ b/js/src/key/rename.js @@ -0,0 +1,63 @@ +/* eslint-env mocha */ +'use strict' + +const chai = require('chai') +const dirtyChai = require('dirty-chai') +const hat = require('hat') +const { getDescribe, getIt } = require('../utils/mocha') + +const expect = chai.expect +chai.use(dirtyChai) + +module.exports = (createCommon, options) => { + const describe = getDescribe(options) + const it = getIt(options) + const common = createCommon() + + describe('.key.rename', () => { + let ipfs + + before(function (done) { + // CI takes longer to instantiate the daemon, so we need to increase the + // timeout for the before step + this.timeout(60 * 1000) + + common.setup((err, factory) => { + expect(err).to.not.exist() + factory.spawnNode((err, node) => { + expect(err).to.not.exist() + ipfs = node + done() + }) + }) + }) + + after((done) => common.teardown(done)) + + it('should rename a key', function (done) { + this.timeout(30 * 1000) + + const oldName = hat() + const newName = hat() + + ipfs.key.gen(oldName, { type: 'rsa', size: 2048 }, (err, key) => { + expect(err).to.not.exist() + + ipfs.key.rename(oldName, newName, (err, res) => { + expect(err).to.not.exist() + expect(res).to.exist() + expect(res).to.have.property('was', oldName) + expect(res).to.have.property('now', newName) + expect(res).to.have.property('id', key.id) + + ipfs.key.list((err, res) => { + expect(err).to.not.exist() + expect(res.find(k => k.name === newName)).to.exist() + expect(res.find(k => k.name === oldName)).to.not.exist() + done() + }) + }) + }) + }) + }) +} diff --git a/js/src/key/rm.js b/js/src/key/rm.js new file mode 100644 index 000000000..502959687 --- /dev/null +++ b/js/src/key/rm.js @@ -0,0 +1,53 @@ +/* eslint-env mocha */ +'use strict' + +const hat = require('hat') +const { getDescribe, getIt, expect } = require('../utils/mocha') + +module.exports = (createCommon, options) => { + const describe = getDescribe(options) + const it = getIt(options) + const common = createCommon() + + describe('.key.rm', () => { + let ipfs + + before(function (done) { + // CI takes longer to instantiate the daemon, so we need to increase the + // timeout for the before step + this.timeout(60 * 1000) + + common.setup((err, factory) => { + expect(err).to.not.exist() + factory.spawnNode((err, node) => { + expect(err).to.not.exist() + ipfs = node + done() + }) + }) + }) + + after((done) => common.teardown(done)) + + it('should rm a key', function (done) { + this.timeout(30 * 1000) + + ipfs.key.gen(hat(), { type: 'rsa', size: 2048 }, (err, key) => { + expect(err).to.not.exist() + + ipfs.key.rm(key.name, (err, res) => { + expect(err).to.not.exist() + expect(res).to.exist() + expect(res).to.have.property('name', key.name) + expect(res).to.have.property('id', key.id) + + ipfs.key.list((err, res) => { + expect(err).to.not.exist() + expect(res.find(k => k.name === key.name)).to.not.exist() + done() + }) + }) + }) + }) + }) +} diff --git a/js/src/ls/index.js b/js/src/ls/index.js new file mode 100644 index 000000000..6016fd03a --- /dev/null +++ b/js/src/ls/index.js @@ -0,0 +1,10 @@ +'use strict' +const { createSuite } = require('../utils/suite') + +const tests = { + ls: require('./ls'), + lsReadableStream: require('./ls-readable-stream'), + lsPullStream: require('./ls-pull-stream') +} + +module.exports = createSuite(tests) diff --git a/js/src/ls/ls-pull-stream.js b/js/src/ls/ls-pull-stream.js new file mode 100644 index 000000000..48ea076b1 --- /dev/null +++ b/js/src/ls/ls-pull-stream.js @@ -0,0 +1,113 @@ +/* eslint-env mocha */ +'use strict' + +const { fixtures } = require('../files/utils') +const pull = require('pull-stream') +const { getDescribe, getIt, expect } = require('../utils/mocha') + +module.exports = (createCommon, options) => { + const describe = getDescribe(options) + const it = getIt(options) + const common = createCommon() + + describe('.lsPullStream', function () { + this.timeout(40 * 1000) + + let ipfs + + before(function (done) { + // CI takes longer to instantiate the daemon, so we need to increase the + // timeout for the before step + this.timeout(60 * 1000) + + common.setup((err, factory) => { + expect(err).to.not.exist() + factory.spawnNode((err, node) => { + expect(err).to.not.exist() + ipfs = node + done() + }) + }) + }) + + after((done) => common.teardown(done)) + + it('should pull stream ls with a base58 encoded CID', function (done) { + const content = (name) => ({ + path: `test-folder/${name}`, + content: fixtures.directory.files[name] + }) + + const emptyDir = (name) => ({ path: `test-folder/${name}` }) + + const dirs = [ + content('pp.txt'), + content('holmes.txt'), + content('jungle.txt'), + content('alice.txt'), + emptyDir('empty-folder'), + content('files/hello.txt'), + content('files/ipfs.txt'), + emptyDir('files/empty') + ] + + ipfs.files.add(dirs, (err, res) => { + expect(err).to.not.exist() + const root = res[res.length - 1] + + expect(root.path).to.equal('test-folder') + expect(root.hash).to.equal(fixtures.directory.cid) + + const cid = 'QmVvjDy7yF7hdnqE8Hrf4MHo5ABDtb5AbX6hWbD3Y42bXP' + const stream = ipfs.lsPullStream(cid) + + pull( + stream, + pull.collect((err, files) => { + expect(err).to.not.exist() + + expect(files).to.eql([ + { depth: 1, + name: 'alice.txt', + path: 'QmVvjDy7yF7hdnqE8Hrf4MHo5ABDtb5AbX6hWbD3Y42bXP/alice.txt', + size: 11696, + hash: 'QmZyUEQVuRK3XV7L9Dk26pg6RVSgaYkiSTEdnT2kZZdwoi', + type: 'file' }, + { depth: 1, + name: 'empty-folder', + path: 'QmVvjDy7yF7hdnqE8Hrf4MHo5ABDtb5AbX6hWbD3Y42bXP/empty-folder', + size: 4, + hash: 'QmUNLLsPACCz1vLxQVkXqqLX5R1X345qqfHbsf67hvA3Nn', + type: 'dir' }, + { depth: 1, + name: 'files', + path: 'QmVvjDy7yF7hdnqE8Hrf4MHo5ABDtb5AbX6hWbD3Y42bXP/files', + size: 183, + hash: 'QmZ25UfTqXGz9RsEJFg7HUAuBcmfx5dQZDXQd2QEZ8Kj74', + type: 'dir' }, + { depth: 1, + name: 'holmes.txt', + path: 'QmVvjDy7yF7hdnqE8Hrf4MHo5ABDtb5AbX6hWbD3Y42bXP/holmes.txt', + size: 582072, + hash: 'QmR4nFjTu18TyANgC65ArNWp5Yaab1gPzQ4D8zp7Kx3vhr', + type: 'file' }, + { depth: 1, + name: 'jungle.txt', + path: 'QmVvjDy7yF7hdnqE8Hrf4MHo5ABDtb5AbX6hWbD3Y42bXP/jungle.txt', + size: 2305, + hash: 'QmT6orWioMiSqXXPGsUi71CKRRUmJ8YkuueV2DPV34E9y9', + type: 'file' }, + { depth: 1, + name: 'pp.txt', + path: 'QmVvjDy7yF7hdnqE8Hrf4MHo5ABDtb5AbX6hWbD3Y42bXP/pp.txt', + size: 4551, + hash: 'QmVwdDCY4SPGVFnNCiZnX5CtzwWDn6kAM98JXzKxE3kCmn', + type: 'file' } + ]) + done() + }) + ) + }) + }) + }) +} diff --git a/js/src/ls/ls-readable-stream.js b/js/src/ls/ls-readable-stream.js new file mode 100644 index 000000000..2f9268f02 --- /dev/null +++ b/js/src/ls/ls-readable-stream.js @@ -0,0 +1,108 @@ +/* eslint-env mocha */ +'use strict' + +const { fixtures } = require('../files/utils') +const concat = require('concat-stream') +const { getDescribe, getIt, expect } = require('../utils/mocha') + +module.exports = (createCommon, options) => { + const describe = getDescribe(options) + const it = getIt(options) + const common = createCommon() + + describe('.lsReadableStream', function () { + this.timeout(40 * 1000) + + let ipfs + + before(function (done) { + // CI takes longer to instantiate the daemon, so we need to increase the + // timeout for the before step + this.timeout(60 * 1000) + + common.setup((err, factory) => { + expect(err).to.not.exist() + factory.spawnNode((err, node) => { + expect(err).to.not.exist() + ipfs = node + done() + }) + }) + }) + + after((done) => common.teardown(done)) + + it('should readable stream ls with a base58 encoded CID', function (done) { + const content = (name) => ({ + path: `test-folder/${name}`, + content: fixtures.directory.files[name] + }) + + const emptyDir = (name) => ({ path: `test-folder/${name}` }) + + const dirs = [ + content('pp.txt'), + content('holmes.txt'), + content('jungle.txt'), + content('alice.txt'), + emptyDir('empty-folder'), + content('files/hello.txt'), + content('files/ipfs.txt'), + emptyDir('files/empty') + ] + + ipfs.files.add(dirs, (err, res) => { + expect(err).to.not.exist() + const root = res[res.length - 1] + + expect(root.path).to.equal('test-folder') + expect(root.hash).to.equal(fixtures.directory.cid) + + const cid = 'QmVvjDy7yF7hdnqE8Hrf4MHo5ABDtb5AbX6hWbD3Y42bXP' + const stream = ipfs.lsReadableStream(cid) + + stream.pipe(concat((files) => { + expect(files).to.eql([ + { depth: 1, + name: 'alice.txt', + path: 'QmVvjDy7yF7hdnqE8Hrf4MHo5ABDtb5AbX6hWbD3Y42bXP/alice.txt', + size: 11696, + hash: 'QmZyUEQVuRK3XV7L9Dk26pg6RVSgaYkiSTEdnT2kZZdwoi', + type: 'file' }, + { depth: 1, + name: 'empty-folder', + path: 'QmVvjDy7yF7hdnqE8Hrf4MHo5ABDtb5AbX6hWbD3Y42bXP/empty-folder', + size: 4, + hash: 'QmUNLLsPACCz1vLxQVkXqqLX5R1X345qqfHbsf67hvA3Nn', + type: 'dir' }, + { depth: 1, + name: 'files', + path: 'QmVvjDy7yF7hdnqE8Hrf4MHo5ABDtb5AbX6hWbD3Y42bXP/files', + size: 183, + hash: 'QmZ25UfTqXGz9RsEJFg7HUAuBcmfx5dQZDXQd2QEZ8Kj74', + type: 'dir' }, + { depth: 1, + name: 'holmes.txt', + path: 'QmVvjDy7yF7hdnqE8Hrf4MHo5ABDtb5AbX6hWbD3Y42bXP/holmes.txt', + size: 582072, + hash: 'QmR4nFjTu18TyANgC65ArNWp5Yaab1gPzQ4D8zp7Kx3vhr', + type: 'file' }, + { depth: 1, + name: 'jungle.txt', + path: 'QmVvjDy7yF7hdnqE8Hrf4MHo5ABDtb5AbX6hWbD3Y42bXP/jungle.txt', + size: 2305, + hash: 'QmT6orWioMiSqXXPGsUi71CKRRUmJ8YkuueV2DPV34E9y9', + type: 'file' }, + { depth: 1, + name: 'pp.txt', + path: 'QmVvjDy7yF7hdnqE8Hrf4MHo5ABDtb5AbX6hWbD3Y42bXP/pp.txt', + size: 4551, + hash: 'QmVwdDCY4SPGVFnNCiZnX5CtzwWDn6kAM98JXzKxE3kCmn', + type: 'file' } + ]) + done() + })) + }) + }) + }) +} diff --git a/js/src/ls/ls.js b/js/src/ls/ls.js new file mode 100644 index 000000000..080c96bf4 --- /dev/null +++ b/js/src/ls/ls.js @@ -0,0 +1,123 @@ +/* eslint-env mocha */ +'use strict' + +const { fixtures } = require('../files/utils') +const { getDescribe, getIt, expect } = require('../utils/mocha') + +module.exports = (createCommon, options) => { + const describe = getDescribe(options) + const it = getIt(options) + const common = createCommon() + + describe('.ls', function () { + this.timeout(40 * 1000) + + let ipfs + + before(function (done) { + // CI takes longer to instantiate the daemon, so we need to increase the + // timeout for the before step + this.timeout(60 * 1000) + + common.setup((err, factory) => { + expect(err).to.not.exist() + factory.spawnNode((err, node) => { + expect(err).to.not.exist() + ipfs = node + done() + }) + }) + }) + + after((done) => common.teardown(done)) + + it('should ls with a base58 encoded CID', function (done) { + const content = (name) => ({ + path: `test-folder/${name}`, + content: fixtures.directory.files[name] + }) + + const emptyDir = (name) => ({ path: `test-folder/${name}` }) + + const dirs = [ + content('pp.txt'), + content('holmes.txt'), + content('jungle.txt'), + content('alice.txt'), + emptyDir('empty-folder'), + content('files/hello.txt'), + content('files/ipfs.txt'), + emptyDir('files/empty') + ] + + ipfs.files.add(dirs, (err, res) => { + expect(err).to.not.exist() + const root = res[res.length - 1] + + expect(root.path).to.equal('test-folder') + expect(root.hash).to.equal(fixtures.directory.cid) + + const cid = 'QmVvjDy7yF7hdnqE8Hrf4MHo5ABDtb5AbX6hWbD3Y42bXP' + ipfs.ls(cid, (err, files) => { + expect(err).to.not.exist() + + expect(files).to.eql([ + { depth: 1, + name: 'alice.txt', + path: 'QmVvjDy7yF7hdnqE8Hrf4MHo5ABDtb5AbX6hWbD3Y42bXP/alice.txt', + size: 11696, + hash: 'QmZyUEQVuRK3XV7L9Dk26pg6RVSgaYkiSTEdnT2kZZdwoi', + type: 'file' }, + { depth: 1, + name: 'empty-folder', + path: 'QmVvjDy7yF7hdnqE8Hrf4MHo5ABDtb5AbX6hWbD3Y42bXP/empty-folder', + size: 4, + hash: 'QmUNLLsPACCz1vLxQVkXqqLX5R1X345qqfHbsf67hvA3Nn', + type: 'dir' }, + { depth: 1, + name: 'files', + path: 'QmVvjDy7yF7hdnqE8Hrf4MHo5ABDtb5AbX6hWbD3Y42bXP/files', + size: 183, + hash: 'QmZ25UfTqXGz9RsEJFg7HUAuBcmfx5dQZDXQd2QEZ8Kj74', + type: 'dir' }, + { depth: 1, + name: 'holmes.txt', + path: 'QmVvjDy7yF7hdnqE8Hrf4MHo5ABDtb5AbX6hWbD3Y42bXP/holmes.txt', + size: 582072, + hash: 'QmR4nFjTu18TyANgC65ArNWp5Yaab1gPzQ4D8zp7Kx3vhr', + type: 'file' }, + { depth: 1, + name: 'jungle.txt', + path: 'QmVvjDy7yF7hdnqE8Hrf4MHo5ABDtb5AbX6hWbD3Y42bXP/jungle.txt', + size: 2305, + hash: 'QmT6orWioMiSqXXPGsUi71CKRRUmJ8YkuueV2DPV34E9y9', + type: 'file' }, + { depth: 1, + name: 'pp.txt', + path: 'QmVvjDy7yF7hdnqE8Hrf4MHo5ABDtb5AbX6hWbD3Y42bXP/pp.txt', + size: 4551, + hash: 'QmVwdDCY4SPGVFnNCiZnX5CtzwWDn6kAM98JXzKxE3kCmn', + type: 'file' } + ]) + done() + }) + }) + }) + + it('should correctly handle a non existing hash', (done) => { + ipfs.ls('surelynotavalidhashheh?', (err, res) => { + expect(err).to.exist() + expect(res).to.not.exist() + done() + }) + }) + + it('should correctly handle a non exiting path', (done) => { + ipfs.ls('QmRNjDeKStKGTQXnJ2NFqeQ9oW/folder_that_isnt_there', (err, res) => { + expect(err).to.exist() + expect(res).to.not.exist() + done() + }) + }) + }) +} diff --git a/js/src/miscellaneous.js b/js/src/miscellaneous.js deleted file mode 100644 index 7c3030435..000000000 --- a/js/src/miscellaneous.js +++ /dev/null @@ -1,100 +0,0 @@ -/* eslint-env mocha */ -/* eslint max-nested-callbacks: ["error", 8] */ - -'use strict' - -const chai = require('chai') -const dirtyChai = require('dirty-chai') -const expect = chai.expect -chai.use(dirtyChai) -const { spawnNodeWithId } = require('./utils/spawn') - -module.exports = (common) => { - describe('.miscellaneous', () => { - let ipfs - let withGo - - before(function (done) { - // CI takes longer to instantiate the daemon, so we need to increase the - // timeout for the before step - this.timeout(60 * 1000) - - common.setup((err, factory) => { - expect(err).to.not.exist() - spawnNodeWithId(factory, (err, node) => { - expect(err).to.not.exist() - ipfs = node - withGo = node.peerId.agentVersion.startsWith('go-ipfs') - done() - }) - }) - }) - - after((done) => { - common.teardown(done) - }) - - it('.id', (done) => { - ipfs.id((err, res) => { - expect(err).to.not.exist() - expect(res).to.have.a.property('id') - expect(res).to.have.a.property('publicKey') - done() - }) - }) - - it('.version', (done) => { - ipfs.version((err, result) => { - expect(err).to.not.exist() - expect(result).to.have.a.property('version') - expect(result).to.have.a.property('commit') - expect(result).to.have.a.property('repo') - done() - }) - }) - - it('.dns', function (done) { - this.timeout(20 * 1000) - - ipfs.dns('ipfs.io', (err, path) => { - expect(err).to.not.exist() - expect(path).to.exist() - done() - }) - }) - - it('.id Promises support', () => { - return ipfs.id() - .then((res) => { - expect(res).to.have.a.property('id') - expect(res).to.have.a.property('publicKey') - }) - }) - - it('.version Promises support', () => { - return ipfs.version() - .then((result) => { - expect(result).to.have.a.property('version') - expect(result).to.have.a.property('commit') - expect(result).to.have.a.property('repo') - }) - }) - - // must be last test to run - it('.stop', function (done) { - this.timeout(10 * 1000) - ipfs.stop((err) => { - // TODO: go-ipfs returns an error, https://github.com/ipfs/go-ipfs/issues/4078 - if (!withGo) { - expect(err).to.not.exist() - } - // Trying to stop an already stopped node should return an error - // as the node can't respond to requests anymore - ipfs.stop((err) => { - expect(err).to.exist() - done() - }) - }) - }) - }) -} diff --git a/js/src/miscellaneous/dns.js b/js/src/miscellaneous/dns.js new file mode 100644 index 000000000..f31e8e83e --- /dev/null +++ b/js/src/miscellaneous/dns.js @@ -0,0 +1,43 @@ +/* eslint-env mocha */ +'use strict' + +const { getDescribe, getIt, expect } = require('../utils/mocha') + +module.exports = (createCommon, options) => { + const describe = getDescribe(options) + const it = getIt(options) + const common = createCommon() + + describe('.dns', () => { + let ipfs + + before(function (done) { + // CI takes longer to instantiate the daemon, so we need to increase the + // timeout for the before step + this.timeout(60 * 1000) + + common.setup((err, factory) => { + expect(err).to.not.exist() + factory.spawnNode((err, node) => { + expect(err).to.not.exist() + ipfs = node + done() + }) + }) + }) + + after((done) => { + common.teardown(done) + }) + + it('should resolve a DNS link', function (done) { + this.timeout(20 * 1000) + + ipfs.dns('ipfs.io', (err, path) => { + expect(err).to.not.exist() + expect(path).to.exist() + done() + }) + }) + }) +} diff --git a/js/src/miscellaneous/id.js b/js/src/miscellaneous/id.js new file mode 100644 index 000000000..2a68ab76f --- /dev/null +++ b/js/src/miscellaneous/id.js @@ -0,0 +1,50 @@ +/* eslint-env mocha */ +'use strict' + +const { getDescribe, getIt, expect } = require('../utils/mocha') + +module.exports = (createCommon, options) => { + const describe = getDescribe(options) + const it = getIt(options) + const common = createCommon() + + describe('.id', () => { + let ipfs + + before(function (done) { + // CI takes longer to instantiate the daemon, so we need to increase the + // timeout for the before step + this.timeout(60 * 1000) + + common.setup((err, factory) => { + expect(err).to.not.exist() + factory.spawnNode((err, node) => { + expect(err).to.not.exist() + ipfs = node + done() + }) + }) + }) + + after((done) => { + common.teardown(done) + }) + + it('should get the node ID', (done) => { + ipfs.id((err, res) => { + expect(err).to.not.exist() + expect(res).to.have.a.property('id') + expect(res).to.have.a.property('publicKey') + done() + }) + }) + + it('should get the node ID (promised)', () => { + return ipfs.id() + .then((res) => { + expect(res).to.have.a.property('id') + expect(res).to.have.a.property('publicKey') + }) + }) + }) +} diff --git a/js/src/miscellaneous/index.js b/js/src/miscellaneous/index.js new file mode 100644 index 000000000..b3a5dc603 --- /dev/null +++ b/js/src/miscellaneous/index.js @@ -0,0 +1,11 @@ +'use strict' +const { createSuite } = require('../utils/suite') + +const tests = { + id: require('./id'), + version: require('./version'), + dns: require('./dns'), + stop: require('./stop') +} + +module.exports = createSuite(tests) diff --git a/js/src/miscellaneous/stop.js b/js/src/miscellaneous/stop.js new file mode 100644 index 000000000..fcc7eb515 --- /dev/null +++ b/js/src/miscellaneous/stop.js @@ -0,0 +1,49 @@ +/* eslint-env mocha */ +'use strict' + +const { getDescribe, getIt, expect } = require('../utils/mocha') + +module.exports = (createCommon, options) => { + const describe = getDescribe(options) + const it = getIt(options) + const common = createCommon() + + describe('.stop', () => { + let ipfs + + before(function (done) { + // CI takes longer to instantiate the daemon, so we need to increase the + // timeout for the before step + this.timeout(60 * 1000) + + common.setup((err, factory) => { + expect(err).to.not.exist() + factory.spawnNode((err, node) => { + expect(err).to.not.exist() + ipfs = node + done() + }) + }) + }) + + after((done) => { + common.teardown(done) + }) + + // must be last test to run + it('should stop the node', function (done) { + this.timeout(10 * 1000) + + ipfs.stop((err) => { + expect(err).to.not.exist() + + // Trying to stop an already stopped node should return an error + // as the node can't respond to requests anymore + ipfs.stop((err) => { + expect(err).to.exist() + done() + }) + }) + }) + }) +} diff --git a/js/src/miscellaneous/version.js b/js/src/miscellaneous/version.js new file mode 100644 index 000000000..3c8e95e92 --- /dev/null +++ b/js/src/miscellaneous/version.js @@ -0,0 +1,52 @@ +/* eslint-env mocha */ +'use strict' + +const { getDescribe, getIt, expect } = require('../utils/mocha') + +module.exports = (createCommon, options) => { + const describe = getDescribe(options) + const it = getIt(options) + const common = createCommon() + + describe('.version', () => { + let ipfs + + before(function (done) { + // CI takes longer to instantiate the daemon, so we need to increase the + // timeout for the before step + this.timeout(60 * 1000) + + common.setup((err, factory) => { + expect(err).to.not.exist() + factory.spawnNode((err, node) => { + expect(err).to.not.exist() + ipfs = node + done() + }) + }) + }) + + after((done) => { + common.teardown(done) + }) + + it('should get the node version', (done) => { + ipfs.version((err, result) => { + expect(err).to.not.exist() + expect(result).to.have.a.property('version') + expect(result).to.have.a.property('commit') + expect(result).to.have.a.property('repo') + done() + }) + }) + + it('should get the node version (promised)', () => { + return ipfs.version() + .then((result) => { + expect(result).to.have.a.property('version') + expect(result).to.have.a.property('commit') + expect(result).to.have.a.property('repo') + }) + }) + }) +} diff --git a/js/src/object.js b/js/src/object.js deleted file mode 100644 index 5bdb40f7d..000000000 --- a/js/src/object.js +++ /dev/null @@ -1,1003 +0,0 @@ -/* eslint-env mocha */ -/* eslint max-nested-callbacks: ["error", 8] */ - -'use strict' - -const chai = require('chai') -const dirtyChai = require('dirty-chai') -const expect = chai.expect -chai.use(dirtyChai) -const dagPB = require('ipld-dag-pb') -const DAGNode = dagPB.DAGNode -const bs58 = require('bs58') -const series = require('async/series') - -module.exports = (common) => { - describe('.object', function () { - this.timeout(80 * 1000) - - let ipfs - - before(function (done) { - // CI takes longer to instantiate the daemon, so we need to increase the - // timeout for the before step - this.timeout(60 * 1000) - - common.setup((err, factory) => { - expect(err).to.not.exist() - factory.spawnNode((err, node) => { - expect(err).to.not.exist() - ipfs = node - done() - }) - }) - }) - - after((done) => { - common.teardown(done) - }) - - describe('callback API', () => { - describe('.new', () => { - it('no layout', (done) => { - ipfs.object.new((err, node) => { - expect(err).to.not.exist() - const nodeJSON = node.toJSON() - expect(nodeJSON.multihash).to.equal('QmdfTbBqBPQ7VNxZEYEj14VmRuZBkqFbiwReogJgS1zR1n') - done() - }) - }) - - it('template unixfs-dir', (done) => { - ipfs.object.new('unixfs-dir', (err, node) => { - expect(err).to.not.exist() - const nodeJSON = node.toJSON() - expect(nodeJSON.multihash) - .to.equal('QmUNLLsPACCz1vLxQVkXqqLX5R1X345qqfHbsf67hvA3Nn') - done() - }) - }) - }) - - describe('.put', () => { - it('of object', (done) => { - const obj = { - Data: Buffer.from('Some data'), - Links: [] - } - - ipfs.object.put(obj, (err, node) => { - expect(err).to.not.exist() - const nodeJSON = node.toJSON() - expect(nodeJSON.data).to.eql(obj.Data) - expect(nodeJSON.links).to.eql(obj.Links) - expect(nodeJSON.multihash).to.equal('QmPb5f92FxKPYdT3QNBd1GKiL4tZUXUrzF4Hkpdr3Gf1gK') - done() - }) - }) - - it('of json encoded buffer', (done) => { - const obj = { - Data: Buffer.from('Some data'), - Links: [] - } - - const obj2 = { - Data: obj.Data.toString(), - Links: obj.Links - } - - const buf = Buffer.from(JSON.stringify(obj2)) - - ipfs.object.put(buf, { enc: 'json' }, (err, node) => { - expect(err).to.not.exist() - const nodeJSON = node.toJSON() - - expect(nodeJSON.data).to.eql(node.data) - expect(nodeJSON.multihash).to.equal('QmPb5f92FxKPYdT3QNBd1GKiL4tZUXUrzF4Hkpdr3Gf1gK') - done() - }) - }) - - it('of protobuf encoded buffer', (done) => { - let node - let serialized - - series([ - (cb) => { - DAGNode.create(Buffer.from('Some data'), (err, _node) => { - expect(err).to.not.exist() - node = _node - cb() - }) - }, - (cb) => { - dagPB.util.serialize(node, (err, _serialized) => { - expect(err).to.not.exist() - serialized = _serialized - cb() - }) - }, - (cb) => { - ipfs.object.put(serialized, { enc: 'protobuf' }, (err, storedNode) => { - expect(err).to.not.exist() - expect(node.data).to.deep.equal(node.data) - expect(node.links).to.deep.equal(node.links) - expect(node.multihash).to.eql(storedNode.multihash) - cb() - }) - } - ], done) - }) - - it('of buffer treated as Data field', (done) => { - const data = Buffer.from('Some data') - ipfs.object.put(data, (err, node) => { - expect(err).to.not.exist() - const nodeJSON = node.toJSON() - expect(data).to.deep.equal(nodeJSON.data) - expect([]).to.deep.equal(nodeJSON.links) - expect(nodeJSON.multihash).to.equal('QmPb5f92FxKPYdT3QNBd1GKiL4tZUXUrzF4Hkpdr3Gf1gK') - done() - }) - }) - - it('of DAGNode', (done) => { - DAGNode.create(Buffer.from('Some data'), (err, dNode) => { - expect(err).to.not.exist() - ipfs.object.put(dNode, (err, node) => { - expect(err).to.not.exist() - expect(dNode.data).to.deep.equal(node.data) - expect(dNode.links).to.deep.equal(node.links) - done() - }) - }) - }) - - it('fails if String is passed', (done) => { - ipfs.object.put('aaa', (err) => { - expect(err).to.exist() - done() - }) - }) - - it('DAGNode with a link', (done) => { - let node1a - let node1b - let node2 - series([ - (cb) => { - DAGNode.create(Buffer.from('Some data 1'), (err, node) => { - expect(err).to.not.exist() - node1a = node - cb() - }) - }, - (cb) => { - DAGNode.create(Buffer.from('Some data 2'), (err, node) => { - expect(err).to.not.exist() - node2 = node - cb() - }) - }, - (cb) => { - const link = node2.toJSON() - link.name = 'some-link' - DAGNode.addLink(node1a, link, (err, node) => { - expect(err).to.not.exist() - node1b = node - cb() - }) - }, - (cb) => { - ipfs.object.put(node1b, (err, node) => { - expect(err).to.not.exist() - expect(node1b.data).to.deep.equal(node.data) - expect(node1b.links.map((l) => l.toJSON())) - .to.deep.equal(node.links.map((l) => l.toJSON())) - cb() - }) - } - ], done) - }) - }) - - describe('.get', () => { - it('with multihash', (done) => { - const obj = { - Data: Buffer.from('get test object'), - Links: [] - } - - let node1 - let node2 - - series([ - (cb) => { - ipfs.object.put(obj, (err, node) => { - expect(err).to.not.exist() - node1 = node - cb() - }) - }, - (cb) => { - ipfs.object.get(node1.multihash, (err, node) => { - expect(err).to.not.exist() - node2 = node - - // because js-ipfs-api can't infer if the - // returned Data is Buffer or String - if (typeof node2.data === 'string') { - node2.data = Buffer.from(node2.data) - } - cb() - }) - }, - (cb) => { - // get object from ipfs multihash string - ipfs.object.get(node1.toJSON().multihash, (err, node) => { - expect(err).to.not.exist() - expect(node).to.exist() - cb() - }) - }, - (cb) => { - expect(node1.data).to.eql(node2.data) - expect(node1.links).to.eql(node2.links) - expect(node1.multihash).to.eql(node2.multihash) - cb() - } - ], done) - }) - - it('with multihash (+ links)', (done) => { - let node1a - let node1b - let node1c - let node2 - - series([ - (cb) => { - DAGNode.create(Buffer.from('Some data 1'), (err, node) => { - expect(err).to.not.exist() - node1a = node - cb() - }) - }, - (cb) => { - DAGNode.create(Buffer.from('Some data 2'), (err, node) => { - expect(err).to.not.exist() - node2 = node - cb() - }) - }, - (cb) => { - const link = node2.toJSON() - link.name = 'some-link' - DAGNode.addLink(node1a, link, (err, node) => { - expect(err).to.not.exist() - node1b = node - cb() - }) - }, - (cb) => { - ipfs.object.put(node1b, cb) - }, - (cb) => { - ipfs.object.get(node1b.multihash, (err, node) => { - expect(err).to.not.exist() - - // because js-ipfs-api can't infer if the - // returned Data is Buffer or String - if (typeof node.data === 'string') { - node.data = Buffer.from(node.data) - } - - node1c = node - cb() - }) - }, - (cb) => { - expect(node1a.data).to.eql(node1c.data) - expect(node1b.multihash).to.eql(node1c.multihash) - cb() - } - ], done) - }) - - it('with multihash base58 encoded', (done) => { - const obj = { - Data: Buffer.from('get test object'), - Links: [] - } - - let node1a - let node1b - - series([ - (cb) => { - ipfs.object.put(obj, (err, node) => { - expect(err).to.not.exist() - node1a = node - cb() - }) - }, - (cb) => { - ipfs.object.get(node1a.multihash, { enc: 'base58' }, (err, node) => { - expect(err).to.not.exist() - // because js-ipfs-api can't infer if the - // returned Data is Buffer or String - if (typeof node.data === 'string') { - node.data = Buffer.from(node.data) - } - node1b = node - cb() - }) - }, - (cb) => { - expect(node1a.multihash).to.eql(node1b.multihash) - expect(node1a.data).to.eql(node1b.data) - expect(node1a.links).to.eql(node1b.links) - cb() - } - ], done) - }) - - it('with multihash base58 encoded toString', (done) => { - const obj = { - Data: Buffer.from('get test object'), - Links: [] - } - - let node1a - let node1b - - series([ - (cb) => { - ipfs.object.put(obj, (err, node) => { - expect(err).to.not.exist() - node1a = node - cb() - }) - }, - (cb) => { - ipfs.object.get(bs58.encode(node1a.multihash).toString(), { enc: 'base58' }, (err, node) => { - expect(err).to.not.exist() - // because js-ipfs-api can't infer if the - // returned Data is Buffer or String - if (typeof node.data === 'string') { - node.data = Buffer.from(node.data) - } - node1b = node - cb() - }) - }, - (cb) => { - expect(node1a.multihash).to.eql(node1b.multihash) - expect(node1a.data).to.eql(node1b.data) - expect(node1a.links).to.eql(node1b.links) - cb() - } - ], done) - }) - }) - - describe('.data', () => { - it('with multihash', (done) => { - const testObj = { - Data: Buffer.from('get test object'), - Links: [] - } - - ipfs.object.put(testObj, (err, node) => { - expect(err).to.not.exist() - - ipfs.object.data(node.multihash, (err, data) => { - expect(err).to.not.exist() - - // because js-ipfs-api can't infer - // if the returned Data is Buffer or String - if (typeof data === 'string') { - data = Buffer.from(data) - } - expect(node.data).to.eql(data) - done() - }) - }) - }) - - it('with multihash base58 encoded', (done) => { - const testObj = { - Data: Buffer.from('get test object'), - Links: [] - } - - ipfs.object.put(testObj, (err, node) => { - expect(err).to.not.exist() - - ipfs.object.data(bs58.encode(node.multihash), { enc: 'base58' }, (err, data) => { - expect(err).to.not.exist() - - // because js-ipfs-api can't infer - // if the returned Data is Buffer or String - if (typeof data === 'string') { - data = Buffer.from(data) - } - expect(node.data).to.eql(data) - done() - }) - }) - }) - - it('with multihash base58 encoded toString', (done) => { - const testObj = { - Data: Buffer.from('get test object'), - Links: [] - } - - ipfs.object.put(testObj, (err, node) => { - expect(err).to.not.exist() - - ipfs.object.data(bs58.encode(node.multihash).toString(), { enc: 'base58' }, (err, data) => { - expect(err).to.not.exist() - - // because js-ipfs-api can't infer if the - // returned Data is Buffer or String - if (typeof data === 'string') { - data = Buffer.from(data) - } - expect(node.data).to.eql(data) - done() - }) - }) - }) - }) - - describe('.links', () => { - it('object.links with multihash', (done) => { - const testObj = { - Data: Buffer.from('get test object'), - Links: [] - } - - ipfs.object.put(testObj, (err, node) => { - expect(err).to.not.exist() - - ipfs.object.links(node.multihash, (err, links) => { - expect(err).to.not.exist() - expect(node.links).to.deep.equal(links) - done() - }) - }) - }) - - it('with multihash (+ links)', (done) => { - let node1a - let node1b - let node2 - - series([ - (cb) => { - DAGNode.create(Buffer.from('Some data 1'), (err, node) => { - expect(err).to.not.exist() - node1a = node - cb() - }) - }, - (cb) => { - DAGNode.create(Buffer.from('Some data 2'), (err, node) => { - expect(err).to.not.exist() - node2 = node - cb() - }) - }, - (cb) => { - const link = node2.toJSON() - link.name = 'some-link' - - DAGNode.addLink(node1a, link, (err, node) => { - expect(err).to.not.exist() - node1b = node - cb() - }) - }, - (cb) => { - ipfs.object.put(node1b, cb) - }, - (cb) => { - ipfs.object.links(node1b.multihash, (err, links) => { - expect(err).to.not.exist() - expect(node1b.links[0].toJSON()).to.eql(links[0].toJSON()) - cb() - }) - } - ], done) - }) - - it('with multihash base58 encoded', (done) => { - const testObj = { - Data: Buffer.from('get test object'), - Links: [] - } - - ipfs.object.put(testObj, (err, node) => { - expect(err).to.not.exist() - - ipfs.object.links(bs58.encode(node.multihash), { enc: 'base58' }, (err, links) => { - expect(err).to.not.exist() - expect(node.links).to.deep.equal(links) - done() - }) - }) - }) - - it('with multihash base58 encoded toString', (done) => { - const testObj = { - Data: Buffer.from('get test object'), - Links: [] - } - - ipfs.object.put(testObj, (err, node) => { - expect(err).to.not.exist() - ipfs.object.links(bs58.encode(node.multihash), { enc: 'base58' }, (err, links) => { - expect(err).to.not.exist() - expect(node.links).to.deep.equal(links) - done() - }) - }) - }) - }) - - describe('.stat', () => { - it('with multihash', (done) => { - const testObj = { - Data: Buffer.from('get test object'), - Links: [] - } - - ipfs.object.put(testObj, (err, node) => { - expect(err).to.not.exist() - - ipfs.object.stat(node.multihash, (err, stats) => { - expect(err).to.not.exist() - const expected = { - Hash: 'QmNggDXca24S6cMPEYHZjeuc4QRmofkRrAEqVL3Ms2sdJZ', - NumLinks: 0, - BlockSize: 17, - LinksSize: 2, - DataSize: 15, - CumulativeSize: 17 - } - expect(expected).to.deep.equal(stats) - done() - }) - }) - }) - - it('with multihash (+ Links)', (done) => { - let node1a - let node1b - let node2 - - series([ - (cb) => { - DAGNode.create(Buffer.from('Some data 1'), (err, node) => { - expect(err).to.not.exist() - node1a = node - cb() - }) - }, - (cb) => { - DAGNode.create(Buffer.from('Some data 2'), (err, node) => { - expect(err).to.not.exist() - node2 = node - cb() - }) - }, - (cb) => { - const link = node2.toJSON() - link.name = 'some-link' - - DAGNode.addLink(node1a, link, (err, node) => { - expect(err).to.not.exist() - node1b = node - cb() - }) - }, - (cb) => { - ipfs.object.put(node1b, cb) - }, - (cb) => { - ipfs.object.stat(node1b.multihash, (err, stats) => { - expect(err).to.not.exist() - const expected = { - Hash: 'QmPR7W4kaADkAo4GKEVVPQN81EDUFCHJtqejQZ5dEG7pBC', - NumLinks: 1, - BlockSize: 64, - LinksSize: 53, - DataSize: 11, - CumulativeSize: 77 - } - expect(expected).to.eql(stats) - cb() - }) - } - ], done) - }) - - it('with multihash base58 encoded', (done) => { - const testObj = { - Data: Buffer.from('get test object'), - Links: [] - } - - ipfs.object.put(testObj, (err, node) => { - expect(err).to.not.exist() - - ipfs.object.stat(bs58.encode(node.multihash), { enc: 'base58' }, (err, stats) => { - expect(err).to.not.exist() - const expected = { - Hash: 'QmNggDXca24S6cMPEYHZjeuc4QRmofkRrAEqVL3Ms2sdJZ', - NumLinks: 0, - BlockSize: 17, - LinksSize: 2, - DataSize: 15, - CumulativeSize: 17 - } - expect(expected).to.deep.equal(stats) - done() - }) - }) - }) - - it('with multihash base58 encoded toString', (done) => { - const testObj = { - Data: Buffer.from('get test object'), - Links: [] - } - - ipfs.object.put(testObj, (err, node) => { - expect(err).to.not.exist() - - ipfs.object.stat(bs58.encode(node.multihash).toString(), { enc: 'base58' }, (err, stats) => { - expect(err).to.not.exist() - const expected = { - Hash: 'QmNggDXca24S6cMPEYHZjeuc4QRmofkRrAEqVL3Ms2sdJZ', - NumLinks: 0, - BlockSize: 17, - LinksSize: 2, - DataSize: 15, - CumulativeSize: 17 - } - expect(expected).to.deep.equal(stats) - done() - }) - }) - }) - }) - - describe('.patch', () => { - let testNodeMultihash - let testNodeWithLinkMultihash - let testLink - - const obj = { - Data: Buffer.from('patch test object'), - Links: [] - } - - before((done) => { - ipfs.object.put(obj, (err, node) => { - expect(err).to.not.exist() - testNodeMultihash = node.multihash - done() - }) - }) - - it('.addLink', (done) => { - let node1a - let node1b - let node2 - - series([ - (cb) => { - DAGNode.create(obj.Data, obj.Links, (err, node) => { - expect(err).to.not.exist() - node1a = node - cb() - }) - }, - (cb) => { - DAGNode.create(Buffer.from('some other node'), (err, node) => { - expect(err).to.not.exist() - node2 = node - cb() - }) - }, - (cb) => { - // note: we need to put the linked obj, otherwise IPFS won't - // timeout. Reason: it needs the node to get its size - ipfs.object.put(node2, cb) - }, - (cb) => { - const link = node2.toJSON() - link.name = 'link-to-node' - DAGNode.addLink(node1a, link, (err, node) => { - expect(err).to.not.exist() - node1b = node - cb() - }) - }, - (cb) => { - ipfs.object.patch.addLink(testNodeMultihash, node1b.links[0], (err, node) => { - expect(err).to.not.exist() - expect(node1b.multihash).to.eql(node.multihash) - testNodeWithLinkMultihash = node.multihash - testLink = node1b.links[0] - cb() - }) - } - /* TODO: revisit this assertions. - (cb) => { - // note: make sure we can link js plain objects - const content = Buffer.from(JSON.stringify({ - title: 'serialized object' - }, null, 0)) - ipfs.add(content, (err, result) => { - expect(err).to.not.exist() - expect(result).to.exist() - expect(result).to.have.lengthOf(1) - const object = result.pop() - node3 = { - name: object.hash, - multihash: object.hash, - size: object.size - } - cb() - }) - }, - (cb) => { - ipfs.object.patch.addLink(testNodeWithLinkMultihash, node3, (err, node) => { - expect(err).to.not.exist() - expect(node).to.exist() - testNodeWithLinkMultihash = node.multihash - testLinkPlainObject = node3 - cb() - }) - } - */ - ], done) - }) - - it('.rmLink', (done) => { - series([ - (cb) => { - ipfs.object.patch.rmLink(testNodeWithLinkMultihash, testLink, (err, node) => { - expect(err).to.not.exist() - expect(node.multihash).to.not.deep.equal(testNodeWithLinkMultihash) - testNodeWithLinkMultihash = node.multihash - - cb() - }) - } - /* TODO: revisit this assertions. - (cb) => { - ipfs.object.patch.rmLink(testNodeWithLinkMultihash, testLinkPlainObject, (err, node) => { - expect(err).to.not.exist() - expect(node.multihash).to.not.deep.equal(testNodeWithLinkMultihash) - cb() - }) - } - */ - ], done) - }) - - it('.appendData', (done) => { - ipfs.object.patch.appendData(testNodeMultihash, Buffer.from('append'), (err, node) => { - expect(err).to.not.exist() - expect(node.multihash).to.not.deep.equal(testNodeMultihash) - done() - }) - }) - - it('.setData', (done) => { - ipfs.object.patch.appendData(testNodeMultihash, Buffer.from('set'), (err, node) => { - expect(err).to.not.exist() - expect(node.multihash).to.not.deep.equal(testNodeMultihash) - done() - }) - }) - }) - }) - - describe('promise API', () => { - it('object.new', () => { - return ipfs.object.new() - .then((node) => { - const nodeJSON = node.toJSON() - expect(nodeJSON.multihash).to.equal('QmdfTbBqBPQ7VNxZEYEj14VmRuZBkqFbiwReogJgS1zR1n') - }) - }) - - it('object.put', () => { - const obj = { - Data: Buffer.from('Some data'), - Links: [] - } - - return ipfs.object.put(obj) - .then((node) => { - const nodeJSON = node.toJSON() - expect(obj.Data).to.deep.equal(nodeJSON.data) - expect(obj.Links).to.deep.equal(nodeJSON.links) - expect(nodeJSON.multihash).to.equal('QmPb5f92FxKPYdT3QNBd1GKiL4tZUXUrzF4Hkpdr3Gf1gK') - }) - }) - - it('object.get', () => { - const testObj = { - Data: Buffer.from('get test object'), - Links: [] - } - - return ipfs.object.put(testObj).then((node1) => { - return ipfs.object.get(node1.multihash).then((node2) => { - // because js-ipfs-api can't infer if the - // returned Data is Buffer or String - if (typeof node2.data === 'string') { - node2.data = Buffer.from(node2.data) - } - - expect(node1.data).to.deep.equal(node2.data) - expect(node1.links).to.deep.equal(node2.links) - }) - }) - }) - - it('object.get multihash string', () => { - return ipfs.object.get('QmPb5f92FxKPYdT3QNBd1GKiL4tZUXUrzF4Hkpdr3Gf1gK').then((node) => { - expect(node.data).to.exist() - }) - }) - - it('object.data', () => { - const testObj = { - Data: Buffer.from('get test object'), - Links: [] - } - - return ipfs.object.put(testObj).then((node) => { - return ipfs.object.data(node.multihash).then((data) => { - // because js-ipfs-api can't infer - // if the returned Data is Buffer or String - if (typeof data === 'string') { - data = Buffer.from(data) - } - expect(node.data).to.deep.equal(data) - }) - }) - }) - - it('object.stat', () => { - const testObj = { - Data: Buffer.from('get test object'), - Links: [] - } - - return ipfs.object.put(testObj, (err, node) => { - expect(err).to.not.exist() - - return ipfs.object.stat('QmNggDXca24S6cMPEYHZjeuc4QRmofkRrAEqVL3Ms2sdJZ', {enc: 'base58'}) - .then((stats) => { - const expected = { - Hash: 'QmNggDXca24S6cMPEYHZjeuc4QRmofkRrAEqVL3Ms2sdJZ', - NumLinks: 0, - BlockSize: 17, - LinksSize: 2, - DataSize: 15, - CumulativeSize: 17 - } - expect(expected).to.deep.equal(stats) - }) - }) - }) - - it('object.links', () => { - const testObj = { - Data: Buffer.from('get test object'), - Links: [] - } - - return ipfs.object.put(testObj).then((node) => { - return ipfs.object.links(node.multihash).then((links) => { - expect(node.links).to.eql(links) - }) - }) - }) - - describe('object.patch', () => { - let testNodeMultihash - let testNodeWithLinkMultihash - let testLink - - const obj = { - Data: Buffer.from('patch test object'), - Links: [] - } - - before(() => { - return ipfs.object.put(obj) - .then((node) => { - testNodeMultihash = node.multihash - }) - }) - - it('.addLink', () => { - let node1a - let node1b - let node2 - return new Promise((resolve, reject) => { - DAGNode.create(obj.Data, obj.Links, function (err, node) { - if (err) { - return reject(err) - } - return resolve(node) - }) - }).then((node) => { - node1a = node - return new Promise((resolve, reject) => { - DAGNode.create(Buffer.from('some other node'), function (err, node) { - if (err) { - return reject(err) - } - return resolve(node) - }) - }).then((node1) => { - node2 = node1 - return ipfs.object.put(node2) - }) - }).then(() => { - const link = node2.toJSON() - link.name = 'link-to-node' - return new Promise((resolve, reject) => { - DAGNode.addLink(node1a, link, function (err, node) { - if (err) { - return reject(err) - } - return resolve(node) - }) - }).then((node) => { - node1b = node - return ipfs.object.patch.addLink(testNodeMultihash, node1b.links[0]) - }) - }).then((node) => { - expect(node1b.multihash).to.eql(node.multihash) - testNodeWithLinkMultihash = node.multihash - testLink = node1b.links[0] - }) - }) - - it('.rmLink', () => { - return ipfs.object.patch.rmLink(testNodeWithLinkMultihash, testLink) - .then((node) => { - expect(node.multihash).to.not.deep.equal(testNodeWithLinkMultihash) - }) - }) - - it('.appendData', () => { - return ipfs.object.patch.appendData(testNodeMultihash, Buffer.from('append')) - .then((node) => { - expect(node.multihash).to.not.deep.equal(testNodeMultihash) - }) - }) - - it('.setData', () => { - return ipfs.object.patch.appendData(testNodeMultihash, Buffer.from('set')) - .then((node) => { - expect(node.multihash).to.not.deep.equal(testNodeMultihash) - }) - }) - }) - }) - }) -} diff --git a/js/src/object/data.js b/js/src/object/data.js new file mode 100644 index 000000000..ddc94ee80 --- /dev/null +++ b/js/src/object/data.js @@ -0,0 +1,122 @@ +/* eslint-env mocha */ +'use strict' + +const bs58 = require('bs58') +const hat = require('hat') +const { getDescribe, getIt, expect } = require('../utils/mocha') + +module.exports = (createCommon, options) => { + const describe = getDescribe(options) + const it = getIt(options) + const common = createCommon() + + describe('.object.data', function () { + this.timeout(80 * 1000) + + let ipfs + + before(function (done) { + // CI takes longer to instantiate the daemon, so we need to increase the + // timeout for the before step + this.timeout(60 * 1000) + + common.setup((err, factory) => { + expect(err).to.not.exist() + factory.spawnNode((err, node) => { + expect(err).to.not.exist() + ipfs = node + done() + }) + }) + }) + + after((done) => common.teardown(done)) + + it('should get data by multihash', (done) => { + const testObj = { + Data: Buffer.from(hat()), + Links: [] + } + + ipfs.object.put(testObj, (err, node) => { + expect(err).to.not.exist() + + ipfs.object.data(node.multihash, (err, data) => { + expect(err).to.not.exist() + + // because js-ipfs-api can't infer + // if the returned Data is Buffer or String + if (typeof data === 'string') { + data = Buffer.from(data) + } + expect(node.data).to.eql(data) + done() + }) + }) + }) + + it('should get data by multihash (promised)', () => { + const testObj = { + Data: Buffer.from(hat()), + Links: [] + } + + return ipfs.object.put(testObj).then((node) => { + return ipfs.object.data(node.multihash).then((data) => { + // because js-ipfs-api can't infer + // if the returned Data is Buffer or String + if (typeof data === 'string') { + data = Buffer.from(data) + } + expect(node.data).to.deep.equal(data) + }) + }) + }) + + it('should get data by base58 encoded multihash', (done) => { + const testObj = { + Data: Buffer.from(hat()), + Links: [] + } + + ipfs.object.put(testObj, (err, node) => { + expect(err).to.not.exist() + + ipfs.object.data(bs58.encode(node.multihash), { enc: 'base58' }, (err, data) => { + expect(err).to.not.exist() + + // because js-ipfs-api can't infer + // if the returned Data is Buffer or String + if (typeof data === 'string') { + data = Buffer.from(data) + } + expect(node.data).to.eql(data) + done() + }) + }) + }) + + it('should get data by base58 encoded multihash string', (done) => { + const testObj = { + Data: Buffer.from(hat()), + Links: [] + } + + ipfs.object.put(testObj, (err, node) => { + expect(err).to.not.exist() + + ipfs.object.data(bs58.encode(node.multihash).toString(), { enc: 'base58' }, (err, data) => { + expect(err).to.not.exist() + + // because js-ipfs-api can't infer if the + // returned Data is Buffer or String + if (typeof data === 'string') { + data = Buffer.from(data) + } + expect(node.data).to.eql(data) + done() + }) + }) + }) + }) +} diff --git a/js/src/object/get.js b/js/src/object/get.js new file mode 100644 index 000000000..8a960aaa9 --- /dev/null +++ b/js/src/object/get.js @@ -0,0 +1,289 @@ +/* eslint-env mocha */ +'use strict' + +const dagPB = require('ipld-dag-pb') +const DAGNode = dagPB.DAGNode +const bs58 = require('bs58') +const series = require('async/series') +const hat = require('hat') +const { getDescribe, getIt, expect } = require('../utils/mocha') + +module.exports = (createCommon, options) => { + const describe = getDescribe(options) + const it = getIt(options) + const common = createCommon() + + describe('.object.get', function () { + this.timeout(80 * 1000) + + let ipfs + + before(function (done) { + // CI takes longer to instantiate the daemon, so we need to increase the + // timeout for the before step + this.timeout(60 * 1000) + + common.setup((err, factory) => { + expect(err).to.not.exist() + factory.spawnNode((err, node) => { + expect(err).to.not.exist() + ipfs = node + done() + }) + }) + }) + + after((done) => common.teardown(done)) + + it('should get object by multihash', (done) => { + const obj = { + Data: Buffer.from(hat()), + Links: [] + } + + let node1 + let node2 + + series([ + (cb) => { + ipfs.object.put(obj, (err, node) => { + expect(err).to.not.exist() + node1 = node + cb() + }) + }, + (cb) => { + ipfs.object.get(node1.multihash, (err, node) => { + expect(err).to.not.exist() + node2 = node + + // because js-ipfs-api can't infer if the + // returned Data is Buffer or String + if (typeof node2.data === 'string') { + node2.data = Buffer.from(node2.data) + } + cb() + }) + }, + (cb) => { + expect(node1.data).to.eql(node2.data) + expect(node1.links).to.eql(node2.links) + expect(node1.multihash).to.eql(node2.multihash) + cb() + } + ], done) + }) + + it('should get object by multihash (promised)', () => { + const testObj = { + Data: Buffer.from(hat()), + Links: [] + } + + return ipfs.object.put(testObj).then((node1) => { + return ipfs.object.get(node1.multihash).then((node2) => { + // because js-ipfs-api can't infer if the + // returned Data is Buffer or String + if (typeof node2.data === 'string') { + node2.data = Buffer.from(node2.data) + } + + expect(node1.data).to.deep.equal(node2.data) + expect(node1.links).to.deep.equal(node2.links) + }) + }) + }) + + it('should get object by multihash string', (done) => { + const obj = { + Data: Buffer.from(hat()), + Links: [] + } + + let node1 + let node2 + + series([ + (cb) => { + ipfs.object.put(obj, (err, node) => { + expect(err).to.not.exist() + node1 = node + cb() + }) + }, + (cb) => { + // get object from ipfs multihash string + ipfs.object.get(node1.toJSON().multihash, (err, node) => { + expect(err).to.not.exist() + // because js-ipfs-api can't infer if the + // returned Data is Buffer or String + if (typeof node.data === 'string') { + node.data = Buffer.from(node.data) + } + node2 = node + cb() + }) + }, + (cb) => { + expect(node1.data).to.eql(node2.data) + expect(node1.links).to.eql(node2.links) + expect(node1.multihash).to.eql(node2.multihash) + cb() + } + ], done) + }) + + it('should get object by multihash string (promised)', () => { + const obj = { + Data: Buffer.from(hat()), + Links: [] + } + + return ipfs.object.put(obj) + .then((node1) => { + return ipfs.object.get(node1.toJSON().multihash) + .then((node2) => { + // because js-ipfs-api can't infer if the + // returned Data is Buffer or String + if (typeof node2.data === 'string') { + node2.data = Buffer.from(node2.data) + } + + expect(node1.data).to.deep.equal(node2.data) + expect(node1.links).to.deep.equal(node2.links) + }) + }) + }) + + it('should get object with links by multihash string', (done) => { + let node1a + let node1b + let node1c + let node2 + + series([ + (cb) => { + DAGNode.create(Buffer.from('Some data 1'), (err, node) => { + expect(err).to.not.exist() + node1a = node + cb() + }) + }, + (cb) => { + DAGNode.create(Buffer.from('Some data 2'), (err, node) => { + expect(err).to.not.exist() + node2 = node + cb() + }) + }, + (cb) => { + const link = node2.toJSON() + link.name = 'some-link' + DAGNode.addLink(node1a, link, (err, node) => { + expect(err).to.not.exist() + node1b = node + cb() + }) + }, + (cb) => { + ipfs.object.put(node1b, cb) + }, + (cb) => { + ipfs.object.get(node1b.multihash, (err, node) => { + expect(err).to.not.exist() + + // because js-ipfs-api can't infer if the + // returned Data is Buffer or String + if (typeof node.data === 'string') { + node.data = Buffer.from(node.data) + } + + node1c = node + cb() + }) + }, + (cb) => { + expect(node1a.data).to.eql(node1c.data) + expect(node1b.multihash).to.eql(node1c.multihash) + cb() + } + ], done) + }) + + it('should get object by base58 encoded multihash', (done) => { + const obj = { + Data: Buffer.from(hat()), + Links: [] + } + + let node1a + let node1b + + series([ + (cb) => { + ipfs.object.put(obj, (err, node) => { + expect(err).to.not.exist() + node1a = node + cb() + }) + }, + (cb) => { + ipfs.object.get(node1a.multihash, { enc: 'base58' }, (err, node) => { + expect(err).to.not.exist() + // because js-ipfs-api can't infer if the + // returned Data is Buffer or String + if (typeof node.data === 'string') { + node.data = Buffer.from(node.data) + } + node1b = node + cb() + }) + }, + (cb) => { + expect(node1a.multihash).to.eql(node1b.multihash) + expect(node1a.data).to.eql(node1b.data) + expect(node1a.links).to.eql(node1b.links) + cb() + } + ], done) + }) + + it('should get object by base58 encoded multihash string', (done) => { + const obj = { + Data: Buffer.from(hat()), + Links: [] + } + + let node1a + let node1b + + series([ + (cb) => { + ipfs.object.put(obj, (err, node) => { + expect(err).to.not.exist() + node1a = node + cb() + }) + }, + (cb) => { + ipfs.object.get(bs58.encode(node1a.multihash).toString(), { enc: 'base58' }, (err, node) => { + expect(err).to.not.exist() + // because js-ipfs-api can't infer if the + // returned Data is Buffer or String + if (typeof node.data === 'string') { + node.data = Buffer.from(node.data) + } + node1b = node + cb() + }) + }, + (cb) => { + expect(node1a.multihash).to.eql(node1b.multihash) + expect(node1a.data).to.eql(node1b.data) + expect(node1a.links).to.eql(node1b.links) + cb() + } + ], done) + }) + }) +} diff --git a/js/src/object/index.js b/js/src/object/index.js new file mode 100644 index 000000000..63fac9706 --- /dev/null +++ b/js/src/object/index.js @@ -0,0 +1,14 @@ +'use strict' +const { createSuite } = require('../utils/suite') + +const tests = { + new: require('./new'), + put: require('./put'), + get: require('./get'), + data: require('./data'), + links: require('./links'), + stat: require('./stat'), + patch: require('./patch') +} + +module.exports = createSuite(tests) diff --git a/js/src/object/links.js b/js/src/object/links.js new file mode 100644 index 000000000..18617b09f --- /dev/null +++ b/js/src/object/links.js @@ -0,0 +1,144 @@ +/* eslint-env mocha */ +'use strict' + +const dagPB = require('ipld-dag-pb') +const DAGNode = dagPB.DAGNode +const bs58 = require('bs58') +const series = require('async/series') +const hat = require('hat') +const { getDescribe, getIt, expect } = require('../utils/mocha') + +module.exports = (createCommon, options) => { + const describe = getDescribe(options) + const it = getIt(options) + const common = createCommon() + + describe('.object.links', function () { + this.timeout(80 * 1000) + + let ipfs + + before(function (done) { + // CI takes longer to instantiate the daemon, so we need to increase the + // timeout for the before step + this.timeout(60 * 1000) + + common.setup((err, factory) => { + expect(err).to.not.exist() + factory.spawnNode((err, node) => { + expect(err).to.not.exist() + ipfs = node + done() + }) + }) + }) + + after((done) => common.teardown(done)) + + it('should get empty links by multihash', (done) => { + const testObj = { + Data: Buffer.from(hat()), + Links: [] + } + + ipfs.object.put(testObj, (err, node) => { + expect(err).to.not.exist() + + ipfs.object.links(node.multihash, (err, links) => { + expect(err).to.not.exist() + expect(node.links).to.deep.equal(links) + done() + }) + }) + }) + + it('should get empty links by multihash (promised)', () => { + const testObj = { + Data: Buffer.from(hat()), + Links: [] + } + + return ipfs.object.put(testObj).then((node) => { + return ipfs.object.links(node.multihash).then((links) => { + expect(node.links).to.eql(links) + }) + }) + }) + + it('should get links by multihash', (done) => { + let node1a + let node1b + let node2 + + series([ + (cb) => { + DAGNode.create(Buffer.from('Some data 1'), (err, node) => { + expect(err).to.not.exist() + node1a = node + cb() + }) + }, + (cb) => { + DAGNode.create(Buffer.from('Some data 2'), (err, node) => { + expect(err).to.not.exist() + node2 = node + cb() + }) + }, + (cb) => { + const link = node2.toJSON() + link.name = 'some-link' + + DAGNode.addLink(node1a, link, (err, node) => { + expect(err).to.not.exist() + node1b = node + cb() + }) + }, + (cb) => { + ipfs.object.put(node1b, cb) + }, + (cb) => { + ipfs.object.links(node1b.multihash, (err, links) => { + expect(err).to.not.exist() + expect(node1b.links[0].toJSON()).to.eql(links[0].toJSON()) + cb() + }) + } + ], done) + }) + + it('should get links by base58 encoded multihash', (done) => { + const testObj = { + Data: Buffer.from(hat()), + Links: [] + } + + ipfs.object.put(testObj, (err, node) => { + expect(err).to.not.exist() + + ipfs.object.links(bs58.encode(node.multihash), { enc: 'base58' }, (err, links) => { + expect(err).to.not.exist() + expect(node.links).to.deep.equal(links) + done() + }) + }) + }) + + it('should get links by base58 encoded multihash string', (done) => { + const testObj = { + Data: Buffer.from(hat()), + Links: [] + } + + ipfs.object.put(testObj, (err, node) => { + expect(err).to.not.exist() + ipfs.object.links(bs58.encode(node.multihash), { enc: 'base58' }, (err, links) => { + expect(err).to.not.exist() + expect(node.links).to.deep.equal(links) + done() + }) + }) + }) + }) +} diff --git a/js/src/object/new.js b/js/src/object/new.js new file mode 100644 index 000000000..8be1c790c --- /dev/null +++ b/js/src/object/new.js @@ -0,0 +1,60 @@ +/* eslint-env mocha */ +'use strict' + +const { getDescribe, getIt, expect } = require('../utils/mocha') + +module.exports = (createCommon, options) => { + const describe = getDescribe(options) + const it = getIt(options) + const common = createCommon() + + describe('.object.new', function () { + this.timeout(80 * 1000) + + let ipfs + + before(function (done) { + // CI takes longer to instantiate the daemon, so we need to increase the + // timeout for the before step + this.timeout(60 * 1000) + + common.setup((err, factory) => { + expect(err).to.not.exist() + factory.spawnNode((err, node) => { + expect(err).to.not.exist() + ipfs = node + done() + }) + }) + }) + + after((done) => common.teardown(done)) + + it('should create a new object with no template', (done) => { + ipfs.object.new((err, node) => { + expect(err).to.not.exist() + const nodeJSON = node.toJSON() + expect(nodeJSON.multihash).to.equal('QmdfTbBqBPQ7VNxZEYEj14VmRuZBkqFbiwReogJgS1zR1n') + done() + }) + }) + + it('should create a new object with no template (promised)', () => { + return ipfs.object.new() + .then((node) => { + const nodeJSON = node.toJSON() + expect(nodeJSON.multihash).to.equal('QmdfTbBqBPQ7VNxZEYEj14VmRuZBkqFbiwReogJgS1zR1n') + }) + }) + + it('should create a new object with unixfs-dir template', (done) => { + ipfs.object.new('unixfs-dir', (err, node) => { + expect(err).to.not.exist() + const nodeJSON = node.toJSON() + expect(nodeJSON.multihash) + .to.equal('QmUNLLsPACCz1vLxQVkXqqLX5R1X345qqfHbsf67hvA3Nn') + done() + }) + }) + }) +} diff --git a/js/src/object/patch/add-link.js b/js/src/object/patch/add-link.js new file mode 100644 index 000000000..e04387d0e --- /dev/null +++ b/js/src/object/patch/add-link.js @@ -0,0 +1,179 @@ +/* eslint-env mocha */ +'use strict' + +const dagPB = require('ipld-dag-pb') +const DAGNode = dagPB.DAGNode +const series = require('async/series') +const { getDescribe, getIt, expect } = require('../../utils/mocha') + +module.exports = (createCommon, options) => { + const describe = getDescribe(options) + const it = getIt(options) + const common = createCommon() + + describe('.object.patch.addLink', function () { + this.timeout(80 * 1000) + + let ipfs + + before(function (done) { + // CI takes longer to instantiate the daemon, so we need to increase the + // timeout for the before step + this.timeout(60 * 1000) + + common.setup((err, factory) => { + expect(err).to.not.exist() + factory.spawnNode((err, node) => { + expect(err).to.not.exist() + ipfs = node + done() + }) + }) + }) + + after((done) => common.teardown(done)) + + it('should add a link to an existing node', (done) => { + let testNodeMultihash + let node1a + let node1b + let node2 + + const obj = { + Data: Buffer.from('patch test object'), + Links: [] + } + + series([ + (cb) => { + ipfs.object.put(obj, (err, node) => { + expect(err).to.not.exist() + testNodeMultihash = node.multihash + cb() + }) + }, + (cb) => { + DAGNode.create(obj.Data, obj.Links, (err, node) => { + expect(err).to.not.exist() + node1a = node + cb() + }) + }, + (cb) => { + DAGNode.create(Buffer.from('some other node'), (err, node) => { + expect(err).to.not.exist() + node2 = node + cb() + }) + }, + (cb) => { + // note: we need to put the linked obj, otherwise IPFS won't + // timeout. Reason: it needs the node to get its size + ipfs.object.put(node2, cb) + }, + (cb) => { + const link = node2.toJSON() + link.name = 'link-to-node' + DAGNode.addLink(node1a, link, (err, node) => { + expect(err).to.not.exist() + node1b = node + cb() + }) + }, + (cb) => { + ipfs.object.patch.addLink(testNodeMultihash, node1b.links[0], (err, node) => { + expect(err).to.not.exist() + expect(node1b.multihash).to.eql(node.multihash) + cb() + }) + } + /* TODO: revisit this assertions. + (cb) => { + // note: make sure we can link js plain objects + const content = Buffer.from(JSON.stringify({ + title: 'serialized object' + }, null, 0)) + ipfs.add(content, (err, result) => { + expect(err).to.not.exist() + expect(result).to.exist() + expect(result).to.have.lengthOf(1) + const object = result.pop() + node3 = { + name: object.hash, + multihash: object.hash, + size: object.size + } + cb() + }) + }, + (cb) => { + ipfs.object.patch.addLink(testNodeWithLinkMultihash, node3, (err, node) => { + expect(err).to.not.exist() + expect(node).to.exist() + testNodeWithLinkMultihash = node.multihash + testLinkPlainObject = node3 + cb() + }) + } + */ + ], done) + }) + + it('should add a link to an existing node (promised)', () => { + let testNodeMultihash + let node1a + let node1b + let node2 + + const obj = { + Data: Buffer.from('patch test object (promised)'), + Links: [] + } + + return ipfs.object.put(obj) + .then((node) => { + testNodeMultihash = node.multihash + }) + .then(() => new Promise((resolve, reject) => { + DAGNode.create(obj.Data, obj.Links, function (err, node) { + if (err) { + return reject(err) + } + return resolve(node) + }) + })) + .then((node) => { + node1a = node + return new Promise((resolve, reject) => { + DAGNode.create(Buffer.from('some other node'), function (err, node) { + if (err) { + return reject(err) + } + return resolve(node) + }) + }).then((node1) => { + node2 = node1 + return ipfs.object.put(node2) + }) + }) + .then(() => { + const link = node2.toJSON() + link.name = 'link-to-node' + return new Promise((resolve, reject) => { + DAGNode.addLink(node1a, link, function (err, node) { + if (err) { + return reject(err) + } + return resolve(node) + }) + }).then((node) => { + node1b = node + return ipfs.object.patch.addLink(testNodeMultihash, node1b.links[0]) + }) + }) + .then((node) => { + expect(node1b.multihash).to.eql(node.multihash) + }) + }) + }) +} diff --git a/js/src/object/patch/append-data.js b/js/src/object/patch/append-data.js new file mode 100644 index 000000000..8687f26cb --- /dev/null +++ b/js/src/object/patch/append-data.js @@ -0,0 +1,66 @@ +/* eslint-env mocha */ +'use strict' + +const { getDescribe, getIt, expect } = require('../../utils/mocha') + +module.exports = (createCommon, options) => { + const describe = getDescribe(options) + const it = getIt(options) + const common = createCommon() + + describe('.object.patch.appendData', function () { + this.timeout(80 * 1000) + + let ipfs + + before(function (done) { + // CI takes longer to instantiate the daemon, so we need to increase the + // timeout for the before step + this.timeout(60 * 1000) + + common.setup((err, factory) => { + expect(err).to.not.exist() + factory.spawnNode((err, node) => { + expect(err).to.not.exist() + ipfs = node + done() + }) + }) + }) + + after((done) => common.teardown(done)) + + it('should append data to an existing node', (done) => { + const obj = { + Data: Buffer.from('patch test object'), + Links: [] + } + + ipfs.object.put(obj, (err, node) => { + expect(err).to.not.exist() + + ipfs.object.patch.appendData(node.multihash, Buffer.from('append'), (err, patchedNode) => { + expect(err).to.not.exist() + expect(patchedNode.multihash).to.not.deep.equal(node.multihash) + done() + }) + }) + }) + + it('should append data to an existing node (promised)', () => { + const obj = { + Data: Buffer.from('patch test object (promised)'), + Links: [] + } + + return ipfs.object.put(obj) + .then((node) => { + return ipfs.object.patch.appendData(node.multihash, Buffer.from('append')) + .then((patchedNode) => ({ patchedNode, node })) + }) + .then(({ patchedNode, node }) => { + expect(patchedNode.multihash).to.not.deep.equal(node.multihash) + }) + }) + }) +} diff --git a/js/src/object/patch/index.js b/js/src/object/patch/index.js new file mode 100644 index 000000000..d84efe199 --- /dev/null +++ b/js/src/object/patch/index.js @@ -0,0 +1,11 @@ +'use strict' +const { createSuite } = require('../../utils/suite') + +const tests = { + addLink: require('./add-link'), + rmLink: require('./rm-link'), + appendData: require('./append-data'), + setData: require('./set-data') +} + +module.exports = createSuite(tests, 'patch') diff --git a/js/src/object/patch/rm-link.js b/js/src/object/patch/rm-link.js new file mode 100644 index 000000000..4b9241b7a --- /dev/null +++ b/js/src/object/patch/rm-link.js @@ -0,0 +1,124 @@ +/* eslint-env mocha */ +'use strict' + +const dagPB = require('ipld-dag-pb') +const DAGLink = dagPB.DAGLink +const series = require('async/series') +const { getDescribe, getIt, expect } = require('../../utils/mocha') + +module.exports = (createCommon, options) => { + const describe = getDescribe(options) + const it = getIt(options) + const common = createCommon() + + describe('.object.patch.rmLink', function () { + this.timeout(80 * 1000) + + let ipfs + + before(function (done) { + // CI takes longer to instantiate the daemon, so we need to increase the + // timeout for the before step + this.timeout(60 * 1000) + + common.setup((err, factory) => { + expect(err).to.not.exist() + factory.spawnNode((err, node) => { + expect(err).to.not.exist() + ipfs = node + done() + }) + }) + }) + + after((done) => common.teardown(done)) + + it('should remove a link from an existing node', (done) => { + let node1a + let node1b + let node2 + let testLink + + const obj1 = { + Data: Buffer.from('patch test object 1'), + Links: [] + } + + const obj2 = { + Data: Buffer.from('patch test object 2'), + Links: [] + } + + series([ + (cb) => { + ipfs.object.put(obj1, (err, node) => { + expect(err).to.not.exist() + node1a = node + cb() + }) + }, + (cb) => { + ipfs.object.put(obj2, (err, node) => { + expect(err).to.not.exist() + node2 = node + cb() + }) + }, + (cb) => { + testLink = new DAGLink('link-to-node', node2.size, node2.multihash) + + ipfs.object.patch.addLink(node1a.multihash, testLink, (err, node) => { + expect(err).to.not.exist() + node1b = node + cb() + }) + }, + (cb) => { + ipfs.object.patch.rmLink(node1b.multihash, testLink, (err, node) => { + expect(err).to.not.exist() + expect(node.multihash).to.not.deep.equal(node1b.multihash) + cb() + }) + } + /* TODO: revisit this assertions. + (cb) => { + ipfs.object.patch.rmLink(testNodeWithLinkMultihash, testLinkPlainObject, (err, node) => { + expect(err).to.not.exist() + expect(node.multihash).to.not.deep.equal(testNodeWithLinkMultihash) + cb() + }) + } + */ + ], done) + }) + + it('should remove a link from an existing node (promised)', () => { + let node1a + let node1b + let node2 + let testLink + + const obj1 = { + Data: Buffer.from('patch test object 1'), + Links: [] + } + + const obj2 = { + Data: Buffer.from('patch test object 2'), + Links: [] + } + + return ipfs.object.put(obj1) + .then((node) => { node1a = node }) + .then(() => ipfs.object.put(obj2)) + .then((node) => { node2 = node }) + .then(() => { + testLink = new DAGLink('link-to-node', node2.size, node2.multihash) + return ipfs.object.patch.addLink(node1a.multihash, testLink) + }) + .then((node) => { node1b = node }) + .then(() => ipfs.object.patch.rmLink(node1b.multihash, testLink)) + .then((node) => expect(node.multihash).to.not.deep.equal(node1b.multihash)) + }) + }) +} diff --git a/js/src/object/patch/set-data.js b/js/src/object/patch/set-data.js new file mode 100644 index 000000000..9d2cb8187 --- /dev/null +++ b/js/src/object/patch/set-data.js @@ -0,0 +1,66 @@ +/* eslint-env mocha */ +'use strict' + +const { getDescribe, getIt, expect } = require('../../utils/mocha') + +module.exports = (createCommon, options) => { + const describe = getDescribe(options) + const it = getIt(options) + const common = createCommon() + + describe('.object.patch.setData', function () { + this.timeout(80 * 1000) + + let ipfs + + before(function (done) { + // CI takes longer to instantiate the daemon, so we need to increase the + // timeout for the before step + this.timeout(60 * 1000) + + common.setup((err, factory) => { + expect(err).to.not.exist() + factory.spawnNode((err, node) => { + expect(err).to.not.exist() + ipfs = node + done() + }) + }) + }) + + after((done) => common.teardown(done)) + + it('should set data for an existing node', (done) => { + const obj = { + Data: Buffer.from('patch test object'), + Links: [] + } + + ipfs.object.put(obj, (err, node) => { + expect(err).to.not.exist() + + ipfs.object.patch.setData(node.multihash, Buffer.from('set'), (err, patchedNode) => { + expect(err).to.not.exist() + expect(node.multihash).to.not.deep.equal(patchedNode.multihash) + done() + }) + }) + }) + + it('should set data for an existing node (promised)', () => { + const obj = { + Data: Buffer.from('patch test object (promised)'), + Links: [] + } + + return ipfs.object.put(obj) + .then((node) => { + return ipfs.object.patch.setData(node.multihash, Buffer.from('set')) + .then((patchedNode) => ({ patchedNode, node })) + }) + .then(({ patchedNode, node }) => { + expect(node.multihash).to.not.deep.equal(patchedNode.multihash) + }) + }) + }) +} diff --git a/js/src/object/put.js b/js/src/object/put.js new file mode 100644 index 000000000..0065008e1 --- /dev/null +++ b/js/src/object/put.js @@ -0,0 +1,194 @@ +/* eslint-env mocha */ +'use strict' + +const dagPB = require('ipld-dag-pb') +const DAGNode = dagPB.DAGNode +const series = require('async/series') +const hat = require('hat') +const { getDescribe, getIt, expect } = require('../utils/mocha') + +module.exports = (createCommon, options) => { + const describe = getDescribe(options) + const it = getIt(options) + const common = createCommon() + + describe('.object.put', function () { + this.timeout(80 * 1000) + + let ipfs + + before(function (done) { + // CI takes longer to instantiate the daemon, so we need to increase the + // timeout for the before step + this.timeout(60 * 1000) + + common.setup((err, factory) => { + expect(err).to.not.exist() + factory.spawnNode((err, node) => { + expect(err).to.not.exist() + ipfs = node + done() + }) + }) + }) + + after((done) => common.teardown(done)) + + it('should put an object', (done) => { + const obj = { + Data: Buffer.from(hat()), + Links: [] + } + + ipfs.object.put(obj, (err, node) => { + expect(err).to.not.exist() + const nodeJSON = node.toJSON() + expect(nodeJSON.data).to.eql(obj.Data) + expect(nodeJSON.links).to.eql(obj.Links) + expect(nodeJSON.multihash).to.exist() + done() + }) + }) + + it('should put an object (promised)', () => { + const obj = { + Data: Buffer.from(hat()), + Links: [] + } + + return ipfs.object.put(obj) + .then((node) => { + const nodeJSON = node.toJSON() + expect(obj.Data).to.deep.equal(nodeJSON.data) + expect(obj.Links).to.deep.equal(nodeJSON.links) + expect(nodeJSON.multihash).to.exist() + }) + }) + + it('should put a JSON encoded Buffer', (done) => { + const obj = { + Data: Buffer.from(hat()), + Links: [] + } + + const obj2 = { + Data: obj.Data.toString(), + Links: obj.Links + } + + const buf = Buffer.from(JSON.stringify(obj2)) + + ipfs.object.put(buf, { enc: 'json' }, (err, node) => { + expect(err).to.not.exist() + const nodeJSON = node.toJSON() + + expect(nodeJSON.data).to.eql(node.data) + expect(nodeJSON.multihash).to.exist() + done() + }) + }) + + it('should put a Protobuf encoded Buffer', (done) => { + let node + let serialized + + series([ + (cb) => { + DAGNode.create(Buffer.from(hat()), (err, _node) => { + expect(err).to.not.exist() + node = _node + cb() + }) + }, + (cb) => { + dagPB.util.serialize(node, (err, _serialized) => { + expect(err).to.not.exist() + serialized = _serialized + cb() + }) + }, + (cb) => { + ipfs.object.put(serialized, { enc: 'protobuf' }, (err, storedNode) => { + expect(err).to.not.exist() + expect(node.data).to.deep.equal(node.data) + expect(node.links).to.deep.equal(node.links) + expect(node.multihash).to.eql(storedNode.multihash) + cb() + }) + } + ], done) + }) + + it('should put a Buffer as data', (done) => { + const data = Buffer.from(hat()) + ipfs.object.put(data, (err, node) => { + expect(err).to.not.exist() + const nodeJSON = node.toJSON() + expect(data).to.deep.equal(nodeJSON.data) + expect([]).to.deep.equal(nodeJSON.links) + expect(nodeJSON.multihash).to.exist() + done() + }) + }) + + it('should put a Protobuf DAGNode', (done) => { + DAGNode.create(Buffer.from(hat()), (err, dNode) => { + expect(err).to.not.exist() + ipfs.object.put(dNode, (err, node) => { + expect(err).to.not.exist() + expect(dNode.data).to.deep.equal(node.data) + expect(dNode.links).to.deep.equal(node.links) + done() + }) + }) + }) + + it('should fail if a string is passed', (done) => { + ipfs.object.put(hat(), (err) => { + expect(err).to.exist() + done() + }) + }) + + it('should put a Protobuf DAGNode with a link', (done) => { + let node1a + let node1b + let node2 + + series([ + (cb) => { + DAGNode.create(Buffer.from(hat()), (err, node) => { + expect(err).to.not.exist() + node1a = node + cb() + }) + }, + (cb) => { + DAGNode.create(Buffer.from(hat()), (err, node) => { + expect(err).to.not.exist() + node2 = node + cb() + }) + }, + (cb) => { + const link = node2.toJSON() + link.name = 'some-link' + DAGNode.addLink(node1a, link, (err, node) => { + expect(err).to.not.exist() + node1b = node + cb() + }) + }, + (cb) => { + ipfs.object.put(node1b, (err, node) => { + expect(err).to.not.exist() + expect(node1b.data).to.deep.equal(node.data) + expect(node1b.links.map((l) => l.toJSON())) + .to.deep.equal(node.links.map((l) => l.toJSON())) + cb() + }) + } + ], done) + }) + }) +} diff --git a/js/src/object/stat.js b/js/src/object/stat.js new file mode 100644 index 000000000..3ce04f958 --- /dev/null +++ b/js/src/object/stat.js @@ -0,0 +1,187 @@ +/* eslint-env mocha */ +'use strict' + +const dagPB = require('ipld-dag-pb') +const DAGNode = dagPB.DAGNode +const bs58 = require('bs58') +const series = require('async/series') +const { getDescribe, getIt, expect } = require('../utils/mocha') + +module.exports = (createCommon, options) => { + const describe = getDescribe(options) + const it = getIt(options) + const common = createCommon() + + describe('.object.stat', function () { + this.timeout(80 * 1000) + + let ipfs + + before(function (done) { + // CI takes longer to instantiate the daemon, so we need to increase the + // timeout for the before step + this.timeout(60 * 1000) + + common.setup((err, factory) => { + expect(err).to.not.exist() + factory.spawnNode((err, node) => { + expect(err).to.not.exist() + ipfs = node + done() + }) + }) + }) + + after((done) => common.teardown(done)) + + it('should get stats by multihash', (done) => { + const testObj = { + Data: Buffer.from('get test object'), + Links: [] + } + + ipfs.object.put(testObj, (err, node) => { + expect(err).to.not.exist() + + ipfs.object.stat(node.multihash, (err, stats) => { + expect(err).to.not.exist() + const expected = { + Hash: 'QmNggDXca24S6cMPEYHZjeuc4QRmofkRrAEqVL3Ms2sdJZ', + NumLinks: 0, + BlockSize: 17, + LinksSize: 2, + DataSize: 15, + CumulativeSize: 17 + } + expect(expected).to.deep.equal(stats) + done() + }) + }) + }) + + it('should get stats for object by multihash (promised)', () => { + const testObj = { + Data: Buffer.from('get test object'), + Links: [] + } + + return ipfs.object.put(testObj, (err, node) => { + expect(err).to.not.exist() + + return ipfs.object.stat('QmNggDXca24S6cMPEYHZjeuc4QRmofkRrAEqVL3Ms2sdJZ', {enc: 'base58'}) + .then((stats) => { + const expected = { + Hash: 'QmNggDXca24S6cMPEYHZjeuc4QRmofkRrAEqVL3Ms2sdJZ', + NumLinks: 0, + BlockSize: 17, + LinksSize: 2, + DataSize: 15, + CumulativeSize: 17 + } + expect(expected).to.deep.equal(stats) + }) + }) + }) + + it('should get stats for object with links by multihash', (done) => { + let node1a + let node1b + let node2 + + series([ + (cb) => { + DAGNode.create(Buffer.from('Some data 1'), (err, node) => { + expect(err).to.not.exist() + node1a = node + cb() + }) + }, + (cb) => { + DAGNode.create(Buffer.from('Some data 2'), (err, node) => { + expect(err).to.not.exist() + node2 = node + cb() + }) + }, + (cb) => { + const link = node2.toJSON() + link.name = 'some-link' + + DAGNode.addLink(node1a, link, (err, node) => { + expect(err).to.not.exist() + node1b = node + cb() + }) + }, + (cb) => { + ipfs.object.put(node1b, cb) + }, + (cb) => { + ipfs.object.stat(node1b.multihash, (err, stats) => { + expect(err).to.not.exist() + const expected = { + Hash: 'QmPR7W4kaADkAo4GKEVVPQN81EDUFCHJtqejQZ5dEG7pBC', + NumLinks: 1, + BlockSize: 64, + LinksSize: 53, + DataSize: 11, + CumulativeSize: 77 + } + expect(expected).to.eql(stats) + cb() + }) + } + ], done) + }) + + it('should get stats by base58 encoded multihash', (done) => { + const testObj = { + Data: Buffer.from('get test object'), + Links: [] + } + + ipfs.object.put(testObj, (err, node) => { + expect(err).to.not.exist() + + ipfs.object.stat(bs58.encode(node.multihash), { enc: 'base58' }, (err, stats) => { + expect(err).to.not.exist() + const expected = { + Hash: 'QmNggDXca24S6cMPEYHZjeuc4QRmofkRrAEqVL3Ms2sdJZ', + NumLinks: 0, + BlockSize: 17, + LinksSize: 2, + DataSize: 15, + CumulativeSize: 17 + } + expect(expected).to.deep.equal(stats) + done() + }) + }) + }) + + it('should get stats by base58 encoded multihash string', (done) => { + const testObj = { + Data: Buffer.from('get test object'), + Links: [] + } + + ipfs.object.put(testObj, (err, node) => { + expect(err).to.not.exist() + + ipfs.object.stat(bs58.encode(node.multihash).toString(), { enc: 'base58' }, (err, stats) => { + expect(err).to.not.exist() + const expected = { + Hash: 'QmNggDXca24S6cMPEYHZjeuc4QRmofkRrAEqVL3Ms2sdJZ', + NumLinks: 0, + BlockSize: 17, + LinksSize: 2, + DataSize: 15, + CumulativeSize: 17 + } + expect(expected).to.deep.equal(stats) + done() + }) + }) + }) + }) +} diff --git a/js/src/pin.js b/js/src/pin.js deleted file mode 100644 index 1fc507ff5..000000000 --- a/js/src/pin.js +++ /dev/null @@ -1,178 +0,0 @@ -/* eslint-env mocha */ -/* eslint max-nested-callbacks: ["error", 8] */ - -'use strict' - -const chai = require('chai') -const dirtyChai = require('dirty-chai') -const expect = chai.expect -chai.use(dirtyChai) -const loadFixture = require('aegir/fixtures') - -const testFile = loadFixture('js/test/fixtures/testfile.txt', 'interface-ipfs-core') -const testHash = 'Qma4hjFTnCasJ8PVp3mZbZK5g2vGDT4LByLJ7m8ciyRFZP' - -module.exports = (common) => { - describe('.pin', function () { - this.timeout(50 * 1000) - - let ipfs - - before(function (done) { - // CI takes longer to instantiate the daemon, so we need to increase the - // timeout for the before step - this.timeout(60 * 1000) - - common.setup((err, factory) => { - expect(err).to.not.exist() - factory.spawnNode((err, node) => { - expect(err).to.not.exist() - ipfs = node - populate() - }) - }) - - function populate () { - ipfs.files.add(testFile, (err, res) => { - expect(err).to.not.exist() - expect(res).to.have.length(1) - expect(res[0].hash).to.equal(testHash) - expect(res[0].path).to.equal(testHash) - done() - }) - } - }) - - after((done) => common.teardown(done)) - - describe('callback API', () => { - // 1st, because ipfs.files.add pins automatically - it('.ls type recursive', (done) => { - ipfs.pin.ls({ type: 'recursive' }, (err, pinset) => { - expect(err).to.not.exist() - expect(pinset).to.deep.include({ - hash: testHash, - type: 'recursive' - }) - done() - }) - }) - - it.skip('.ls type indirect', (done) => { - ipfs.pin.ls({ type: 'indirect' }, (err, pinset) => { - expect(err).to.not.exist() - // because the pinned file has no links - expect(pinset).to.be.empty() - done() - }) - }) - - it('.rm', (done) => { - ipfs.pin.rm(testHash, { recursive: true }, (err, pinset) => { - expect(err).to.not.exist() - expect(pinset).to.deep.equal([{ - hash: testHash - }]) - ipfs.pin.ls({ type: 'direct' }, (err, pinset) => { - expect(err).to.not.exist() - expect(pinset).to.not.deep.include({ - hash: testHash, - type: 'recursive' - }) - done() - }) - }) - }) - - it('.add', (done) => { - ipfs.pin.add(testHash, { recursive: false }, (err, pinset) => { - expect(err).to.not.exist() - expect(pinset).to.deep.equal([{ - hash: testHash - }]) - done() - }) - }) - - it('.ls', (done) => { - ipfs.pin.ls((err, pinset) => { - expect(err).to.not.exist() - expect(pinset).to.not.be.empty() - expect(pinset).to.deep.include({ - hash: testHash, - type: 'direct' - }) - done() - }) - }) - - it('.ls type direct', (done) => { - ipfs.pin.ls({ type: 'direct' }, (err, pinset) => { - expect(err).to.not.exist() - expect(pinset).to.deep.include({ - hash: testHash, - type: 'direct' - }) - done() - }) - }) - - it('.ls for a specific hash', (done) => { - ipfs.pin.ls(testHash, (err, pinset) => { - expect(err).to.not.exist() - expect(pinset).to.deep.equal([{ - hash: testHash, - type: 'direct' - }]) - done() - }) - }) - }) - - describe('promise API', () => { - it('.add', () => { - return ipfs.pin.add(testHash, { recursive: false }) - .then((pinset) => { - expect(pinset).to.deep.equal([{ - hash: testHash - }]) - }) - }) - - it('.ls', () => { - return ipfs.pin.ls() - .then((pinset) => { - expect(pinset).to.deep.include({ - hash: testHash, - type: 'direct' - }) - }) - }) - - it('.ls hash', () => { - return ipfs.pin.ls(testHash) - .then((pinset) => { - expect(pinset).to.deep.equal([{ - hash: testHash, - type: 'direct' - }]) - }) - }) - - it('.rm', () => { - return ipfs.pin.rm(testHash, { recursive: false }) - .then((pinset) => { - expect(pinset).to.deep.equal([{ - hash: testHash - }]) - return ipfs.pin.ls({ type: 'direct' }) - }) - .then((pinset) => { - expect(pinset).to.not.deep.include({ - hash: testHash - }) - }) - }) - }) - }) -} diff --git a/js/src/pin/add.js b/js/src/pin/add.js new file mode 100644 index 000000000..3adc653d7 --- /dev/null +++ b/js/src/pin/add.js @@ -0,0 +1,60 @@ +/* eslint-env mocha */ +'use strict' + +const each = require('async/each') +const { fixtures } = require('./utils') +const { getDescribe, getIt, expect } = require('../utils/mocha') + +module.exports = (createCommon, options) => { + const describe = getDescribe(options) + const it = getIt(options) + const common = createCommon() + + describe('.pin.add', function () { + this.timeout(50 * 1000) + + let ipfs + + before(function (done) { + // CI takes longer to instantiate the daemon, so we need to increase the + // timeout for the before step + this.timeout(60 * 1000) + + common.setup((err, factory) => { + expect(err).to.not.exist() + factory.spawnNode((err, node) => { + expect(err).to.not.exist() + ipfs = node + populate() + }) + }) + + function populate () { + each(fixtures.files, (file, cb) => { + ipfs.files.add(file.data, { pin: false }, cb) + }, done) + } + }) + + after((done) => common.teardown(done)) + + it('should add a pin', (done) => { + ipfs.pin.add(fixtures.files[0].cid, { recursive: false }, (err, pinset) => { + expect(err).to.not.exist() + expect(pinset).to.deep.include({ + hash: fixtures.files[0].cid + }) + done() + }) + }) + + it('should add a pin (promised)', () => { + return ipfs.pin.add(fixtures.files[1].cid, { recursive: false }) + .then((pinset) => { + expect(pinset).to.deep.include({ + hash: fixtures.files[1].cid + }) + }) + }) + }) +} diff --git a/js/src/pin/index.js b/js/src/pin/index.js new file mode 100644 index 000000000..b3723d07c --- /dev/null +++ b/js/src/pin/index.js @@ -0,0 +1,10 @@ +'use strict' +const { createSuite } = require('../utils/suite') + +const tests = { + ls: require('./ls'), + rm: require('./rm'), + add: require('./add') +} + +module.exports = createSuite(tests) diff --git a/js/src/pin/ls.js b/js/src/pin/ls.js new file mode 100644 index 000000000..032f60c2d --- /dev/null +++ b/js/src/pin/ls.js @@ -0,0 +1,142 @@ +/* eslint-env mocha */ +'use strict' + +const parallel = require('async/parallel') +const { fixtures } = require('./utils') +const { getDescribe, getIt, expect } = require('../utils/mocha') + +module.exports = (createCommon, options) => { + const describe = getDescribe(options) + const it = getIt(options) + const common = createCommon() + + describe('.pin.ls', function () { + this.timeout(50 * 1000) + + let ipfs + + before(function (done) { + // CI takes longer to instantiate the daemon, so we need to increase the + // timeout for the before step + this.timeout(60 * 1000) + + common.setup((err, factory) => { + expect(err).to.not.exist() + factory.spawnNode((err, node) => { + expect(err).to.not.exist() + ipfs = node + populate() + }) + }) + + function populate () { + parallel([ + (cb) => { + ipfs.files.add(fixtures.files[0].data, { pin: false }, (err, res) => { + if (err) return cb(err) + ipfs.pin.add(fixtures.files[0].cid, { recursive: true }, cb) + }) + }, + (cb) => { + ipfs.files.add(fixtures.files[1].data, { pin: false }, (err, res) => { + if (err) return cb(err) + ipfs.pin.add(fixtures.files[1].cid, { recursive: false }, cb) + }) + } + ], done) + } + }) + + after((done) => common.teardown(done)) + + // 1st, because ipfs.files.add pins automatically + it('should list recursive pins', (done) => { + ipfs.pin.ls({ type: 'recursive' }, (err, pinset) => { + expect(err).to.not.exist() + expect(pinset).to.deep.include({ + type: 'recursive', + hash: fixtures.files[0].cid + }) + done() + }) + }) + + it('should list indirect pins', (done) => { + ipfs.pin.ls({ type: 'indirect' }, (err, pinset) => { + expect(err).to.not.exist() + // because the pinned files have no links + expect(pinset).to.not.deep.include({ + type: 'recursive', + hash: fixtures.files[0].cid + }) + expect(pinset).to.not.deep.include({ + type: 'direct', + hash: fixtures.files[1].cid + }) + done() + }) + }) + + it('should list pins', (done) => { + ipfs.pin.ls((err, pinset) => { + expect(err).to.not.exist() + expect(pinset).to.not.be.empty() + expect(pinset).to.deep.include({ + type: 'recursive', + hash: fixtures.files[0].cid + }) + expect(pinset).to.deep.include({ + type: 'direct', + hash: fixtures.files[1].cid + }) + done() + }) + }) + + it('should list pins (promised)', () => { + return ipfs.pin.ls() + .then((pinset) => { + expect(pinset).to.deep.include({ + type: 'recursive', + hash: fixtures.files[0].cid + }) + expect(pinset).to.deep.include({ + type: 'direct', + hash: fixtures.files[1].cid + }) + }) + }) + + it('should list direct pins', (done) => { + ipfs.pin.ls({ type: 'direct' }, (err, pinset) => { + expect(err).to.not.exist() + expect(pinset).to.deep.include({ + type: 'direct', + hash: fixtures.files[1].cid + }) + done() + }) + }) + + it('should list pins for a specific hash', (done) => { + ipfs.pin.ls(fixtures.files[0].cid, (err, pinset) => { + expect(err).to.not.exist() + expect(pinset).to.deep.equal([{ + type: 'recursive', + hash: fixtures.files[0].cid + }]) + done() + }) + }) + + it('should list pins for a specific hash (promised)', () => { + return ipfs.pin.ls(fixtures.files[0].cid) + .then((pinset) => { + expect(pinset).to.deep.equal([{ + type: 'recursive', + hash: fixtures.files[0].cid + }]) + }) + }) + }) +} diff --git a/js/src/pin/rm.js b/js/src/pin/rm.js new file mode 100644 index 000000000..99c47312c --- /dev/null +++ b/js/src/pin/rm.js @@ -0,0 +1,84 @@ +/* eslint-env mocha */ +'use strict' + +const parallel = require('async/parallel') +const { fixtures } = require('./utils') +const { getDescribe, getIt, expect } = require('../utils/mocha') + +module.exports = (createCommon, options) => { + const describe = getDescribe(options) + const it = getIt(options) + const common = createCommon() + + describe('.pin.rm', function () { + this.timeout(50 * 1000) + + let ipfs + + before(function (done) { + // CI takes longer to instantiate the daemon, so we need to increase the + // timeout for the before step + this.timeout(60 * 1000) + + common.setup((err, factory) => { + expect(err).to.not.exist() + factory.spawnNode((err, node) => { + expect(err).to.not.exist() + ipfs = node + populate() + }) + }) + + function populate () { + parallel([ + (cb) => { + ipfs.files.add(fixtures.files[0].data, { pin: false }, (err, res) => { + if (err) return cb(err) + ipfs.pin.add(fixtures.files[0].cid, { recursive: true }, cb) + }) + }, + (cb) => { + ipfs.files.add(fixtures.files[1].data, { pin: false }, (err, res) => { + if (err) return cb(err) + ipfs.pin.add(fixtures.files[1].cid, { recursive: false }, cb) + }) + } + ], done) + } + }) + + after((done) => common.teardown(done)) + + it('should remove a recursive pin', (done) => { + ipfs.pin.rm(fixtures.files[0].cid, { recursive: true }, (err, pinset) => { + expect(err).to.not.exist() + expect(pinset).to.deep.equal([{ + hash: fixtures.files[0].cid + }]) + ipfs.pin.ls({ type: 'recursive' }, (err, pinset) => { + expect(err).to.not.exist() + expect(pinset).to.not.deep.include({ + hash: fixtures.files[0].cid, + type: 'recursive' + }) + done() + }) + }) + }) + + it('should remove a direct pin (promised)', () => { + return ipfs.pin.rm(fixtures.files[1].cid, { recursive: false }) + .then((pinset) => { + expect(pinset).to.deep.equal([{ + hash: fixtures.files[1].cid + }]) + return ipfs.pin.ls({ type: 'direct' }) + }) + .then((pinset) => { + expect(pinset).to.not.deep.include({ + hash: fixtures.files[1].cid + }) + }) + }) + }) +} diff --git a/js/src/pin/utils.js b/js/src/pin/utils.js new file mode 100644 index 000000000..5f1add5ce --- /dev/null +++ b/js/src/pin/utils.js @@ -0,0 +1,13 @@ +'use strict' + +const loadFixture = require('aegir/fixtures') + +exports.fixtures = Object.freeze({ + files: Object.freeze([Object.freeze({ + data: loadFixture('js/test/fixtures/testfile.txt', 'interface-ipfs-core'), + cid: 'Qma4hjFTnCasJ8PVp3mZbZK5g2vGDT4LByLJ7m8ciyRFZP' + }), Object.freeze({ + data: loadFixture('js/test/fixtures/test-folder/files/hello.txt', 'interface-ipfs-core'), + cid: 'QmY9cxiHqTFoWamkQVkpmmqzBrY3hCBEL2XNu3NtX74Fuu' + })]) +}) diff --git a/js/src/ping.js b/js/src/ping.js deleted file mode 100644 index de88498dd..000000000 --- a/js/src/ping.js +++ /dev/null @@ -1,236 +0,0 @@ -/* eslint-env mocha */ -'use strict' - -const chai = require('chai') -const dirtyChai = require('dirty-chai') -const pull = require('pull-stream') -const pump = require('pump') -const { Writable } = require('stream') -const series = require('async/series') -const { spawnNodesWithId } = require('./utils/spawn') - -const expect = chai.expect -chai.use(dirtyChai) - -function expectIsPingResponse (obj) { - expect(obj).to.have.a.property('success') - expect(obj).to.have.a.property('time') - expect(obj).to.have.a.property('text') - expect(obj.success).to.be.a('boolean') - expect(obj.time).to.be.a('number') - expect(obj.text).to.be.a('string') -} - -// Determine if a ping response object is a pong, or something else, like a status message -function isPong (pingResponse) { - return Boolean(pingResponse && pingResponse.success && !pingResponse.text) -} - -module.exports = (common) => { - describe('.ping', function () { - let ipfsA - let ipfsB - - before(function (done) { - this.timeout(60 * 1000) - - common.setup((err, factory) => { - if (err) return done(err) - - series([ - (cb) => { - spawnNodesWithId(2, factory, (err, nodes) => { - if (err) return cb(err) - ipfsA = nodes[0] - ipfsB = nodes[1] - cb() - }) - }, - (cb) => ipfsA.swarm.connect(ipfsB.peerId.addresses[0], cb) - ], done) - }) - }) - - after((done) => common.teardown(done)) - - describe('.ping', function () { - this.timeout(15 * 1000) - - it('sends the specified number of packets', (done) => { - const count = 3 - ipfsA.ping(ipfsB.peerId.id, { count }, (err, responses) => { - expect(err).to.not.exist() - responses.forEach(expectIsPingResponse) - const pongs = responses.filter(isPong) - expect(pongs.length).to.equal(count) - done() - }) - }) - - it('fails when pinging an unknown peer', (done) => { - const unknownPeerId = 'QmUmaEnH1uMmvckMZbh3yShaasvELPW4ZLPWnB4entMTEn' - const count = 2 - - ipfsA.ping(unknownPeerId, { count }, (err, responses) => { - expect(err).to.exist() - expect(responses[0].text).to.include('Looking up') - expect(responses[1].success).to.be.false() - done() - }) - }) - - it('fails when pinging an invalid peer', (done) => { - const invalidPeerId = 'not a peer ID' - const count = 2 - ipfsA.ping(invalidPeerId, { count }, (err, responses) => { - expect(err).to.exist() - expect(err.message).to.include('failed to parse peer address') - done() - }) - }) - }) - - describe('.pingPullStream', function () { - this.timeout(15 * 1000) - - it('sends the specified number of packets', (done) => { - let packetNum = 0 - const count = 3 - pull( - ipfsA.pingPullStream(ipfsB.peerId.id, { count }), - pull.drain((res) => { - expect(res.success).to.be.true() - // It's a pong - if (isPong(res)) { - packetNum++ - } - }, (err) => { - expect(err).to.not.exist() - expect(packetNum).to.equal(count) - done() - }) - ) - }) - - it('fails when pinging an unknown peer', (done) => { - let messageNum = 0 - const unknownPeerId = 'QmUmaEnH1uMmvckMZbh3yShaasvELPW4ZLPWnB4entMTEn' - const count = 2 - pull( - ipfsA.pingPullStream(unknownPeerId, { count }), - pull.drain((res) => { - expectIsPingResponse(res) - messageNum++ - - // First message should be "looking up" response - if (messageNum === 1) { - expect(res.text).to.include('Looking up') - } - - // Second message should be a failure response - if (messageNum === 2) { - expect(res.success).to.be.false() - } - }, (err) => { - expect(err).to.exist() - done() - }) - ) - }) - - it('fails when pinging an invalid peer', (done) => { - const invalidPeerId = 'not a peer ID' - const count = 2 - pull( - ipfsA.pingPullStream(invalidPeerId, { count }), - pull.collect((err) => { - expect(err).to.exist() - expect(err.message).to.include('failed to parse peer address') - done() - }) - ) - }) - }) - - describe('.pingReadableStream', function () { - this.timeout(15 * 1000) - - it('sends the specified number of packets', (done) => { - let packetNum = 0 - const count = 3 - - pump( - ipfsA.pingReadableStream(ipfsB.peerId.id, { count }), - new Writable({ - objectMode: true, - write (res, enc, cb) { - expect(res.success).to.be.true() - // It's a pong - if (isPong(res)) { - packetNum++ - } - - cb() - } - }), - (err) => { - expect(err).to.not.exist() - expect(packetNum).to.equal(count) - done() - } - ) - }) - - it('fails when pinging an unknown peer', (done) => { - let messageNum = 0 - const unknownPeerId = 'QmUmaEnH1uMmvckMZbh3yShaasvELPW4ZLPWnB4entMTEn' - const count = 2 - - pump( - ipfsA.pingReadableStream(unknownPeerId, { count }), - new Writable({ - objectMode: true, - write (res, enc, cb) { - expectIsPingResponse(res) - messageNum++ - - // First message should be "looking up" response - if (messageNum === 1) { - expect(res.text).to.include('Looking up') - } - - // Second message should be a failure response - if (messageNum === 2) { - expect(res.success).to.be.false() - } - - cb() - } - }), - (err) => { - expect(err).to.exist() - done() - } - ) - }) - - it('fails when pinging an invalid peer', (done) => { - const invalidPeerId = 'not a peer ID' - const count = 2 - - pump( - ipfsA.pingReadableStream(invalidPeerId, { count }), - new Writable({ - objectMode: true, - write: (chunk, enc, cb) => cb() - }), - (err) => { - expect(err).to.exist() - expect(err.message).to.include('failed to parse peer address') - done() - } - ) - }) - }) - }) -} diff --git a/js/src/ping/index.js b/js/src/ping/index.js new file mode 100644 index 000000000..a33bbddc1 --- /dev/null +++ b/js/src/ping/index.js @@ -0,0 +1,10 @@ +'use strict' +const { createSuite } = require('../utils/suite') + +const tests = { + ping: require('./ping'), + pingPullStream: require('./ping-pull-stream'), + pingReadableStream: require('./ping-readable-stream') +} + +module.exports = createSuite(tests) diff --git a/js/src/ping/ping-pull-stream.js b/js/src/ping/ping-pull-stream.js new file mode 100644 index 000000000..e51c4e95d --- /dev/null +++ b/js/src/ping/ping-pull-stream.js @@ -0,0 +1,101 @@ +/* eslint-env mocha */ +'use strict' + +const pull = require('pull-stream') +const series = require('async/series') +const { spawnNodesWithId } = require('../utils/spawn') +const { getDescribe, getIt, expect } = require('../utils/mocha') +const { expectIsPingResponse, isPong } = require('./utils') + +module.exports = (createCommon, options) => { + const describe = getDescribe(options) + const it = getIt(options) + const common = createCommon() + + describe('.pingPullStream', function () { + this.timeout(15 * 1000) + + let ipfsA + let ipfsB + + before(function (done) { + this.timeout(60 * 1000) + + common.setup((err, factory) => { + if (err) return done(err) + + series([ + (cb) => { + spawnNodesWithId(2, factory, (err, nodes) => { + if (err) return cb(err) + ipfsA = nodes[0] + ipfsB = nodes[1] + cb() + }) + }, + (cb) => ipfsA.swarm.connect(ipfsB.peerId.addresses[0], cb) + ], done) + }) + }) + + after((done) => common.teardown(done)) + + it('should send the specified number of packets over pull stream', (done) => { + let packetNum = 0 + const count = 3 + pull( + ipfsA.pingPullStream(ipfsB.peerId.id, { count }), + pull.drain((res) => { + expect(res.success).to.be.true() + // It's a pong + if (isPong(res)) { + packetNum++ + } + }, (err) => { + expect(err).to.not.exist() + expect(packetNum).to.equal(count) + done() + }) + ) + }) + + it('should fail when pinging an unknown peer over pull stream', (done) => { + let messageNum = 0 + const unknownPeerId = 'QmUmaEnH1uMmvckMZbh3yShaasvELPW4ZLPWnB4entMTEn' + const count = 2 + pull( + ipfsA.pingPullStream(unknownPeerId, { count }), + pull.drain((res) => { + expectIsPingResponse(res) + messageNum++ + + // First message should be "looking up" response + if (messageNum === 1) { + expect(res.text).to.include('Looking up') + } + + // Second message should be a failure response + if (messageNum === 2) { + expect(res.success).to.be.false() + } + }, (err) => { + expect(err).to.exist() + done() + }) + ) + }) + + it('should fail when pinging an invalid peer over pull stream', (done) => { + const invalidPeerId = 'not a peer ID' + const count = 2 + pull( + ipfsA.pingPullStream(invalidPeerId, { count }), + pull.collect((err) => { + expect(err).to.exist() + expect(err.message).to.include('failed to parse peer address') + done() + }) + ) + }) + }) +} diff --git a/js/src/ping/ping-readable-stream.js b/js/src/ping/ping-readable-stream.js new file mode 100644 index 000000000..aad13beec --- /dev/null +++ b/js/src/ping/ping-readable-stream.js @@ -0,0 +1,121 @@ +/* eslint-env mocha */ +'use strict' + +const pump = require('pump') +const { Writable } = require('stream') +const series = require('async/series') +const { spawnNodesWithId } = require('../utils/spawn') +const { getDescribe, getIt, expect } = require('../utils/mocha') +const { expectIsPingResponse, isPong } = require('./utils') + +module.exports = (createCommon, options) => { + const describe = getDescribe(options) + const it = getIt(options) + const common = createCommon() + + describe('.pingReadableStream', function () { + this.timeout(15 * 1000) + + let ipfsA + let ipfsB + + before(function (done) { + this.timeout(60 * 1000) + + common.setup((err, factory) => { + if (err) return done(err) + + series([ + (cb) => { + spawnNodesWithId(2, factory, (err, nodes) => { + if (err) return cb(err) + ipfsA = nodes[0] + ipfsB = nodes[1] + cb() + }) + }, + (cb) => ipfsA.swarm.connect(ipfsB.peerId.addresses[0], cb) + ], done) + }) + }) + + after((done) => common.teardown(done)) + + it('should send the specified number of packets over readable stream', (done) => { + let packetNum = 0 + const count = 3 + + pump( + ipfsA.pingReadableStream(ipfsB.peerId.id, { count }), + new Writable({ + objectMode: true, + write (res, enc, cb) { + expect(res.success).to.be.true() + // It's a pong + if (isPong(res)) { + packetNum++ + } + + cb() + } + }), + (err) => { + expect(err).to.not.exist() + expect(packetNum).to.equal(count) + done() + } + ) + }) + + it('should fail when pinging an unknown peer over readable stream', (done) => { + let messageNum = 0 + const unknownPeerId = 'QmUmaEnH1uMmvckMZbh3yShaasvELPW4ZLPWnB4entMTEn' + const count = 2 + + pump( + ipfsA.pingReadableStream(unknownPeerId, { count }), + new Writable({ + objectMode: true, + write (res, enc, cb) { + expectIsPingResponse(res) + messageNum++ + + // First message should be "looking up" response + if (messageNum === 1) { + expect(res.text).to.include('Looking up') + } + + // Second message should be a failure response + if (messageNum === 2) { + expect(res.success).to.be.false() + } + + cb() + } + }), + (err) => { + expect(err).to.exist() + done() + } + ) + }) + + it('should fail when pinging an invalid peer over readable stream', (done) => { + const invalidPeerId = 'not a peer ID' + const count = 2 + + pump( + ipfsA.pingReadableStream(invalidPeerId, { count }), + new Writable({ + objectMode: true, + write: (chunk, enc, cb) => cb() + }), + (err) => { + expect(err).to.exist() + expect(err.message).to.include('failed to parse peer address') + done() + } + ) + }) + }) +} diff --git a/js/src/ping/ping.js b/js/src/ping/ping.js new file mode 100644 index 000000000..df58b5a5f --- /dev/null +++ b/js/src/ping/ping.js @@ -0,0 +1,75 @@ +/* eslint-env mocha */ +'use strict' + +const series = require('async/series') +const { spawnNodesWithId } = require('../utils/spawn') +const { getDescribe, getIt, expect } = require('../utils/mocha') +const { expectIsPingResponse, isPong } = require('./utils') + +module.exports = (createCommon, options) => { + const describe = getDescribe(options) + const it = getIt(options) + const common = createCommon() + + describe('.ping', function () { + this.timeout(15 * 1000) + + let ipfsA + let ipfsB + + before(function (done) { + this.timeout(60 * 1000) + + common.setup((err, factory) => { + if (err) return done(err) + + series([ + (cb) => { + spawnNodesWithId(2, factory, (err, nodes) => { + if (err) return cb(err) + ipfsA = nodes[0] + ipfsB = nodes[1] + cb() + }) + }, + (cb) => ipfsA.swarm.connect(ipfsB.peerId.addresses[0], cb) + ], done) + }) + }) + + after((done) => common.teardown(done)) + + it('should send the specified number of packets', (done) => { + const count = 3 + ipfsA.ping(ipfsB.peerId.id, { count }, (err, responses) => { + expect(err).to.not.exist() + responses.forEach(expectIsPingResponse) + const pongs = responses.filter(isPong) + expect(pongs.length).to.equal(count) + done() + }) + }) + + it('should fail when pinging an unknown peer', (done) => { + const unknownPeerId = 'QmUmaEnH1uMmvckMZbh3yShaasvELPW4ZLPWnB4entMTEn' + const count = 2 + + ipfsA.ping(unknownPeerId, { count }, (err, responses) => { + expect(err).to.exist() + expect(responses[0].text).to.include('Looking up') + expect(responses[1].success).to.be.false() + done() + }) + }) + + it('should fail when pinging an invalid peer', (done) => { + const invalidPeerId = 'not a peer ID' + const count = 2 + ipfsA.ping(invalidPeerId, { count }, (err, responses) => { + expect(err).to.exist() + expect(err.message).to.include('failed to parse peer address') + done() + }) + }) + }) +} diff --git a/js/src/ping/utils.js b/js/src/ping/utils.js new file mode 100644 index 000000000..8009cada9 --- /dev/null +++ b/js/src/ping/utils.js @@ -0,0 +1,21 @@ +'use strict' + +const { expect } = require('../utils/mocha') + +function expectIsPingResponse (obj) { + expect(obj).to.have.a.property('success') + expect(obj).to.have.a.property('time') + expect(obj).to.have.a.property('text') + expect(obj.success).to.be.a('boolean') + expect(obj.time).to.be.a('number') + expect(obj.text).to.be.a('string') +} + +exports.expectIsPingResponse = expectIsPingResponse + +// Determine if a ping response object is a pong, or something else, like a status message +function isPong (pingResponse) { + return Boolean(pingResponse && pingResponse.success && !pingResponse.text) +} + +exports.isPong = isPong diff --git a/js/src/pubsub.js b/js/src/pubsub.js deleted file mode 100644 index fc97c522e..000000000 --- a/js/src/pubsub.js +++ /dev/null @@ -1,678 +0,0 @@ -/* eslint-env mocha */ -/* eslint max-nested-callbacks: ['error', 8] */ -'use strict' - -const chai = require('chai') -const dirtyChai = require('dirty-chai') -const expect = chai.expect -chai.use(dirtyChai) -const series = require('async/series') -const each = require('async/each') -const parallel = require('async/parallel') -const whilst = require('async/whilst') -const hat = require('hat') -const { spawnNodesWithId } = require('./utils/spawn') - -// On Browsers it will be false, but the tests currently aren't run -// there anyway -let isWindows = process.platform && process.platform === 'win32' - -function waitForPeers (ipfs, topic, peersToWait, callback) { - const i = setInterval(() => { - ipfs.pubsub.peers(topic, (err, peers) => { - if (err) { - clearInterval(i) - return callback(err) - } - - const missingPeers = peersToWait - .map((e) => peers.indexOf(e) !== -1) - .filter((e) => !e) - - if (missingPeers.length === 0) { - clearInterval(i) - callback() - } - }) - }, 500) -} - -function makeCheck (n, done) { - let i = 0 - return (err) => { - if (err) { - return done(err) - } - - if (++i === n) { - done() - } - } -} - -module.exports = (common) => { - describe('.pubsub', function () { - this.timeout(80 * 1000) - - const getTopic = () => 'pubsub-tests-' + hat() - - let ipfs1 - let ipfs2 - let ipfs3 - let withGo - - before(function (done) { - // CI takes longer to instantiate the daemon, so we need to increase the - // timeout for the before step - this.timeout(100 * 1000) - - common.setup((err, factory) => { - if (err) { - return done(err) - } - - spawnNodesWithId(3, factory, (err, nodes) => { - if (err) { - return done(err) - } - - ipfs1 = nodes[0] - ipfs2 = nodes[1] - ipfs3 = nodes[2] - - ipfs1.id((err, id) => { - expect(err).to.not.exist() - withGo = id.agentVersion.startsWith('go-ipfs') - done() - }) - }) - }) - }) - - after((done) => { - common.teardown(done) - }) - - describe('single node', () => { - describe('.publish', () => { - it('errors on string messags', (done) => { - const topic = getTopic() - ipfs1.pubsub.publish(topic, 'hello friend', (err) => { - expect(err).to.exist() - done() - }) - }) - - it('message from buffer', (done) => { - const topic = getTopic() - ipfs1.pubsub.publish(topic, Buffer.from('hello friend'), done) - }) - }) - - describe('.subscribe', () => { - it('to one topic', (done) => { - const check = makeCheck(2, done) - const topic = getTopic() - - const handler = (msg) => { - expect(msg.data.toString()).to.equal('hi') - expect(msg).to.have.property('seqno') - expect(Buffer.isBuffer(msg.seqno)).to.eql(true) - expect(msg).to.have.property('topicIDs').eql([topic]) - expect(msg).to.have.property('from', ipfs1.peerId.id) - - ipfs1.pubsub.unsubscribe(topic, handler, (err) => { - expect(err).to.not.exist() - - ipfs1.pubsub.ls((err, topics) => { - expect(err).to.not.exist() - expect(topics).to.be.empty() - check() - }) - }) - } - - ipfs1.pubsub.subscribe(topic, handler, (err) => { - expect(err).to.not.exist() - ipfs1.pubsub.publish(topic, Buffer.from('hi'), check) - }) - }) - - it('to one topic with Promise', (done) => { - const check = makeCheck(2, done) - const topic = getTopic() - - const handler = (msg) => { - expect(msg.data.toString()).to.equal('hi') - expect(msg).to.have.property('seqno') - expect(Buffer.isBuffer(msg.seqno)).to.eql(true) - expect(msg).to.have.property('topicIDs').eql([topic]) - expect(msg).to.have.property('from', ipfs1.peerId.id) - - ipfs1.pubsub.unsubscribe(topic, handler, (err) => { - expect(err).to.not.exist() - - ipfs1.pubsub.ls((err, topics) => { - expect(err).to.not.exist() - expect(topics).to.be.empty() - check() - }) - }) - } - - ipfs1.pubsub - .subscribe(topic, handler) - .then(() => ipfs1.pubsub.publish(topic, Buffer.from('hi'), check)) - .catch((err) => expect(err).to.not.exist()) - }) - - it('to one topic with options and Promise', (done) => { - const check = makeCheck(2, done) - const topic = getTopic() - - const handler = (msg) => { - expect(msg.data.toString()).to.equal('hi') - expect(msg).to.have.property('seqno') - expect(Buffer.isBuffer(msg.seqno)).to.eql(true) - expect(msg).to.have.property('topicIDs').eql([topic]) - expect(msg).to.have.property('from', ipfs1.peerId.id) - - ipfs1.pubsub.unsubscribe(topic, handler, (err) => { - expect(err).to.not.exist() - - ipfs1.pubsub.ls((err, topics) => { - expect(err).to.not.exist() - expect(topics).to.be.empty() - check() - }) - }) - } - - ipfs1.pubsub - .subscribe(topic, handler, {}) - .then(() => ipfs1.pubsub.publish(topic, Buffer.from('hi'), check)) - .catch((err) => expect(err).to.not.exist()) - }) - - it('attaches multiple event listeners', (done) => { - const topic = getTopic() - - const check = makeCheck(3, done) - const handler1 = (msg) => { - expect(msg.data.toString()).to.eql('hello') - - series([ - (cb) => ipfs1.pubsub.unsubscribe(topic, handler1, cb), - (cb) => ipfs1.pubsub.ls(cb), - (cb) => ipfs1.pubsub.unsubscribe(topic, handler2, cb), - (cb) => ipfs1.pubsub.ls(cb) - ], (err, res) => { - expect(err).to.not.exist() - - // Still subscribed as there is one listener left - expect(res[1]).to.eql([topic]) - // Now all listeners are gone no subscription anymore - expect(res[3]).to.eql([]) - check() - }) - } - - const handler2 = (msg) => { - expect(msg.data.toString()).to.eql('hello') - check() - } - - parallel([ - (cb) => ipfs1.pubsub.subscribe(topic, handler1, cb), - (cb) => ipfs1.pubsub.subscribe(topic, handler2, cb) - ], (err) => { - expect(err).to.not.exist() - ipfs1.pubsub.publish(topic, Buffer.from('hello'), check) - }) - }) - - it('discover options', (done) => { - const check = makeCheck(2, done) - const topic = getTopic() - - const handler = (msg) => { - expect(msg.data.toString()).to.eql('hi') - ipfs1.pubsub.unsubscribe(topic, handler, check) - } - - ipfs1.pubsub.subscribe(topic, handler, { discover: true }, (err) => { - expect(err).to.not.exist() - ipfs1.pubsub.publish(topic, Buffer.from('hi'), check) - }) - }) - }) - }) - - describe('multiple nodes connected', () => { - before((done) => { - parallel([ - (cb) => ipfs1.swarm.connect(ipfs2.peerId.addresses.find((a) => a.includes('127.0.0.1')), cb), - (cb) => ipfs2.swarm.connect(ipfs3.peerId.addresses.find((a) => a.includes('127.0.0.1')), cb), - (cb) => ipfs1.swarm.connect(ipfs3.peerId.addresses.find((a) => a.includes('127.0.0.1')), cb) - ], (err) => { - if (err) { - return done(err) - } - // give some time to let everything connect - setTimeout(done, 300) - }) - }) - - describe('.peers', () => { - it('does not error when not subscribed to a topic', (done) => { - const topic = getTopic() - ipfs1.pubsub.peers(topic, (err, peers) => { - expect(err).to.not.exist() - // Should be empty() but as mentioned below go-ipfs returns more than it should - // expect(peers).to.be.empty() - - done() - }) - }) - - it("doesn't return extra peers", (done) => { - // Currently go-ipfs returns peers that have not been - // subscribed to the topic. Enable when go-ipfs has been fixed - const sub1 = (msg) => {} - const sub2 = (msg) => {} - const sub3 = (msg) => {} - - const topic = getTopic() - const topicOther = topic + 'different topic' - - series([ - (cb) => ipfs1.pubsub.subscribe(topic, sub1, cb), - (cb) => ipfs2.pubsub.subscribe(topicOther, sub2, cb), - (cb) => ipfs3.pubsub.subscribe(topicOther, sub3, cb) - ], (err) => { - expect(err).to.not.exist() - - ipfs1.pubsub.peers(topic, (err, peers) => { - expect(err).to.not.exist() - expect(peers).to.be.empty() - - parallel([ - (cb) => ipfs1.pubsub.unsubscribe(topic, sub1, cb), - (cb) => ipfs2.pubsub.unsubscribe(topicOther, sub2, cb), - (cb) => ipfs3.pubsub.unsubscribe(topicOther, sub3, cb) - ], done) - }) - }) - }) - - it('returns peers for a topic - one peer', (done) => { - // Currently go-ipfs returns peers that have not been - // subscribed to the topic. Enable when go-ipfs has been fixed - const sub1 = (msg) => {} - const sub2 = (msg) => {} - const sub3 = (msg) => {} - const topic = getTopic() - - series([ - (cb) => ipfs1.pubsub.subscribe(topic, sub1, cb), - (cb) => ipfs2.pubsub.subscribe(topic, sub2, cb), - (cb) => ipfs3.pubsub.subscribe(topic, sub3, cb), - (cb) => waitForPeers(ipfs1, topic, [ipfs2.peerId.id], cb) - ], (err) => { - expect(err).to.not.exist() - - parallel([ - (cb) => ipfs1.pubsub.unsubscribe(topic, sub1, cb), - (cb) => ipfs2.pubsub.unsubscribe(topic, sub2, cb), - (cb) => ipfs3.pubsub.unsubscribe(topic, sub3, cb) - ], done) - }) - }) - - it('lists peers for a topic - multiple peers', (done) => { - const sub1 = (msg) => {} - const sub2 = (msg) => {} - const sub3 = (msg) => {} - const topic = getTopic() - - series([ - (cb) => ipfs1.pubsub.subscribe(topic, sub1, cb), - (cb) => ipfs2.pubsub.subscribe(topic, sub2, cb), - (cb) => ipfs3.pubsub.subscribe(topic, sub3, cb), - (cb) => waitForPeers(ipfs1, topic, [ - ipfs2.peerId.id, - ipfs3.peerId.id - ], cb) - ], (err) => { - expect(err).to.not.exist() - - parallel([ - (cb) => ipfs1.pubsub.unsubscribe(topic, sub1, cb), - (cb) => ipfs2.pubsub.unsubscribe(topic, sub2, cb), - (cb) => ipfs3.pubsub.unsubscribe(topic, sub3, cb) - ], done) - }) - }) - }) - - describe('.ls', () => { - it('empty() list when no topics are subscribed', (done) => { - ipfs1.pubsub.ls((err, topics) => { - expect(err).to.not.exist() - expect(topics.length).to.equal(0) - done() - }) - }) - - it('list with 1 subscribed topic', (done) => { - const sub1 = (msg) => {} - const topic = getTopic() - - ipfs1.pubsub.subscribe(topic, sub1, (err) => { - expect(err).to.not.exist() - - ipfs1.pubsub.ls((err, topics) => { - expect(err).to.not.exist() - expect(topics).to.be.eql([topic]) - - ipfs1.pubsub.unsubscribe(topic, sub1, done) - }) - }) - }) - - it('list with 3 subscribed topics', (done) => { - const topics = [{ - name: 'one', - handler () {} - }, { - name: 'two', - handler () {} - }, { - name: 'three', - handler () {} - }] - - each(topics, (t, cb) => { - ipfs1.pubsub.subscribe(t.name, t.handler, cb) - }, (err) => { - expect(err).to.not.exist() - ipfs1.pubsub.ls((err, list) => { - expect(err).to.not.exist() - - expect(list.sort()) - .to.eql(topics.map((t) => t.name).sort()) - - parallel(topics.map((t) => { - return (cb) => ipfs1.pubsub.unsubscribe(t.name, t.handler, cb) - }), done) - }) - }) - }) - }) - - describe('multiple nodes', () => { - let topic - let sub1 - let sub2 - - beforeEach(() => { - topic = getTopic() - }) - - afterEach((done) => { - parallel([ - (cb) => ipfs1.pubsub.unsubscribe(topic, sub1, cb), - (cb) => ipfs2.pubsub.unsubscribe(topic, sub2, cb) - ], done) - }) - - it('receive messages from different node', (done) => { - const check = makeCheck(3, done) - const expectedString = 'hello from the other side' - - sub1 = (msg) => { - expect(msg.data.toString()).to.be.eql(expectedString) - expect(msg.from).to.eql(ipfs2.peerId.id) - check() - } - - sub2 = (msg) => { - expect(msg.data.toString()).to.be.eql(expectedString) - expect(msg.from).to.eql(ipfs2.peerId.id) - check() - } - - series([ - (cb) => ipfs1.pubsub.subscribe(topic, sub1, cb), - (cb) => ipfs2.pubsub.subscribe(topic, sub2, cb), - (cb) => waitForPeers(ipfs2, topic, [ipfs1.peerId.id], cb) - ], (err) => { - expect(err).to.not.exist() - - ipfs2.pubsub.publish(topic, Buffer.from(expectedString), check) - }) - }) - - it('round trips a non-utf8 binary buffer correctly', (done) => { - const check = makeCheck(3, done) - const expectedHex = 'a36161636179656162830103056164a16466666666f4' - const buffer = Buffer.from(expectedHex, 'hex') - - sub1 = (msg) => { - try { - expect(msg.data.toString('hex')).to.be.eql(expectedHex) - expect(msg.from).to.eql(ipfs2.peerId.id) - check() - } catch (err) { - check(err) - } - } - - sub2 = (msg) => { - try { - expect(msg.data.toString('hex')).to.eql(expectedHex) - expect(msg.from).to.eql(ipfs2.peerId.id) - check() - } catch (err) { - check(err) - } - } - - series([ - (cb) => ipfs1.pubsub.subscribe(topic, sub1, cb), - (cb) => ipfs2.pubsub.subscribe(topic, sub2, cb), - (cb) => waitForPeers(ipfs2, topic, [ipfs1.peerId.id], cb) - ], (err) => { - expect(err).to.not.exist() - - ipfs2.pubsub.publish(topic, buffer, check) - }) - }) - - it('receive multiple messages', function (done) { - // TODO fix https://github.com/ipfs/interface-ipfs-core/pull/188#issuecomment-354673246 - // and https://github.com/ipfs/go-ipfs/issues/4778 - if (withGo && isWindows) { - this.skip() - } - - const inbox1 = [] - const inbox2 = [] - const outbox = ['hello', 'world', 'this', 'is', 'pubsub'] - - const check = makeCheck(outbox.length * 3, (err) => { - expect(inbox1.sort()).to.eql(outbox.sort()) - expect(inbox2.sort()).to.eql(outbox.sort()) - - done(err) - }) - - sub1 = (msg) => { - inbox1.push(msg.data.toString()) - expect(msg.from).to.eql(ipfs2.peerId.id) - check() - } - - sub2 = (msg) => { - inbox2.push(msg.data.toString()) - expect(msg.from).to.be.eql(ipfs2.peerId.id) - check() - } - - series([ - (cb) => ipfs1.pubsub.subscribe(topic, sub1, cb), - (cb) => ipfs2.pubsub.subscribe(topic, sub2, cb), - (cb) => waitForPeers(ipfs2, topic, [ipfs1.peerId.id], cb) - ], (err) => { - expect(err).to.not.exist() - - outbox.forEach((msg) => { - ipfs2.pubsub.publish(topic, Buffer.from(msg), check) - }) - }) - }) - }) - - describe('light-load tests', function () { - before(() => { - ipfs1.pubsub.setMaxListeners(10 * 10) - ipfs2.pubsub.setMaxListeners(10 * 10) - }) - - after(() => { - ipfs1.pubsub.setMaxListeners(10) - ipfs2.pubsub.setMaxListeners(10) - }) - - it('call publish 10 times', (done) => { - const count = 10 - let sendCount = 0 - const topic = getTopic() - - whilst( - () => sendCount < count, - (cb) => { - sendCount++ - ipfs1.pubsub.publish(topic, Buffer.from('hey there'), cb) - }, - done - ) - }) - - describe('send/receive', () => { - let topic - let sub1 - let sub2 - - beforeEach(function () { - // TODO fix https://github.com/ipfs/interface-ipfs-core/pull/188#issuecomment-354673246 - // and https://github.com/ipfs/go-ipfs/issues/4778 - if (withGo && isWindows) { - this.skip() - } - - topic = getTopic() - }) - - afterEach((done) => { - parallel([ - (cb) => ipfs1.pubsub.unsubscribe(topic, sub1, cb), - (cb) => ipfs2.pubsub.unsubscribe(topic, sub2, cb) - ], done) - }) - - it('send/receive 100 messages', function (done) { - this.timeout(2 * 60 * 1000) - - const msgBase = 'msg - ' - const count = 100 - let sendCount = 0 - let receivedCount = 0 - let startTime - let counter = 0 - - sub1 = (msg) => { - // go-ipfs can't send messages in order when there are - // only two nodes in the same machine ¯\_(ツ)_/¯ - // https://github.com/ipfs/js-ipfs-api/pull/493#issuecomment-289499943 - // const expectedMsg = msgBase + receivedCount - // const receivedMsg = msg.data.toString() - // expect(receivedMsg).to.eql(expectedMsg) - - receivedCount++ - - if (receivedCount >= count) { - const duration = new Date().getTime() - startTime - const opsPerSec = Math.floor(count / (duration / 1000)) - - console.log(`Send/Receive 100 messages took: ${duration} ms, ${opsPerSec} ops / s\n`) - - check() - } - } - - sub2 = (msg) => {} - - function check () { - if (++counter === 2) { - done() - } - } - - series([ - (cb) => ipfs1.pubsub.subscribe(topic, sub1, cb), - (cb) => ipfs2.pubsub.subscribe(topic, sub2, cb), - (cb) => waitForPeers(ipfs1, topic, [ipfs2.peerId.id], cb) - ], (err) => { - expect(err).to.not.exist() - startTime = new Date().getTime() - - whilst( - () => sendCount < count, - (cb) => { - const msgData = Buffer.from(msgBase + sendCount) - sendCount++ - ipfs2.pubsub.publish(topic, msgData, cb) - }, - check - ) - }) - }) - }) - - it('call subscribe/unsubscribe 10 times', (done) => { - const count = 10 - let sendCount = 0 - const handlers = [] - - const someTopic = getTopic() - - whilst( - () => sendCount < count, - (cb) => { - sendCount++ - const handler = (msg) => {} - handlers.push(handler) - ipfs1.pubsub.subscribe(someTopic, handler, cb) - }, - (err) => { - expect(err).to.not.exist() - each( - handlers, - (handler, cb) => ipfs1.pubsub.unsubscribe(someTopic, handler, cb), - (err) => { - expect(err).to.not.exist() - ipfs1.pubsub.ls((err, topics) => { - expect(err).to.not.exist() - expect(topics).to.eql([]) - done() - }) - } - ) - } - ) - }) - }) - }) - }) -} diff --git a/js/src/pubsub/index.js b/js/src/pubsub/index.js new file mode 100644 index 000000000..da5469773 --- /dev/null +++ b/js/src/pubsub/index.js @@ -0,0 +1,12 @@ +'use strict' +const { createSuite } = require('../utils/suite') + +const tests = { + publish: require('./publish'), + subscribe: require('./subscribe'), + unsubscribe: require('./unsubscribe'), + peers: require('./peers'), + ls: require('./ls') +} + +module.exports = createSuite(tests) diff --git a/js/src/pubsub/ls.js b/js/src/pubsub/ls.js new file mode 100644 index 000000000..2b7162270 --- /dev/null +++ b/js/src/pubsub/ls.js @@ -0,0 +1,89 @@ +/* eslint-env mocha */ +'use strict' + +const each = require('async/each') +const { getTopic } = require('./utils') +const { getDescribe, getIt, expect } = require('../utils/mocha') + +module.exports = (createCommon, options) => { + const describe = getDescribe(options) + const it = getIt(options) + const common = createCommon() + + describe('.pubsub.ls', function () { + this.timeout(80 * 1000) + + let ipfs + + before(function (done) { + // CI takes longer to instantiate the daemon, so we need to increase the + // timeout for the before step + this.timeout(60 * 1000) + + common.setup((err, factory) => { + expect(err).to.not.exist() + factory.spawnNode((err, node) => { + expect(err).to.not.exist() + ipfs = node + done() + }) + }) + }) + + after((done) => common.teardown(done)) + + it('should return an empty list when no topics are subscribed', (done) => { + ipfs.pubsub.ls((err, topics) => { + expect(err).to.not.exist() + expect(topics.length).to.equal(0) + done() + }) + }) + + it('should return a list with 1 subscribed topic', (done) => { + const sub1 = (msg) => {} + const topic = getTopic() + + ipfs.pubsub.subscribe(topic, sub1, (err) => { + expect(err).to.not.exist() + + ipfs.pubsub.ls((err, topics) => { + expect(err).to.not.exist() + expect(topics).to.be.eql([topic]) + + ipfs.pubsub.unsubscribe(topic, sub1, done) + }) + }) + }) + + it('should return a list with 3 subscribed topics', (done) => { + const topics = [{ + name: 'one', + handler () {} + }, { + name: 'two', + handler () {} + }, { + name: 'three', + handler () {} + }] + + each(topics, (t, cb) => { + ipfs.pubsub.subscribe(t.name, t.handler, cb) + }, (err) => { + expect(err).to.not.exist() + + ipfs.pubsub.ls((err, list) => { + expect(err).to.not.exist() + + expect(list.sort()) + .to.eql(topics.map((t) => t.name).sort()) + + each(topics, (t, cb) => { + ipfs.pubsub.unsubscribe(t.name, t.handler, cb) + }, done) + }) + }) + }) + }) +} diff --git a/js/src/pubsub/peers.js b/js/src/pubsub/peers.js new file mode 100644 index 000000000..7b6576f41 --- /dev/null +++ b/js/src/pubsub/peers.js @@ -0,0 +1,149 @@ +/* eslint-env mocha */ +'use strict' + +const parallel = require('async/parallel') +const auto = require('async/auto') +const { spawnNodesWithId } = require('../utils/spawn') +const { waitForPeers, getTopic } = require('./utils') +const { getDescribe, getIt, expect } = require('../utils/mocha') + +module.exports = (createCommon, options) => { + const describe = getDescribe(options) + const it = getIt(options) + const common = createCommon() + + describe('.pubsub.peers', function () { + this.timeout(80 * 1000) + + let ipfs1 + let ipfs2 + let ipfs3 + + before(function (done) { + // CI takes longer to instantiate the daemon, so we need to increase the + // timeout for the before step + this.timeout(100 * 1000) + + common.setup((err, factory) => { + if (err) return done(err) + + spawnNodesWithId(3, factory, (err, nodes) => { + if (err) return done(err) + + ipfs1 = nodes[0] + ipfs2 = nodes[1] + ipfs3 = nodes[2] + + done() + }) + }) + }) + + after((done) => common.teardown(done)) + + before((done) => { + const ipfs2Addr = ipfs2.peerId.addresses.find((a) => a.includes('127.0.0.1')) + const ipfs3Addr = ipfs3.peerId.addresses.find((a) => a.includes('127.0.0.1')) + + parallel([ + (cb) => ipfs1.swarm.connect(ipfs2Addr, cb), + (cb) => ipfs1.swarm.connect(ipfs3Addr, cb), + (cb) => ipfs2.swarm.connect(ipfs3Addr, cb) + ], done) + }) + + it('should not error when not subscribed to a topic', (done) => { + const topic = getTopic() + ipfs1.pubsub.peers(topic, (err, peers) => { + expect(err).to.not.exist() + // Should be empty() but as mentioned below go-ipfs returns more than it should + // expect(peers).to.be.empty() + + done() + }) + }) + + it('should not return extra peers', (done) => { + // Currently go-ipfs returns peers that have not been + // subscribed to the topic. Enable when go-ipfs has been fixed + const sub1 = (msg) => {} + const sub2 = (msg) => {} + const sub3 = (msg) => {} + + const topic = getTopic() + const topicOther = topic + 'different topic' + + parallel([ + (cb) => ipfs1.pubsub.subscribe(topic, sub1, cb), + (cb) => ipfs2.pubsub.subscribe(topicOther, sub2, cb), + (cb) => ipfs3.pubsub.subscribe(topicOther, sub3, cb) + ], (err) => { + expect(err).to.not.exist() + + ipfs1.pubsub.peers(topic, (err, peers) => { + expect(err).to.not.exist() + expect(peers).to.be.empty() + + parallel([ + (cb) => ipfs1.pubsub.unsubscribe(topic, sub1, cb), + (cb) => ipfs2.pubsub.unsubscribe(topicOther, sub2, cb), + (cb) => ipfs3.pubsub.unsubscribe(topicOther, sub3, cb) + ], done) + }) + }) + }) + + it('should return peers for a topic - one peer', (done) => { + // Currently go-ipfs returns peers that have not been + // subscribed to the topic. Enable when go-ipfs has been fixed + const sub1 = (msg) => {} + const sub2 = (msg) => {} + const sub3 = (msg) => {} + const topic = getTopic() + + auto({ + sub1: (cb) => ipfs1.pubsub.subscribe(topic, sub1, cb), + sub2: (cb) => ipfs2.pubsub.subscribe(topic, sub2, cb), + sub3: (cb) => ipfs3.pubsub.subscribe(topic, sub3, cb), + peers: ['sub1', 'sub2', 'sub3', (_, cb) => { + waitForPeers(ipfs1, topic, [ipfs2.peerId.id], cb) + }] + }, (err) => { + expect(err).to.not.exist() + + parallel([ + (cb) => ipfs1.pubsub.unsubscribe(topic, sub1, cb), + (cb) => ipfs2.pubsub.unsubscribe(topic, sub2, cb), + (cb) => ipfs3.pubsub.unsubscribe(topic, sub3, cb) + ], done) + }) + }) + + it('should return peers for a topic - multiple peers', (done) => { + const sub1 = (msg) => {} + const sub2 = (msg) => {} + const sub3 = (msg) => {} + const topic = getTopic() + + auto({ + sub1: (cb) => ipfs1.pubsub.subscribe(topic, sub1, cb), + sub2: (cb) => ipfs2.pubsub.subscribe(topic, sub2, cb), + sub3: (cb) => ipfs3.pubsub.subscribe(topic, sub3, cb), + peers: ['sub1', 'sub2', 'sub3', (_, cb) => { + waitForPeers(ipfs1, topic, [ + ipfs2.peerId.id, + ipfs3.peerId.id + ], cb) + }] + }, (err) => { + expect(err).to.not.exist() + + parallel([ + (cb) => ipfs1.pubsub.unsubscribe(topic, sub1, cb), + (cb) => ipfs2.pubsub.unsubscribe(topic, sub2, cb), + (cb) => ipfs3.pubsub.unsubscribe(topic, sub3, cb) + ], done) + }) + }) + }) +} diff --git a/js/src/pubsub/publish.js b/js/src/pubsub/publish.js new file mode 100644 index 000000000..17522d088 --- /dev/null +++ b/js/src/pubsub/publish.js @@ -0,0 +1,58 @@ +/* eslint-env mocha */ +'use strict' + +const times = require('async/times') +const hat = require('hat') +const { getTopic } = require('./utils') +const { getDescribe, getIt, expect } = require('../utils/mocha') + +module.exports = (createCommon, options) => { + const describe = getDescribe(options) + const it = getIt(options) + const common = createCommon() + + describe('.pubsub.publish', function () { + this.timeout(80 * 1000) + + let ipfs + + before(function (done) { + // CI takes longer to instantiate the daemon, so we need to increase the + // timeout for the before step + this.timeout(60 * 1000) + + common.setup((err, factory) => { + expect(err).to.not.exist() + factory.spawnNode((err, node) => { + expect(err).to.not.exist() + ipfs = node + done() + }) + }) + }) + + after((done) => common.teardown(done)) + + it('should error on string messags', (done) => { + const topic = getTopic() + ipfs.pubsub.publish(topic, 'hello friend', (err) => { + expect(err).to.exist() + done() + }) + }) + + it('should publish message from buffer', (done) => { + const topic = getTopic() + ipfs.pubsub.publish(topic, Buffer.from(hat()), done) + }) + + it('should publish 10 times within time limit', (done) => { + const count = 10 + const topic = getTopic() + + times(count, (_, cb) => { + ipfs.pubsub.publish(topic, Buffer.from(hat()), cb) + }, done) + }) + }) +} diff --git a/js/src/pubsub/subscribe.js b/js/src/pubsub/subscribe.js new file mode 100644 index 000000000..fc726d4ea --- /dev/null +++ b/js/src/pubsub/subscribe.js @@ -0,0 +1,400 @@ +/* eslint-env mocha */ +'use strict' + +const series = require('async/series') +const parallel = require('async/parallel') +const times = require('async/times') +const auto = require('async/auto') +const { spawnNodesWithId } = require('../utils/spawn') +const { waitForPeers, makeCheck, getTopic } = require('./utils') +const { getDescribe, getIt, expect } = require('../utils/mocha') + +module.exports = (createCommon, options) => { + const describe = getDescribe(options) + const it = getIt(options) + const common = createCommon() + + describe('.pubsub.subscribe', function () { + this.timeout(80 * 1000) + + let ipfs1 + let ipfs2 + + before(function (done) { + // CI takes longer to instantiate the daemon, so we need to increase the + // timeout for the before step + this.timeout(100 * 1000) + + common.setup((err, factory) => { + if (err) return done(err) + + spawnNodesWithId(2, factory, (err, nodes) => { + if (err) return done(err) + + ipfs1 = nodes[0] + ipfs2 = nodes[1] + + done() + }) + }) + }) + + after((done) => common.teardown(done)) + + describe('single node', () => { + it('should subscribe to one topic', (done) => { + const check = makeCheck(2, done) + const topic = getTopic() + + const handler = (msg) => { + expect(msg.data.toString()).to.equal('hi') + expect(msg).to.have.property('seqno') + expect(Buffer.isBuffer(msg.seqno)).to.eql(true) + expect(msg).to.have.property('topicIDs').eql([topic]) + expect(msg).to.have.property('from', ipfs1.peerId.id) + + ipfs1.pubsub.unsubscribe(topic, handler, (err) => { + expect(err).to.not.exist() + + ipfs1.pubsub.ls((err, topics) => { + expect(err).to.not.exist() + expect(topics).to.be.empty() + check() + }) + }) + } + + ipfs1.pubsub.subscribe(topic, handler, (err) => { + expect(err).to.not.exist() + ipfs1.pubsub.publish(topic, Buffer.from('hi'), check) + }) + }) + + it('should subscribe to one topic (promised)', (done) => { + const check = makeCheck(2, done) + const topic = getTopic() + + const handler = (msg) => { + expect(msg.data.toString()).to.equal('hi') + expect(msg).to.have.property('seqno') + expect(Buffer.isBuffer(msg.seqno)).to.eql(true) + expect(msg).to.have.property('topicIDs').eql([topic]) + expect(msg).to.have.property('from', ipfs1.peerId.id) + + ipfs1.pubsub.unsubscribe(topic, handler, (err) => { + expect(err).to.not.exist() + + ipfs1.pubsub.ls((err, topics) => { + expect(err).to.not.exist() + expect(topics).to.be.empty() + check() + }) + }) + } + + ipfs1.pubsub + .subscribe(topic, handler) + .then(() => ipfs1.pubsub.publish(topic, Buffer.from('hi'), check)) + .catch((err) => expect(err).to.not.exist()) + }) + + it('should subscribe to one topic with options', (done) => { + const check = makeCheck(2, done) + const topic = getTopic() + + const handler = (msg) => { + expect(msg.data.toString()).to.equal('hi') + expect(msg).to.have.property('seqno') + expect(Buffer.isBuffer(msg.seqno)).to.eql(true) + expect(msg).to.have.property('topicIDs').eql([topic]) + expect(msg).to.have.property('from', ipfs1.peerId.id) + + ipfs1.pubsub.unsubscribe(topic, handler, (err) => { + expect(err).to.not.exist() + + ipfs1.pubsub.ls((err, topics) => { + expect(err).to.not.exist() + expect(topics).to.be.empty() + check() + }) + }) + } + + ipfs1.pubsub.subscribe(topic, handler, {}, (err) => { + expect(err).to.not.exist() + ipfs1.pubsub.publish(topic, Buffer.from('hi'), check) + }) + }) + + it('should subscribe to one topic with options (promised)', (done) => { + const check = makeCheck(2, done) + const topic = getTopic() + + const handler = (msg) => { + expect(msg.data.toString()).to.equal('hi') + expect(msg).to.have.property('seqno') + expect(Buffer.isBuffer(msg.seqno)).to.eql(true) + expect(msg).to.have.property('topicIDs').eql([topic]) + expect(msg).to.have.property('from', ipfs1.peerId.id) + + ipfs1.pubsub.unsubscribe(topic, handler, (err) => { + expect(err).to.not.exist() + + ipfs1.pubsub.ls((err, topics) => { + expect(err).to.not.exist() + expect(topics).to.be.empty() + check() + }) + }) + } + + ipfs1.pubsub + .subscribe(topic, handler, {}) + .then(() => ipfs1.pubsub.publish(topic, Buffer.from('hi'), check)) + .catch((err) => expect(err).to.not.exist()) + }) + + it('should subscribe to topic multiple times with different handlers', (done) => { + const topic = getTopic() + + const check = makeCheck(3, done) + const handler1 = (msg) => { + expect(msg.data.toString()).to.eql('hello') + + series([ + (cb) => ipfs1.pubsub.unsubscribe(topic, handler1, cb), + (cb) => ipfs1.pubsub.ls(cb), + (cb) => ipfs1.pubsub.unsubscribe(topic, handler2, cb), + (cb) => ipfs1.pubsub.ls(cb) + ], (err, res) => { + expect(err).to.not.exist() + + // Still subscribed as there is one listener left + expect(res[1]).to.eql([topic]) + // Now all listeners are gone no subscription anymore + expect(res[3]).to.eql([]) + check() + }) + } + + const handler2 = (msg) => { + expect(msg.data.toString()).to.eql('hello') + check() + } + + parallel([ + (cb) => ipfs1.pubsub.subscribe(topic, handler1, cb), + (cb) => ipfs1.pubsub.subscribe(topic, handler2, cb) + ], (err) => { + expect(err).to.not.exist() + ipfs1.pubsub.publish(topic, Buffer.from('hello'), check) + }) + }) + + it('should allow discover option to be passed', (done) => { + const check = makeCheck(2, done) + const topic = getTopic() + + const handler = (msg) => { + expect(msg.data.toString()).to.eql('hi') + ipfs1.pubsub.unsubscribe(topic, handler, check) + } + + ipfs1.pubsub.subscribe(topic, handler, { discover: true }, (err) => { + expect(err).to.not.exist() + ipfs1.pubsub.publish(topic, Buffer.from('hi'), check) + }) + }) + }) + + describe('multiple connected nodes', () => { + before((done) => { + if (ipfs1.pubsub.setMaxListeners) { + ipfs1.pubsub.setMaxListeners(100) + } + + if (ipfs2.pubsub.setMaxListeners) { + ipfs2.pubsub.setMaxListeners(100) + } + + const ipfs2Addr = ipfs2.peerId.addresses.find((a) => a.includes('127.0.0.1')) + ipfs1.swarm.connect(ipfs2Addr, done) + }) + + let topic + let sub1 + let sub2 + + beforeEach(() => { + topic = getTopic() + }) + + afterEach((done) => { + parallel([ + (cb) => ipfs1.pubsub.unsubscribe(topic, sub1, cb), + (cb) => ipfs2.pubsub.unsubscribe(topic, sub2, cb) + ], done) + }) + + it('should receive messages from a different node', (done) => { + const check = makeCheck(3, done) + const expectedString = 'hello from the other side' + + sub1 = (msg) => { + expect(msg.data.toString()).to.be.eql(expectedString) + expect(msg.from).to.eql(ipfs2.peerId.id) + check() + } + + sub2 = (msg) => { + expect(msg.data.toString()).to.be.eql(expectedString) + expect(msg.from).to.eql(ipfs2.peerId.id) + check() + } + + auto({ + sub1: (cb) => ipfs1.pubsub.subscribe(topic, sub1, cb), + sub2: (cb) => ipfs2.pubsub.subscribe(topic, sub2, cb), + peers: ['sub1', 'sub2', (_, cb) => { + waitForPeers(ipfs2, topic, [ipfs1.peerId.id], cb) + }] + }, (err) => { + expect(err).to.not.exist() + + ipfs2.pubsub.publish(topic, Buffer.from(expectedString), check) + }) + }) + + it('should round trip a non-utf8 binary buffer', (done) => { + const check = makeCheck(3, done) + const expectedHex = 'a36161636179656162830103056164a16466666666f4' + const buffer = Buffer.from(expectedHex, 'hex') + + sub1 = (msg) => { + try { + expect(msg.data.toString('hex')).to.be.eql(expectedHex) + expect(msg.from).to.eql(ipfs2.peerId.id) + check() + } catch (err) { + check(err) + } + } + + sub2 = (msg) => { + try { + expect(msg.data.toString('hex')).to.eql(expectedHex) + expect(msg.from).to.eql(ipfs2.peerId.id) + check() + } catch (err) { + check(err) + } + } + + auto({ + sub1: (cb) => ipfs1.pubsub.subscribe(topic, sub1, cb), + sub2: (cb) => ipfs2.pubsub.subscribe(topic, sub2, cb), + peers: ['sub1', 'sub2', (_, cb) => { + waitForPeers(ipfs2, topic, [ipfs1.peerId.id], cb) + }] + }, (err) => { + expect(err).to.not.exist() + + ipfs2.pubsub.publish(topic, buffer, check) + }) + }) + + it('should receive multiple messages', (done) => { + const inbox1 = [] + const inbox2 = [] + const outbox = ['hello', 'world', 'this', 'is', 'pubsub'] + + const check = makeCheck(outbox.length * 3, (err) => { + expect(inbox1.sort()).to.eql(outbox.sort()) + expect(inbox2.sort()).to.eql(outbox.sort()) + + done(err) + }) + + sub1 = (msg) => { + inbox1.push(msg.data.toString()) + expect(msg.from).to.eql(ipfs2.peerId.id) + check() + } + + sub2 = (msg) => { + inbox2.push(msg.data.toString()) + expect(msg.from).to.be.eql(ipfs2.peerId.id) + check() + } + + auto({ + sub1: (cb) => ipfs1.pubsub.subscribe(topic, sub1, cb), + sub2: (cb) => ipfs2.pubsub.subscribe(topic, sub2, cb), + peers: ['sub1', 'sub2', (_, cb) => { + waitForPeers(ipfs2, topic, [ipfs1.peerId.id], cb) + }] + }, (err) => { + expect(err).to.not.exist() + + outbox.forEach((msg) => { + ipfs2.pubsub.publish(topic, Buffer.from(msg), check) + }) + }) + }) + + it('send/receive 100 messages', function (done) { + this.timeout(2 * 60 * 1000) + + const msgBase = 'msg - ' + const count = 100 + let receivedCount = 0 + let startTime + let counter = 0 + + sub1 = (msg) => { + // go-ipfs can't send messages in order when there are + // only two nodes in the same machine ¯\_(ツ)_/¯ + // https://github.com/ipfs/js-ipfs-api/pull/493#issuecomment-289499943 + // const expectedMsg = msgBase + receivedCount + // const receivedMsg = msg.data.toString() + // expect(receivedMsg).to.eql(expectedMsg) + + receivedCount++ + + if (receivedCount >= count) { + const duration = new Date().getTime() - startTime + const opsPerSec = Math.floor(count / (duration / 1000)) + + console.log(`Send/Receive 100 messages took: ${duration} ms, ${opsPerSec} ops / s`) + + check() + } + } + + sub2 = (msg) => {} + + function check () { + if (++counter === 2) { + done() + } + } + + auto({ + sub1: (cb) => ipfs1.pubsub.subscribe(topic, sub1, cb), + sub2: (cb) => ipfs2.pubsub.subscribe(topic, sub2, cb), + peers: ['sub1', 'sub2', (_, cb) => { + waitForPeers(ipfs1, topic, [ipfs2.peerId.id], cb) + }] + }, (err) => { + expect(err).to.not.exist() + startTime = new Date().getTime() + + times(count, (sendCount, cb) => { + const msgData = Buffer.from(msgBase + sendCount) + ipfs2.pubsub.publish(topic, msgData, cb) + }, check) + }) + }) + }) + }) +} diff --git a/js/src/pubsub/unsubscribe.js b/js/src/pubsub/unsubscribe.js new file mode 100644 index 000000000..70fcea30d --- /dev/null +++ b/js/src/pubsub/unsubscribe.js @@ -0,0 +1,61 @@ +/* eslint-env mocha */ +'use strict' + +const each = require('async/each') +const times = require('async/times') +const { getTopic } = require('./utils') +const { getDescribe, getIt, expect } = require('../utils/mocha') + +module.exports = (createCommon, options) => { + const describe = getDescribe(options) + const it = getIt(options) + const common = createCommon() + + describe('.pubsub.unsubscribe', function () { + this.timeout(80 * 1000) + + let ipfs + + before(function (done) { + // CI takes longer to instantiate the daemon, so we need to increase the + // timeout for the before step + this.timeout(60 * 1000) + + common.setup((err, factory) => { + expect(err).to.not.exist() + factory.spawnNode((err, node) => { + expect(err).to.not.exist() + ipfs = node + done() + }) + }) + }) + + after((done) => common.teardown(done)) + + it('should subscribe and unsubscribe 10 times', (done) => { + const count = 10 + const someTopic = getTopic() + + times(count, (_, cb) => { + const handler = (msg) => {} + ipfs.pubsub.subscribe(someTopic, handler, (err) => cb(err, handler)) + }, (err, handlers) => { + expect(err).to.not.exist() + each( + handlers, + (handler, cb) => ipfs.pubsub.unsubscribe(someTopic, handler, cb), + (err) => { + expect(err).to.not.exist() + // Assert unsubscribe worked + ipfs.pubsub.ls((err, topics) => { + expect(err).to.not.exist() + expect(topics).to.eql([]) + done() + }) + } + ) + }) + }) + }) +} diff --git a/js/src/pubsub/utils.js b/js/src/pubsub/utils.js new file mode 100644 index 000000000..03c8d3468 --- /dev/null +++ b/js/src/pubsub/utils.js @@ -0,0 +1,44 @@ +'use strict' + +const hat = require('hat') + +function waitForPeers (ipfs, topic, peersToWait, callback) { + const checkPeers = () => { + ipfs.pubsub.peers(topic, (err, peers) => { + if (err) { + return callback(err) + } + + const missingPeers = peersToWait + .map((e) => peers.indexOf(e) !== -1) + .filter((e) => !e) + + if (missingPeers.length === 0) { + return callback() + } + + setTimeout(checkPeers, 10) + }) + } + + checkPeers() +} + +exports.waitForPeers = waitForPeers + +function makeCheck (n, done) { + let i = 0 + return (err) => { + if (err) { + return done(err) + } + + if (++i === n) { + done() + } + } +} + +exports.makeCheck = makeCheck + +exports.getTopic = () => 'pubsub-tests-' + hat() diff --git a/js/src/repo.js b/js/src/repo.js deleted file mode 100644 index db143a268..000000000 --- a/js/src/repo.js +++ /dev/null @@ -1,76 +0,0 @@ -/* eslint-env mocha */ -/* eslint max-nested-callbacks: ["error", 8] */ - -'use strict' - -const chai = require('chai') -const dirtyChai = require('dirty-chai') -const statsTests = require('./utils/stats') -const expect = chai.expect -chai.use(dirtyChai) - -module.exports = (common) => { - describe('.repo', () => { - let ipfs - - before(function (done) { - // CI takes longer to instantiate the daemon, so we need to increase the - // timeout for the before step - this.timeout(60 * 1000) - - common.setup((err, factory) => { - expect(err).to.not.exist() - factory.spawnNode((err, node) => { - expect(err).to.not.exist() - ipfs = node - done() - }) - }) - }) - - after((done) => { - common.teardown(done) - }) - - it('.version', (done) => { - ipfs.repo.version((err, version) => { - expect(err).to.not.exist() - expect(version).to.exist() - done() - }) - }) - - it('.version Promise', () => { - return ipfs.repo.version().then((version) => { - expect(version).to.exist() - }) - }) - - it('.stat', (done) => { - ipfs.repo.stat((err, res) => { - statsTests.expectIsRepo(err, res) - done() - }) - }) - - it('.stat Promise', () => { - return ipfs.repo.stat().then((res) => { - statsTests.expectIsRepo(null, res) - }) - }) - - it('.gc', (done) => { - ipfs.repo.gc((err, res) => { - expect(err).to.not.exist() - expect(res).to.exist() - done() - }) - }) - - it('.gc Promise', () => { - return ipfs.repo.gc().then((res) => { - expect(res).to.exist() - }) - }) - }) -} diff --git a/js/src/repo/gc.js b/js/src/repo/gc.js new file mode 100644 index 000000000..fb108b2ba --- /dev/null +++ b/js/src/repo/gc.js @@ -0,0 +1,45 @@ +/* eslint-env mocha */ +'use strict' + +const { getDescribe, getIt, expect } = require('../utils/mocha') + +module.exports = (createCommon, options) => { + const describe = getDescribe(options) + const it = getIt(options) + const common = createCommon() + + describe('.repo.gc', () => { + let ipfs + + before(function (done) { + // CI takes longer to instantiate the daemon, so we need to increase the + // timeout for the before step + this.timeout(60 * 1000) + + common.setup((err, factory) => { + expect(err).to.not.exist() + factory.spawnNode((err, node) => { + expect(err).to.not.exist() + ipfs = node + done() + }) + }) + }) + + after((done) => common.teardown(done)) + + it('should run garbage collection', (done) => { + ipfs.repo.gc((err, res) => { + expect(err).to.not.exist() + expect(res).to.exist() + done() + }) + }) + + it('should run garbage collection (promised)', () => { + return ipfs.repo.gc().then((res) => { + expect(res).to.exist() + }) + }) + }) +} diff --git a/js/src/repo/index.js b/js/src/repo/index.js new file mode 100644 index 000000000..f8daaea23 --- /dev/null +++ b/js/src/repo/index.js @@ -0,0 +1,10 @@ +'use strict' +const { createSuite } = require('../utils/suite') + +const tests = { + version: require('./version'), + stat: require('./stat'), + gc: require('./gc') +} + +module.exports = createSuite(tests) diff --git a/js/src/repo/stat.js b/js/src/repo/stat.js new file mode 100644 index 000000000..d313a9998 --- /dev/null +++ b/js/src/repo/stat.js @@ -0,0 +1,45 @@ +/* eslint-env mocha */ +'use strict' + +const { expectIsRepo } = require('../stats/utils') +const { getDescribe, getIt, expect } = require('../utils/mocha') + +module.exports = (createCommon, options) => { + const describe = getDescribe(options) + const it = getIt(options) + const common = createCommon() + + describe('.repo.stat', () => { + let ipfs + + before(function (done) { + // CI takes longer to instantiate the daemon, so we need to increase the + // timeout for the before step + this.timeout(60 * 1000) + + common.setup((err, factory) => { + expect(err).to.not.exist() + factory.spawnNode((err, node) => { + expect(err).to.not.exist() + ipfs = node + done() + }) + }) + }) + + after((done) => common.teardown(done)) + + it('should get repo stats', (done) => { + ipfs.repo.stat((err, res) => { + expectIsRepo(err, res) + done() + }) + }) + + it('should get repo stats (promised)', () => { + return ipfs.repo.stat().then((res) => { + expectIsRepo(null, res) + }) + }) + }) +} diff --git a/js/src/repo/version.js b/js/src/repo/version.js new file mode 100644 index 000000000..20c012a5b --- /dev/null +++ b/js/src/repo/version.js @@ -0,0 +1,45 @@ +/* eslint-env mocha */ +'use strict' + +const { getDescribe, getIt, expect } = require('../utils/mocha') + +module.exports = (createCommon, options) => { + const describe = getDescribe(options) + const it = getIt(options) + const common = createCommon() + + describe('.repo.version', () => { + let ipfs + + before(function (done) { + // CI takes longer to instantiate the daemon, so we need to increase the + // timeout for the before step + this.timeout(60 * 1000) + + common.setup((err, factory) => { + expect(err).to.not.exist() + factory.spawnNode((err, node) => { + expect(err).to.not.exist() + ipfs = node + done() + }) + }) + }) + + after((done) => common.teardown(done)) + + it('should get the repo version', (done) => { + ipfs.repo.version((err, version) => { + expect(err).to.not.exist() + expect(version).to.exist() + done() + }) + }) + + it('should get the repo version (promised)', () => { + return ipfs.repo.version().then((version) => { + expect(version).to.exist() + }) + }) + }) +} diff --git a/js/src/stats.js b/js/src/stats.js deleted file mode 100644 index 83441e024..000000000 --- a/js/src/stats.js +++ /dev/null @@ -1,97 +0,0 @@ -/* eslint-env mocha */ -/* eslint max-nested-callbacks: ["error", 8] */ - -'use strict' - -const chai = require('chai') -const dirtyChai = require('dirty-chai') -const statsTests = require('./utils/stats') -const expect = chai.expect -const pull = require('pull-stream') -chai.use(dirtyChai) - -module.exports = (common) => { - describe('.stats', () => { - let ipfs - - before(function (done) { - // CI takes longer to instantiate the daemon, so we need to increase the - // timeout for the before step - this.timeout(60 * 1000) - - common.setup((err, factory) => { - expect(err).to.not.exist() - factory.spawnNode((err, node) => { - expect(err).to.not.exist() - ipfs = node - done() - }) - }) - }) - - after((done) => { - common.teardown(done) - }) - - it('.bitswap', (done) => { - ipfs.stats.bitswap((err, res) => { - statsTests.expectIsBitswap(err, res) - done() - }) - }) - - it('.bitswap Promise', () => { - return ipfs.stats.bitswap().then((res) => { - statsTests.expectIsBitswap(null, res) - }) - }) - - it('.bw', function (done) { - ipfs.stats.bw((err, res) => { - statsTests.expectIsBandwidth(err, res) - done() - }) - }) - - it('.bw Promise', () => { - return ipfs.stats.bw().then((res) => { - statsTests.expectIsBandwidth(null, res) - }) - }) - - it('.bwReadableStream', (done) => { - const stream = ipfs.stats.bwReadableStream() - - stream.once('data', (data) => { - statsTests.expectIsBandwidth(null, data) - stream.destroy() - done() - }) - }) - - it('.bwPullStream', (done) => { - const stream = ipfs.stats.bwPullStream() - - pull( - stream, - pull.collect((err, data) => { - statsTests.expectIsBandwidth(err, data[0]) - done() - }) - ) - }) - - it('.repo', (done) => { - ipfs.stats.repo((err, res) => { - statsTests.expectIsRepo(err, res) - done() - }) - }) - - it('.repo Promise', () => { - return ipfs.stats.repo().then((res) => { - statsTests.expectIsRepo(null, res) - }) - }) - }) -} diff --git a/js/src/stats/bitswap.js b/js/src/stats/bitswap.js new file mode 100644 index 000000000..b0e57f38b --- /dev/null +++ b/js/src/stats/bitswap.js @@ -0,0 +1,45 @@ +/* eslint-env mocha */ +'use strict' + +const { getDescribe, getIt, expect } = require('../utils/mocha') +const { expectIsBitswap } = require('./utils') + +module.exports = (createCommon, options) => { + const describe = getDescribe(options) + const it = getIt(options) + const common = createCommon() + + describe('.stats.bitswap', () => { + let ipfs + + before(function (done) { + // CI takes longer to instantiate the daemon, so we need to increase the + // timeout for the before step + this.timeout(60 * 1000) + + common.setup((err, factory) => { + expect(err).to.not.exist() + factory.spawnNode((err, node) => { + expect(err).to.not.exist() + ipfs = node + done() + }) + }) + }) + + after((done) => common.teardown(done)) + + it('should get bitswap stats', (done) => { + ipfs.stats.bitswap((err, res) => { + expectIsBitswap(err, res) + done() + }) + }) + + it('should get bitswap stats (promised)', () => { + return ipfs.stats.bitswap().then((res) => { + expectIsBitswap(null, res) + }) + }) + }) +} diff --git a/js/src/stats/bw-pull-stream.js b/js/src/stats/bw-pull-stream.js new file mode 100644 index 000000000..000525c5f --- /dev/null +++ b/js/src/stats/bw-pull-stream.js @@ -0,0 +1,45 @@ +/* eslint-env mocha */ +'use strict' + +const { expectIsBandwidth } = require('./utils') +const pull = require('pull-stream') +const { getDescribe, getIt, expect } = require('../utils/mocha') + +module.exports = (createCommon, options) => { + const describe = getDescribe(options) + const it = getIt(options) + const common = createCommon() + + describe('.stats.bwPullStream', () => { + let ipfs + + before(function (done) { + // CI takes longer to instantiate the daemon, so we need to increase the + // timeout for the before step + this.timeout(60 * 1000) + + common.setup((err, factory) => { + expect(err).to.not.exist() + factory.spawnNode((err, node) => { + expect(err).to.not.exist() + ipfs = node + done() + }) + }) + }) + + after((done) => common.teardown(done)) + + it('should get bandwidth stats over pull stream', (done) => { + const stream = ipfs.stats.bwPullStream() + + pull( + stream, + pull.collect((err, data) => { + expectIsBandwidth(err, data[0]) + done() + }) + ) + }) + }) +} diff --git a/js/src/stats/bw-readable-stream.js b/js/src/stats/bw-readable-stream.js new file mode 100644 index 000000000..e8bc490b9 --- /dev/null +++ b/js/src/stats/bw-readable-stream.js @@ -0,0 +1,42 @@ +/* eslint-env mocha */ +'use strict' + +const { expectIsBandwidth } = require('./utils') +const { getDescribe, getIt, expect } = require('../utils/mocha') + +module.exports = (createCommon, options) => { + const describe = getDescribe(options) + const it = getIt(options) + const common = createCommon() + + describe('.stats.bwReadableStream', () => { + let ipfs + + before(function (done) { + // CI takes longer to instantiate the daemon, so we need to increase the + // timeout for the before step + this.timeout(60 * 1000) + + common.setup((err, factory) => { + expect(err).to.not.exist() + factory.spawnNode((err, node) => { + expect(err).to.not.exist() + ipfs = node + done() + }) + }) + }) + + after((done) => common.teardown(done)) + + it('should get bandwidth stats over readable stream', (done) => { + const stream = ipfs.stats.bwReadableStream() + + stream.once('data', (data) => { + expectIsBandwidth(null, data) + stream.destroy() + done() + }) + }) + }) +} diff --git a/js/src/stats/bw.js b/js/src/stats/bw.js new file mode 100644 index 000000000..c851f4f27 --- /dev/null +++ b/js/src/stats/bw.js @@ -0,0 +1,45 @@ +/* eslint-env mocha */ +'use strict' + +const { expectIsBandwidth } = require('./utils') +const { getDescribe, getIt, expect } = require('../utils/mocha') + +module.exports = (createCommon, options) => { + const describe = getDescribe(options) + const it = getIt(options) + const common = createCommon() + + describe('.stats.bw', () => { + let ipfs + + before(function (done) { + // CI takes longer to instantiate the daemon, so we need to increase the + // timeout for the before step + this.timeout(60 * 1000) + + common.setup((err, factory) => { + expect(err).to.not.exist() + factory.spawnNode((err, node) => { + expect(err).to.not.exist() + ipfs = node + done() + }) + }) + }) + + after((done) => common.teardown(done)) + + it('should get bandwidth stats', function (done) { + ipfs.stats.bw((err, res) => { + expectIsBandwidth(err, res) + done() + }) + }) + + it('should get bandwidth stats (promised)', () => { + return ipfs.stats.bw().then((res) => { + expectIsBandwidth(null, res) + }) + }) + }) +} diff --git a/js/src/stats/index.js b/js/src/stats/index.js new file mode 100644 index 000000000..e07efd478 --- /dev/null +++ b/js/src/stats/index.js @@ -0,0 +1,12 @@ +'use strict' +const { createSuite } = require('../utils/suite') + +const tests = { + bitswap: require('./bitswap'), + bw: require('./bw'), + bwPullStream: require('./bw-pull-stream'), + bwReadableStream: require('./bw-readable-stream'), + repo: require('./repo') +} + +module.exports = createSuite(tests) diff --git a/js/src/stats/repo.js b/js/src/stats/repo.js new file mode 100644 index 000000000..77b85e3e7 --- /dev/null +++ b/js/src/stats/repo.js @@ -0,0 +1,45 @@ +/* eslint-env mocha */ +'use strict' + +const { expectIsRepo } = require('./utils') +const { getDescribe, getIt, expect } = require('../utils/mocha') + +module.exports = (createCommon, options) => { + const describe = getDescribe(options) + const it = getIt(options) + const common = createCommon() + + describe('.stats.repo', () => { + let ipfs + + before(function (done) { + // CI takes longer to instantiate the daemon, so we need to increase the + // timeout for the before step + this.timeout(60 * 1000) + + common.setup((err, factory) => { + expect(err).to.not.exist() + factory.spawnNode((err, node) => { + expect(err).to.not.exist() + ipfs = node + done() + }) + }) + }) + + after((done) => common.teardown(done)) + + it('should get repo stats', (done) => { + ipfs.stats.repo((err, res) => { + expectIsRepo(err, res) + done() + }) + }) + + it('should get repo stats (promised)', () => { + return ipfs.stats.repo().then((res) => { + expectIsRepo(null, res) + }) + }) + }) +} diff --git a/js/src/utils/stats.js b/js/src/stats/utils.js similarity index 88% rename from js/src/utils/stats.js rename to js/src/stats/utils.js index d9c56f07c..7d02ce0e5 100644 --- a/js/src/utils/stats.js +++ b/js/src/stats/utils.js @@ -1,15 +1,12 @@ -/* eslint-env mocha */ -/* eslint max-nested-callbacks: ["error", 8] */ - 'use strict' -const { expect } = require('chai') +const { expect } = require('../utils/mocha') const isBigInt = (n) => { return n.constructor.name === 'Big' } -module.exports.expectIsBitswap = (err, stats) => { +exports.expectIsBitswap = (err, stats) => { expect(err).to.not.exist() expect(stats).to.exist() expect(stats).to.have.a.property('provideBufLen') @@ -33,7 +30,7 @@ module.exports.expectIsBitswap = (err, stats) => { expect(isBigInt(stats.dupDataReceived)).to.eql(true) } -module.exports.expectIsBandwidth = (err, stats) => { +exports.expectIsBandwidth = (err, stats) => { expect(err).to.not.exist() expect(stats).to.exist() expect(stats).to.have.a.property('totalIn') @@ -46,7 +43,7 @@ module.exports.expectIsBandwidth = (err, stats) => { expect(isBigInt(stats.rateOut)).to.eql(true) } -module.exports.expectIsRepo = (err, res) => { +exports.expectIsRepo = (err, res) => { expect(err).to.not.exist() expect(res).to.exist() expect(res).to.have.a.property('numObjects') diff --git a/js/src/swarm.js b/js/src/swarm.js deleted file mode 100644 index 5905412c3..000000000 --- a/js/src/swarm.js +++ /dev/null @@ -1,306 +0,0 @@ -/* eslint-env mocha */ -/* eslint max-nested-callbacks: ["error", 8] */ - -'use strict' - -const chai = require('chai') -const dirtyChai = require('dirty-chai') -const expect = chai.expect -chai.use(dirtyChai) -const series = require('async/series') -const multiaddr = require('multiaddr') -const PeerId = require('peer-id') -const os = require('os') -const path = require('path') -const hat = require('hat') -const { spawnNodes } = require('./utils/spawn') - -module.exports = (common) => { - describe('.swarm', function () { - this.timeout(80 * 1000) - - let ipfsA - let ipfsB - let factoryInstance - - before(function (done) { - // CI takes longer to instantiate the daemon, so we need to increase the - // timeout for the before step - this.timeout(100 * 1000) - - common.setup((err, factory) => { - expect(err).to.not.exist() - factoryInstance = factory - - spawnNodes(2, factory, (err, nodes) => { - expect(err).to.not.exist() - ipfsA = nodes[0] - ipfsB = nodes[1] - done() - }) - }) - }) - - after((done) => common.teardown(done)) - - let ipfsBId - - describe('callback API', function () { - this.timeout(80 * 1000) - - it('.connect', (done) => { - ipfsB.id((err, id) => { - expect(err).to.not.exist() - ipfsBId = id - const ipfsBAddr = id.addresses[0] - ipfsA.swarm.connect(ipfsBAddr, done) - }) - }) - - // for Identify to finish - it('time', (done) => setTimeout(done, 1500)) - - describe('.peers', () => { - beforeEach((done) => { - const ipfsBAddr = ipfsBId.addresses[0] - ipfsA.swarm.connect(ipfsBAddr, done) - }) - - it('default', (done) => { - ipfsA.swarm.peers((err, peers) => { - expect(err).to.not.exist() - expect(peers).to.have.length.above(0) - - const peer = peers[0] - - expect(peer).to.have.a.property('addr') - expect(multiaddr.isMultiaddr(peer.addr)).to.equal(true) - expect(peer).to.have.a.property('peer') - expect(PeerId.isPeerId(peer.peer)).to.equal(true) - expect(peer).to.not.have.a.property('latency') - - // only available in 0.4.5 - // expect(peer).to.have.a.property('muxer') - // expect(peer).to.not.have.a.property('streams') - - done() - }) - }) - - it('verbose', (done) => { - ipfsA.swarm.peers({verbose: true}, (err, peers) => { - expect(err).to.not.exist() - expect(peers).to.have.length.above(0) - - const peer = peers[0] - expect(peer).to.have.a.property('addr') - expect(multiaddr.isMultiaddr(peer.addr)).to.equal(true) - expect(peer).to.have.a.property('peer') - expect(peer).to.have.a.property('latency') - - // Only available in 0.4.5 - // expect(peer).to.have.a.property('muxer') - // expect(peer).to.have.a.property('streams') - - done() - }) - }) - - describe('Shows connected peers only once', () => { - function getConfig (addrs) { - addrs = Array.isArray(addrs) ? addrs : [addrs] - - return { - Addresses: { - Swarm: addrs, - API: '/ip4/127.0.0.1/tcp/0', - Gateway: '/ip4/127.0.0.1/tcp/0' - }, - Bootstrap: [], - Discovery: { - MDNS: { - Enabled: false - } - } - } - } - - function getRepoPath () { - return path.join(os.tmpdir(), '.ipfs-' + hat()) - } - - it('Connecting two peers with one address each', (done) => { - let nodeA - let nodeB - let nodeBAddress - const addresses = ['/ip4/127.0.0.1/tcp/0'] - const config = getConfig(addresses) - series([ - (cb) => { - factoryInstance.spawnNode(getRepoPath(), config, (err, node) => { - expect(err).to.not.exist() - nodeA = node - cb() - }) - }, - (cb) => { - factoryInstance.spawnNode(getRepoPath(), config, (err, node) => { - expect(err).to.not.exist() - nodeB = node - cb() - }) - }, - (cb) => { - nodeB.id((err, info) => { - expect(err).to.not.exist() - nodeBAddress = info.addresses[0] - cb() - }) - }, - (cb) => nodeA.swarm.connect(nodeBAddress, cb), - (cb) => setTimeout(cb, 1000), // time for identify - (cb) => { - nodeA.swarm.peers((err, peers) => { - expect(err).to.not.exist() - expect(peers).to.have.length(1) - cb() - }) - }, - (cb) => { - nodeB.swarm.peers((err, peers) => { - expect(err).to.not.exist() - expect(peers).to.have.length(1) - cb() - }) - } - ], done) - }) - - it('Connecting two peers with two addresses each', (done) => { - let nodeA - let nodeB - let nodeBAddress - - // TODO: Change to port 0, needs: https://github.com/ipfs/interface-ipfs-core/issues/152 - const configA = getConfig([ - '/ip4/127.0.0.1/tcp/16543', - '/ip4/127.0.0.1/tcp/16544' - ]) - const configB = getConfig([ - '/ip4/127.0.0.1/tcp/26545', - '/ip4/127.0.0.1/tcp/26546' - ]) - series([ - (cb) => { - factoryInstance.spawnNode(getRepoPath(), configA, (err, node) => { - expect(err).to.not.exist() - nodeA = node - cb() - }) - }, - (cb) => { - factoryInstance.spawnNode(getRepoPath(), configB, (err, node) => { - expect(err).to.not.exist() - nodeB = node - cb() - }) - }, - (cb) => { - nodeB.id((err, info) => { - expect(err).to.not.exist() - nodeBAddress = info.addresses[0] - cb() - }) - }, - (cb) => nodeA.swarm.connect(nodeBAddress, cb), - (cb) => setTimeout(cb, 1000), // time for identify - (cb) => { - nodeA.swarm.peers((err, peers) => { - expect(err).to.not.exist() - expect(peers).to.have.length(1) - cb() - }) - }, - (cb) => { - nodeB.swarm.peers((err, peers) => { - expect(err).to.not.exist() - expect(peers).to.have.length(1) - cb() - }) - } - ], done) - }) - }) - }) - - it('.addrs', (done) => { - ipfsA.swarm.addrs((err, multiaddrs) => { - expect(err).to.not.exist() - expect(multiaddrs).to.not.be.empty() - expect(multiaddrs).to.be.an('array') - expect(multiaddrs[0].constructor.name).to.be.eql('PeerInfo') - done() - }) - }) - - it('.localAddrs', (done) => { - ipfsA.swarm.localAddrs((err, multiaddrs) => { - expect(err).to.not.exist() - expect(multiaddrs).to.have.length.above(0) - done() - }) - }) - - it('.disconnect', (done) => { - ipfsB.id((err, id) => { - expect(err).to.not.exist() - const ipfsBAddr = id.addresses[0] - ipfsA.swarm.disconnect(ipfsBAddr, done) - }) - }) - }) - - describe('promise API', function () { - this.timeout(80 * 1000) - - it('.connect', () => { - return ipfsB.id() - .then((id) => { - const ipfsBAddr = id.addresses[0] - return ipfsA.swarm.connect(ipfsBAddr) - }) - }) - - // for Identify to finish - it('time', (done) => { - setTimeout(done, 1500) - }) - - it('.peers', () => { - return ipfsA.swarm.peers().then((multiaddrs) => { - expect(multiaddrs).to.have.length.above(0) - }) - }) - - it('.addrs', () => { - return ipfsA.swarm.addrs().then((multiaddrs) => { - expect(multiaddrs).to.have.length.above(0) - }) - }) - - it('.localAddrs', () => { - return ipfsA.swarm.localAddrs().then((multiaddrs) => { - expect(multiaddrs).to.have.length.above(0) - }) - }) - - it('.disconnect', () => { - return ipfsB.id() - .then((id) => { - const ipfsBAddr = id.addresses[0] - return ipfsA.swarm.disconnect(ipfsBAddr) - }) - }) - }) - }) -} diff --git a/js/src/swarm/addrs.js b/js/src/swarm/addrs.js new file mode 100644 index 000000000..8e13dac97 --- /dev/null +++ b/js/src/swarm/addrs.js @@ -0,0 +1,49 @@ +/* eslint-env mocha */ +'use strict' + +const { getDescribe, getIt, expect } = require('../utils/mocha') + +module.exports = (createCommon, options) => { + const describe = getDescribe(options) + const it = getIt(options) + const common = createCommon() + + describe('.swarm.addrs', function () { + this.timeout(80 * 1000) + + let ipfs + + before(function (done) { + // CI takes longer to instantiate the daemon, so we need to increase the + // timeout for the before step + this.timeout(100 * 1000) + + common.setup((err, factory) => { + expect(err).to.not.exist() + factory.spawnNode((err, node) => { + expect(err).to.not.exist() + ipfs = node + done() + }) + }) + }) + + after((done) => common.teardown(done)) + + it('should get a list of node addresses', (done) => { + ipfs.swarm.addrs((err, multiaddrs) => { + expect(err).to.not.exist() + expect(multiaddrs).to.not.be.empty() + expect(multiaddrs).to.be.an('array') + expect(multiaddrs[0].constructor.name).to.be.eql('PeerInfo') + done() + }) + }) + + it('should get a list of node addresses (promised)', () => { + return ipfs.swarm.addrs().then((multiaddrs) => { + expect(multiaddrs).to.have.length.above(0) + }) + }) + }) +} diff --git a/js/src/swarm/connect.js b/js/src/swarm/connect.js new file mode 100644 index 000000000..e6022ac64 --- /dev/null +++ b/js/src/swarm/connect.js @@ -0,0 +1,45 @@ +/* eslint-env mocha */ +'use strict' + +const { spawnNodesWithId } = require('../utils/spawn') +const { getDescribe, getIt, expect } = require('../utils/mocha') + +module.exports = (createCommon, options) => { + const describe = getDescribe(options) + const it = getIt(options) + const common = createCommon() + + describe('.swarm.connect', function () { + this.timeout(80 * 1000) + + let ipfsA + let ipfsB + + before(function (done) { + // CI takes longer to instantiate the daemon, so we need to increase the + // timeout for the before step + this.timeout(100 * 1000) + + common.setup((err, factory) => { + expect(err).to.not.exist() + + spawnNodesWithId(2, factory, (err, nodes) => { + expect(err).to.not.exist() + ipfsA = nodes[0] + ipfsB = nodes[1] + done() + }) + }) + }) + + after((done) => common.teardown(done)) + + it('should connect to a peer', (done) => { + ipfsA.swarm.connect(ipfsB.peerId.addresses[0], done) + }) + + it('should connect to a peer (promised)', () => { + return ipfsA.swarm.connect(ipfsB.peerId.addresses[0]) + }) + }) +} diff --git a/js/src/swarm/disconnect.js b/js/src/swarm/disconnect.js new file mode 100644 index 000000000..4cd3d48c9 --- /dev/null +++ b/js/src/swarm/disconnect.js @@ -0,0 +1,45 @@ +/* eslint-env mocha */ +'use strict' + +const { spawnNodesWithId } = require('../utils/spawn') +const { getDescribe, getIt, expect } = require('../utils/mocha') + +module.exports = (createCommon, options) => { + const describe = getDescribe(options) + const it = getIt(options) + const common = createCommon() + + describe('.swarm.disconnect', function () { + this.timeout(80 * 1000) + + let ipfsA + let ipfsB + + before(function (done) { + // CI takes longer to instantiate the daemon, so we need to increase the + // timeout for the before step + this.timeout(100 * 1000) + + common.setup((err, factory) => { + expect(err).to.not.exist() + + spawnNodesWithId(2, factory, (err, nodes) => { + expect(err).to.not.exist() + ipfsA = nodes[0] + ipfsB = nodes[1] + ipfsA.swarm.connect(ipfsB.peerId.addresses[0], done) + }) + }) + }) + + after((done) => common.teardown(done)) + + it('should disconnect from a peer', (done) => { + ipfsA.swarm.disconnect(ipfsB.peerId.addresses[0], done) + }) + + it('should disconnect from a peer (promised)', () => { + return ipfsA.swarm.disconnect(ipfsB.peerId.addresses[0]) + }) + }) +} diff --git a/js/src/swarm/index.js b/js/src/swarm/index.js new file mode 100644 index 000000000..23bef9e36 --- /dev/null +++ b/js/src/swarm/index.js @@ -0,0 +1,12 @@ +'use strict' +const { createSuite } = require('../utils/suite') + +const tests = { + connect: require('./connect'), + peers: require('./peers'), + addrs: require('./addrs'), + localAddrs: require('./local-addrs'), + disconnect: require('./disconnect') +} + +module.exports = createSuite(tests) diff --git a/js/src/swarm/local-addrs.js b/js/src/swarm/local-addrs.js new file mode 100644 index 000000000..7f5463310 --- /dev/null +++ b/js/src/swarm/local-addrs.js @@ -0,0 +1,47 @@ +/* eslint-env mocha */ +'use strict' + +const { getDescribe, getIt, expect } = require('../utils/mocha') + +module.exports = (createCommon, options) => { + const describe = getDescribe(options) + const it = getIt(options) + const common = createCommon() + + describe('.swarm.localAddrs', function () { + this.timeout(80 * 1000) + + let ipfs + + before(function (done) { + // CI takes longer to instantiate the daemon, so we need to increase the + // timeout for the before step + this.timeout(100 * 1000) + + common.setup((err, factory) => { + expect(err).to.not.exist() + factory.spawnNode((err, node) => { + expect(err).to.not.exist() + ipfs = node + done() + }) + }) + }) + + after((done) => common.teardown(done)) + + it('should list local addresses the node is listening on', (done) => { + ipfs.swarm.localAddrs((err, multiaddrs) => { + expect(err).to.not.exist() + expect(multiaddrs).to.have.length.above(0) + done() + }) + }) + + it('should list local addresses the node is listening on (promised)', () => { + return ipfs.swarm.localAddrs().then((multiaddrs) => { + expect(multiaddrs).to.have.length.above(0) + }) + }) + }) +} diff --git a/js/src/swarm/peers.js b/js/src/swarm/peers.js new file mode 100644 index 000000000..2308383c6 --- /dev/null +++ b/js/src/swarm/peers.js @@ -0,0 +1,199 @@ +/* eslint-env mocha */ +'use strict' + +const auto = require('async/auto') +const multiaddr = require('multiaddr') +const PeerId = require('peer-id') +const os = require('os') +const path = require('path') +const hat = require('hat') +const { spawnNodesWithId } = require('../utils/spawn') +const { getDescribe, getIt, expect } = require('../utils/mocha') + +module.exports = (createCommon, options) => { + const describe = getDescribe(options) + const it = getIt(options) + const common = createCommon() + + describe('.swarm.peers', function () { + this.timeout(80 * 1000) + + let ipfsA + let ipfsB + let ipfsFactory + + before(function (done) { + // CI takes longer to instantiate the daemon, so we need to increase the + // timeout for the before step + this.timeout(100 * 1000) + + common.setup((err, factory) => { + expect(err).to.not.exist() + ipfsFactory = factory + + spawnNodesWithId(2, factory, (err, nodes) => { + expect(err).to.not.exist() + ipfsA = nodes[0] + ipfsB = nodes[1] + ipfsA.swarm.connect(ipfsB.peerId.addresses[0], done) + }) + }) + }) + + after((done) => common.teardown(done)) + + it('should list peers this node is connected to', (done) => { + ipfsA.swarm.peers((err, peers) => { + expect(err).to.not.exist() + expect(peers).to.have.length.above(0) + + const peer = peers[0] + + expect(peer).to.have.a.property('addr') + expect(multiaddr.isMultiaddr(peer.addr)).to.equal(true) + expect(peer).to.have.a.property('peer') + expect(PeerId.isPeerId(peer.peer)).to.equal(true) + expect(peer).to.not.have.a.property('latency') + + // only available in 0.4.5 + // expect(peer).to.have.a.property('muxer') + // expect(peer).to.not.have.a.property('streams') + + done() + }) + }) + + it('should list peers this node is connected to (promised)', () => { + return ipfsA.swarm.peers().then((peers) => { + expect(peers).to.have.length.above(0) + + const peer = peers[0] + + expect(peer).to.have.a.property('addr') + expect(multiaddr.isMultiaddr(peer.addr)).to.equal(true) + expect(peer).to.have.a.property('peer') + expect(PeerId.isPeerId(peer.peer)).to.equal(true) + expect(peer).to.not.have.a.property('latency') + + // only available in 0.4.5 + // expect(peer).to.have.a.property('muxer') + // expect(peer).to.not.have.a.property('streams') + }) + }) + + it('should list peers this node is connected to with verbose option', (done) => { + ipfsA.swarm.peers({ verbose: true }, (err, peers) => { + expect(err).to.not.exist() + expect(peers).to.have.length.above(0) + + const peer = peers[0] + expect(peer).to.have.a.property('addr') + expect(multiaddr.isMultiaddr(peer.addr)).to.equal(true) + expect(peer).to.have.a.property('peer') + expect(peer).to.have.a.property('latency') + + // Only available in 0.4.5 + // expect(peer).to.have.a.property('muxer') + // expect(peer).to.have.a.property('streams') + + done() + }) + }) + + function getConfig (addrs) { + addrs = Array.isArray(addrs) ? addrs : [addrs] + + return { + Addresses: { + Swarm: addrs, + API: '/ip4/127.0.0.1/tcp/0', + Gateway: '/ip4/127.0.0.1/tcp/0' + }, + Bootstrap: [], + Discovery: { + MDNS: { + Enabled: false + } + } + } + } + + function getRepoPath () { + return path.join(os.tmpdir(), '.ipfs-' + hat()) + } + + it('should list peers only once', (done) => { + const config = getConfig(['/ip4/127.0.0.1/tcp/0']) + + auto({ + nodeA: (cb) => ipfsFactory.spawnNode(getRepoPath(), config, cb), + nodeB: ['nodeA', (_, cb) => { + ipfsFactory.spawnNode(getRepoPath(), config, cb) + }], + nodeBAddress: ['nodeB', (res, cb) => { + res.nodeB.id((err, info) => { + if (err) return cb(err) + cb(null, info.addresses[0]) + }) + }], + connectA2B: ['nodeA', 'nodeBAddress', (res, cb) => { + res.nodeA.swarm.connect(res.nodeBAddress, cb) + }], + // time for identify + wait: ['connectA2B', (_, cb) => setTimeout(cb, 1000)], + nodeAPeers: ['nodeA', 'wait', (res, cb) => { + res.nodeA.swarm.peers(cb) + }], + nodeBPeers: ['nodeB', 'wait', (res, cb) => { + res.nodeB.swarm.peers(cb) + }] + }, (err, res) => { + expect(err).to.not.exist() + expect(res.nodeAPeers).to.have.length(1) + expect(res.nodeBPeers).to.have.length(1) + done() + }) + }) + + it('should list peers only once even if they have multiple addresses', (done) => { + // TODO: Change to port 0, needs: https://github.com/ipfs/interface-ipfs-core/issues/152 + const configA = getConfig([ + '/ip4/127.0.0.1/tcp/16543', + '/ip4/127.0.0.1/tcp/16544' + ]) + const configB = getConfig([ + '/ip4/127.0.0.1/tcp/26545', + '/ip4/127.0.0.1/tcp/26546' + ]) + + auto({ + nodeA: (cb) => ipfsFactory.spawnNode(getRepoPath(), configA, cb), + nodeB: ['nodeA', (_, cb) => { + ipfsFactory.spawnNode(getRepoPath(), configB, cb) + }], + nodeBAddress: ['nodeB', (res, cb) => { + res.nodeB.id((err, info) => { + if (err) return cb(err) + cb(null, info.addresses[0]) + }) + }], + connectA2B: ['nodeA', 'nodeBAddress', (res, cb) => { + res.nodeA.swarm.connect(res.nodeBAddress, cb) + }], + // time for identify + wait: ['connectA2B', (_, cb) => setTimeout(cb, 1000)], + nodeAPeers: ['nodeA', 'wait', (res, cb) => { + res.nodeA.swarm.peers(cb) + }], + nodeBPeers: ['nodeB', 'wait', (res, cb) => { + res.nodeB.swarm.peers(cb) + }] + }, (err, res) => { + expect(err).to.not.exist() + expect(res.nodeAPeers).to.have.length(1) + expect(res.nodeBPeers).to.have.length(1) + done() + }) + }) + }) +} diff --git a/js/src/types.js b/js/src/types.js index 070b40034..e40bf6b8d 100644 --- a/js/src/types.js +++ b/js/src/types.js @@ -9,13 +9,13 @@ const multiaddr = require('multiaddr') const multibase = require('multibase') const multihash = require('multihashes') const CID = require('cids') +const { getDescribe, getIt, expect } = require('./utils/mocha') -const chai = require('chai') -const dirtyChai = require('dirty-chai') -const expect = chai.expect -chai.use(dirtyChai) +module.exports = (createCommon, options) => { + const describe = getDescribe(options) + const it = getIt(options) + const common = createCommon() -module.exports = (common) => { describe('.types', function () { let ipfs @@ -34,11 +34,9 @@ module.exports = (common) => { }) }) - after((done) => { - common.teardown(done) - }) + after((done) => common.teardown(done)) - it('types object', () => { + it('should have a types object with the required values', () => { expect(ipfs.types).to.be.deep.equal({ Buffer: Buffer, PeerId: PeerId, diff --git a/js/src/util.js b/js/src/util.js index b64ef2b3c..3a0f32c05 100644 --- a/js/src/util.js +++ b/js/src/util.js @@ -3,12 +3,13 @@ const crypto = require('libp2p-crypto') const isIPFS = require('is-ipfs') -const chai = require('chai') -const dirtyChai = require('dirty-chai') -const expect = chai.expect -chai.use(dirtyChai) +const { getDescribe, getIt, expect } = require('./utils/mocha') + +module.exports = (createCommon, options) => { + const describe = getDescribe(options) + const it = getIt(options) + const common = createCommon() -module.exports = (common) => { describe('.util', function () { let ipfs @@ -27,11 +28,9 @@ module.exports = (common) => { }) }) - after((done) => { - common.teardown(done) - }) + after((done) => common.teardown(done)) - it('util object', () => { + it('should have a util object with the required values', () => { expect(ipfs.util).to.be.deep.equal({ crypto: crypto, isIPFS: isIPFS diff --git a/js/src/utils/mocha.js b/js/src/utils/mocha.js new file mode 100644 index 000000000..80c51d062 --- /dev/null +++ b/js/src/utils/mocha.js @@ -0,0 +1,69 @@ +/* eslint-env mocha */ +'use strict' + +const chai = require('chai') +const dirtyChai = require('dirty-chai') + +chai.use(dirtyChai) + +module.exports.expect = chai.expect + +// Get a "describe" function that is optionally 'skipped' or 'onlyed' +// If skip/only are boolean true, or an object with a reason property, then we +// want to skip/only the whole suite +function getDescribe (config) { + if (config) { + if (config.only === true) return describe.only + if (config.skip === true) return describe.skip + + if (typeof config.skip === 'object' && config.skip.reason) { + const _describe = (name, impl) => { + describe.skip(`${name} (${config.skip.reason})`, impl) + } + + _describe.skip = describe.skip + _describe.only = describe.only + + return _describe + } + } + + return describe +} + +module.exports.getDescribe = getDescribe + +// Get an "it" function that is optionally 'skipped' or 'onlyed' +// If skip/only is an array, then we _might_ want to skip/only the specific +// test if one of the items in the array is the same as the test name or if one +// of the items in the array is an object with a name property that is the same +// as the test name. +function getIt (config) { + if (!config) return it + + const _it = (name, impl) => { + if (Array.isArray(config.skip)) { + const skip = config.skip + .map((s) => s && typeof s === 'object' ? s : { name: s }) + .find((s) => s.name === name) + + if (skip) { + if (skip.reason) name = `${name} (${skip.reason})` + return it.skip(name, impl) + } + } + + if (Array.isArray(config.only)) { + if (config.only.includes(name)) return it.only(name, impl) + } + + it(name, impl) + } + + _it.skip = it.skip + _it.only = it.only + + return _it +} + +module.exports.getIt = getIt diff --git a/js/src/utils/spawn.js b/js/src/utils/spawn.js index ea985932f..792d482bc 100644 --- a/js/src/utils/spawn.js +++ b/js/src/utils/spawn.js @@ -1,3 +1,5 @@ +'use strict' + const waterfall = require('async/waterfall') const timesSeries = require('async/timesSeries') const map = require('async/map') diff --git a/js/src/utils/suite.js b/js/src/utils/suite.js new file mode 100644 index 000000000..7e2c88c6a --- /dev/null +++ b/js/src/utils/suite.js @@ -0,0 +1,32 @@ +'use strict' + +function createSuite (tests, parent) { + const suite = (createCommon, options) => { + Object.keys(tests).forEach(t => { + const opts = Object.assign({}, options) + const suiteName = parent ? `${parent}.${t}` : t + + if (Array.isArray(opts.skip)) { + const skip = opts.skip + .map((s) => s && typeof s === 'object' ? s : { name: s }) + .find((s) => s.name === suiteName) + + if (skip) { + opts.skip = skip + } + } + + if (Array.isArray(opts.only)) { + if (opts.only.includes(suiteName)) { + opts.only = true + } + } + + tests[t](createCommon, opts) + }) + } + + return Object.assign(suite, tests) +} + +module.exports.createSuite = createSuite diff --git a/package.json b/package.json index 82d0714b6..eb2188d89 100644 --- a/package.json +++ b/package.json @@ -38,7 +38,6 @@ "chai": "^4.1.2", "cids": "~0.5.3", "concat-stream": "^1.6.2", - "detect-node": "^2.0.3", "dirty-chai": "^2.0.1", "hat": "0.0.3", "ipfs-block": "~0.7.1",