diff --git a/.gitignore b/.gitignore index 254988dc..3222b42d 100644 --- a/.gitignore +++ b/.gitignore @@ -33,3 +33,6 @@ node_modules lib dist + +# Bin default dir +vendor diff --git a/.npmignore b/.npmignore index ea440ea6..b65b7b7f 100644 --- a/.npmignore +++ b/.npmignore @@ -30,3 +30,6 @@ build # Dependency directory # https://www.npmjs.org/doc/misc/npm-faq.html#should-i-check-my-node_modules-folder-into-git node_modules + +# Bin default dir +vendor diff --git a/README.md b/README.md index 13a43ff2..535028bc 100644 --- a/README.md +++ b/README.md @@ -25,6 +25,8 @@ Install: npm install --save ipfsd-ctl ``` +__The current go ipfs version used is v0.4.3-rc3__ + ## Usage IPFS daemons are already easy to start and stop, but this module is here to do it from javascript itself. @@ -36,17 +38,58 @@ IPFS daemons are already easy to start and stop, but this module is here to do i // IPFS_PATH will point to /tmp/ipfs_***** and will be // cleaned up when the process exits. -var ipfsd = require('ipfsd-ctl') - -ipfsd.disposableApi(function (err, ipfs) { - ipfs.id(function (err, id) { - console.log(id) - process.kill() +const ipfsd = require('ipfsd-ctl') + +ipfsd.create((err, node) => { + if (err) throw err + node.startDaemon((err) => { + if (err) throw err + const ipfs = node.apiCtl() + ipfs.id((err, id) => { + console.log(id) + process.kill() + }) }) }) ``` -If you need want to use an existing ipfs installation you can set `$IPFS_EXEC=/path/to/ipfs` to ensure it uses that. +The daemon controller safely spawns the node for you and exposes you an ipfs API client through `node.apiCtl()`. __If the parent process exits, the daemon will also be killed__ ensuring that the daemon isn't left hanging. + +This module works by downloading the binary once, on first use, if it detects that no current binary is available to use. So keep in mind that the first command executed might throw in some overhead. + +If you want to use an existing ipfs installation you can set `$IPFS_EXEC=/path/to/ipfs` to ensure it uses that. + +## API + +## ipfsd + +#### ipfsd.local(path, done) + +#### ipfsd.create(opts, done) + +## IPFSNode(path, opts, disposable) + +#### node.init(initOpts, done) + +#### node.shutdown(done) + +#### node.startDaemon(done) + +#### node.stopDaemon(done) + +#### node.apiCtl(done) + +#### node.daemonPid() + +#### node.getConfig(key, done) + +#### node.setConfig(key, value, done) + +#### node.replaceConf(file, done) + +#### node.version(done) + +#### node.subprocess ## Contribute diff --git a/bin/check-binary b/bin/check-binary new file mode 100755 index 00000000..e9b00656 --- /dev/null +++ b/bin/check-binary @@ -0,0 +1,11 @@ +#!/usr/bin/env node +'use strict' + +console.log('Checking IPFS binary...') +const binary = require('../src/binary')() +const version = require('../src/config').version + +binary.check((err) => { + if (err) throw err + console.log(`IPFS binary version ${version} working`) +}) diff --git a/examples/id.js b/examples/id.js index aab3d5b4..3a697443 100644 --- a/examples/id.js +++ b/examples/id.js @@ -1,6 +1,6 @@ 'use strict' -var ipfsd = require('../index.js') +var ipfsd = require('../src') ipfsd.disposableApi(function (err, ipfs) { if (err) throw err diff --git a/examples/local.js b/examples/local.js index 9386a484..fb1a1ce1 100644 --- a/examples/local.js +++ b/examples/local.js @@ -1,6 +1,6 @@ 'use strict' -var ipfsd = require('../index.js') +var ipfsd = require('../src') // opens an api connection to local running ipfs node diff --git a/package.json b/package.json index deecaf7f..8268086f 100644 --- a/package.json +++ b/package.json @@ -5,6 +5,7 @@ "main": "lib/index.js", "jsxnext:main": "src/index.js", "scripts": { + "postinstall": "./bin/check-binary", "lint": "aegir-lint", "coverage": "aegir-coverage", "test": "aegir-test --env node", @@ -43,17 +44,17 @@ ], "license": "MIT", "dependencies": { - "go-ipfs-dep": "0.4.1", "ipfs-api": "^4.1.0", "multiaddr": "^2.0.0", "rimraf": "^2.4.5", "run-series": "^1.1.4", "shutdown": "^0.2.4", - "subcomandante": "^1.0.5" + "execa": "git://github.com/sindresorhus/execa.git#master", + "bin-wrapper": "^3.0.2" }, "devDependencies": { - "aegir": "^8.0.1", - "mkdirp": "^0.5.1", + "aegir": "^8.1.0", + "chai": "^3.5.0", "pre-commit": "^1.1.2" }, "repository": { @@ -68,4 +69,4 @@ "example": "examples", "test": "test" } -} \ No newline at end of file +} diff --git a/src/binary.js b/src/binary.js new file mode 100644 index 00000000..f664a62d --- /dev/null +++ b/src/binary.js @@ -0,0 +1,36 @@ +'use strict' + +const BinWrapper = require('bin-wrapper') +const config = require('./config') + +const binWrapper = new BinWrapper() + .src(config.baseUrl + 'darwin-386.tar.gz', 'darwin', 'ia32') + .src(config.baseUrl + 'darwin-amd64.tar.gz', 'darwin', 'x64') + .src(config.baseUrl + 'freebsd-amd64.tar.gz', 'freebsd', 'x64') + .src(config.baseUrl + 'linux-386.tar.gz', 'linux', 'ia32') + .src(config.baseUrl + 'linux-amd64.tar.gz', 'linux', 'x64') + .src(config.baseUrl + 'linux-arm.tar.gz', 'linux', 'arm') + .src(config.baseUrl + 'windows-386.tar.gz', 'win32', 'ia32') + .src(config.baseUrl + 'windows-amd64.tar.gz', 'win32', 'x64') + .use(process.platform === 'win32' ? 'ipfs.exe' : 'ipfs') + +function Binary (execPath) { + // Set dest path for the exec file + binWrapper.dest(process.env.IPFS_EXEC || execPath || config.defaultExecPath) + return { + path: () => binWrapper.path(), + // Has the binary been checked? + checked: false, + // Check that the binary exists and works + check (cb) { + if (this.checked) return cb() + binWrapper.run(['version'], (err) => { + // The binary is ok if no error poped up + this.checked = !err + return cb(err) + }) + } + } +} + +module.exports = Binary diff --git a/src/config.js b/src/config.js new file mode 100644 index 00000000..0af33c81 --- /dev/null +++ b/src/config.js @@ -0,0 +1,14 @@ +'use strict' + +const path = require('path') + +const config = { + version: 'v0.4.3-rc4', // ipfs dist version + defaultExecPath: path.join(__dirname, '..', 'vendor'), // default path for the downloaded exec + gracePeriod: 7500 // amount of ms to wait before sigkill +} + +const version = config.version +config.baseUrl = `https://dist.ipfs.io/go-ipfs/${version}/go-ipfs_${version}_` + +module.exports = config diff --git a/src/exec.js b/src/exec.js new file mode 100644 index 00000000..07a11030 --- /dev/null +++ b/src/exec.js @@ -0,0 +1,57 @@ +'use strict' + +const execa = require('execa') + +function exec (cmd, args, opts, handlers) { + opts = opts || {} + let err = '' + let result = '' + let callback + // Handy method if we just want the result and err returned in a callback + if (typeof handlers === 'function') { + callback = handlers + handlers = { + error: callback, + data: (data) => { + result += data + }, + done: () => { + if (err) return callback(new Error(err)) + callback(null, result) + } + } + } + + // The listeners that will actually be set on the process + const listeners = { + data: handlers.data, + error: (data) => { + err += data + }, + done: (code) => { + if (code !== 0) { + return handlers.error( + new Error(`non-zero exit code ${code}\n + while running: ${cmd} ${args.join(' ')}\n\n + ${err}`) + ) + } + if (handlers.done) handlers.done() + } + } + + const command = execa(cmd, args, opts) + + if (listeners.data) command.stdout.on('data', listeners.data) + + command.stderr.on('data', listeners.error) + + // If command fails to execute return directly to the handler + command.on('error', handlers.error) + + command.on('close', listeners.done) + + return command +} + +module.exports = exec diff --git a/src/index.js b/src/index.js index 7e1b93c7..6b9caa78 100644 --- a/src/index.js +++ b/src/index.js @@ -9,10 +9,15 @@ function tempDir () { return join(os.tmpdir(), `ipfs_${String(Math.random()).substr(2)}`) } +const defaultOptions = { + 'Addresses.Swarm': ['/ip4/0.0.0.0/tcp/0'], + 'Addresses.Gateway': '', + 'Addresses.API': '/ip4/127.0.0.1/tcp/0', + disposable: true, + init: true +} + module.exports = { - version (done) { - (new Node()).version(done) - }, local (path, done) { if (!done) { done = path @@ -24,36 +29,23 @@ module.exports = { done(null, new Node(path)) }) }, - disposableApi (opts, done) { + create (opts, done) { if (typeof opts === 'function') { done = opts opts = {} } - this.disposable(opts, (err, node) => { - if (err) return done(err) - node.startDaemon(done) - }) - }, - disposable (opts, done) { - if (typeof opts === 'function') { - done = opts - opts = {} - } - opts['Addresses.Swarm'] = ['/ip4/0.0.0.0/tcp/0'] - opts['Addresses.Gateway'] = '' - opts['Addresses.API'] = '/ip4/127.0.0.1/tcp/0' - if (opts.apiAddr) { - opts['Addresses.API'] = opts.apiAddr - } + let options = {} + Object.assign(options, defaultOptions, opts || {}) - if (opts.gatewayAddr) { - opts['Addresses.Gateway'] = opts.gatewayAddr - } + const repoPath = options.repoPath || tempDir() + const disposable = options.disposable + delete options.disposable + delete options.repoPath - const node = new Node(opts.repoPath || tempDir(), opts, true) + const node = new Node(repoPath, options, disposable) - if (typeof opts.init === 'boolean' && opts.init === false) { + if (typeof options.init === 'boolean' && options.init === false) { process.nextTick(() => { done(null, node) }) diff --git a/src/node.js b/src/node.js index 499acb7d..801e10b8 100644 --- a/src/node.js +++ b/src/node.js @@ -1,33 +1,18 @@ 'use strict' const fs = require('fs') -const run = require('subcomandante') const series = require('run-series') -const ipfs = require('ipfs-api') +const IpfsAPI = require('ipfs-api') const multiaddr = require('multiaddr') const rimraf = require('rimraf') const shutdown = require('shutdown') + const path = require('path') const join = path.join -const rootPath = process.env.testpath ? process.env.testpath : __dirname - -const ipfsDefaultPath = findIpfsExecutable() - -const GRACE_PERIOD = 7500 // amount of ms to wait before sigkill - -function findIpfsExecutable () { - let npm3Path = path.join(rootPath, '../../', '/go-ipfs-dep/go-ipfs/ipfs') - - let npm2Path = path.join(rootPath, '../', 'node_modules/go-ipfs-dep/go-ipfs/ipfs') - - try { - fs.statSync(npm3Path) - return npm3Path - } catch (e) { - return npm2Path - } -} +const config = require('./config') +const exec = require('./exec') +const binary = require('./binary')() function configureNode (node, conf, done) { const keys = Object.keys(conf) @@ -35,13 +20,10 @@ function configureNode (node, conf, done) { const value = conf[key] const env = {env: node.env} - run(node.exec, ['config', key, '--json', JSON.stringify(value)], env) - .on('error', cb) - .on('end', cb) + node._run(['config', key, '--json', JSON.stringify(value)], env, cb) }), done) } -// Consistent error handling function parseConfig (path, done) { try { const file = fs.readFileSync(join(path, 'config')) @@ -56,23 +38,36 @@ module.exports = class Node { constructor (path, opts, disposable) { this.path = path this.opts = opts || {} - this.exec = process.env.IPFS_EXEC || ipfsDefaultPath + this.exec = binary.path() + this.subprocess = null + // Only set when the daemon is started this.initialized = fs.existsSync(path) this.clean = true - this.env = Object.assign({}, process.env, {IPFS_PATH: path}) + this.disposable = disposable + this.env = Object.assign({}, process.env, {IPFS_PATH: path}) if (this.opts.env) Object.assign(this.env, this.opts.env) } - _run (args, envArg, done) { - let result = '' - run(this.exec, args, envArg) - .on('error', done) - .on('data', (data) => { - result += data - }) - .on('end', () => done(null, result.trim())) + _run (args, opts, handlers, done) { + opts = opts || {} + // If no done callback return error to the error handler + let errorHandler = done || handlers.error + // Single handler `callback(err, res)` provided + if (typeof handlers === 'function') { + errorHandler = handlers + } + // Cleanup the process on exit + opts.cleanup = true + + binary.check((err) => { + if (err) return errorHandler(err) + const command = exec(this.exec, args, opts, handlers) + + // If done callback return command + if (done) done(null, command) + }) } init (initOpts, done) { @@ -80,7 +75,6 @@ module.exports = class Node { done = initOpts initOpts = {} } - let buf = '' const keySize = initOpts.keysize || 2048 @@ -89,19 +83,15 @@ module.exports = class Node { this.env.IPFS_PATH = this.path } - run(this.exec, ['init', '-b', keySize], {env: this.env}) - .on('error', done) - .on('data', (data) => { - buf += data - }) - .on('end', () => { - configureNode(this, this.opts, (err) => { - if (err) return done(err) - this.clean = false - this.initialized = true - done(null, this) - }) + this._run(['init', '-b', keySize], {env: this.env}, (err, result) => { + if (err) return done(err) + configureNode(this, this.opts, (err) => { + if (err) return done(err) + this.clean = false + this.initialized = true + done(null, this) }) + }) if (this.disposable) { shutdown.addHandler('disposable', 1, this.shutdown.bind(this)) @@ -110,43 +100,48 @@ module.exports = class Node { // cleanup tmp files shutdown (done) { - if (!this.clean && this.disposable) { - rimraf(this.path, (err) => { - if (err) throw err - done() - }) - } + if (this.clean || !this.disposable) return done() + rimraf(this.path, done) } startDaemon (done) { parseConfig(this.path, (err, conf) => { if (err) return done(err) - this.subprocess = run(this.exec, ['daemon'], {env: this.env}) - .on('error', (err) => { + this._run(['daemon'], {env: this.env}, { + error: (err) => { if (String(err).match('daemon is running')) { // we're good - done(null, ipfs(conf.Addresses.API)) - } else if (String(err).match('non-zero exit code')) { - // ignore when kill -9'd - } else { + return done() + } + // ignore when kill -9'd + if (!String(err).match('non-zero exit code')) { done(err) } - }) - .on('data', (data) => { + }, + data: (data) => { const match = String(data).trim().match(/API server listening on (.*)/) if (match) { this.apiAddr = match[1] - const addr = multiaddr(this.apiAddr).nodeAddress() - const api = ipfs(this.apiAddr) - api.apiHost = addr.address - api.apiPort = addr.port - done(null, api) + done() } - }) + } + }, (err, process) => { + if (err) return done(err) + this.subprocess = process + }) }) } + apiCtl () { + if (!this.apiAddr) return null + const addr = multiaddr(this.apiAddr).nodeAddress() + const api = IpfsAPI(addr) + api.apiHost = addr.address + api.apiPort = addr.port + return api + } + stopDaemon (done) { if (!done) done = () => {} if (!this.subprocess) return done(null) @@ -156,7 +151,7 @@ module.exports = class Node { const timeout = setTimeout(() => { this.subprocess.kill('SIGKILL') done(null) - }, GRACE_PERIOD) + }, config.gracePeriod) this.subprocess.on('close', () => { clearTimeout(timeout) @@ -166,24 +161,25 @@ module.exports = class Node { this.subprocess = null } - daemonPid () { - return this.subprocess && this.subprocess.pid - } - getConfig (key, done) { if (typeof key === 'function') { done = key key = '' } - this._run(['config', key], {env: this.env}, done) + this._run(['config', key], {env: this.env}, (err, config) => { + if (err) return done(err) + try { + const parsed = JSON.parse(config) + return done(null, parsed) + } catch (err) { + return done(err) + } + }) } setConfig (key, value, done) { - run(this.exec, ['config', key, value, '--json'], {env: this.env}) - .on('error', done) - .on('data', (data) => {}) - .on('end', () => done()) + this._run(['config', key, value, '--json'], {env: this.env}, done) } replaceConf (file, done) { diff --git a/test/binary.spec.js b/test/binary.spec.js new file mode 100644 index 00000000..4f7d9c76 --- /dev/null +++ b/test/binary.spec.js @@ -0,0 +1,48 @@ +/* eslint-env mocha */ +'use strict' + +const config = require('../src/config') +const expect = require('chai').expect +const fs = require('fs') +const rimraf = require('rimraf') +const join = require('path').join +const Binary = require('../src/binary') + +describe('external ipfs binary', function () { + this.timeout(30000) + + it('allows passing via $IPFS_EXEC', (done) => { + process.env.IPFS_EXEC = '/some/path' + const binary = Binary() + expect(binary.path()).to.be.equal('/some/path/ipfs') + expect(binary.checked).to.be.false + + process.env.IPFS_EXEC = '' + done() + }) + + it('allows path to be set through function argument', (done) => { + const binary = Binary('/some/path2') + expect(binary.path()).to.be.equal('/some/path2/ipfs') + expect(binary.checked).to.be.false + + done() + }) + + it('the binary download works accordingly to the default path', (done) => { + const execPath = join(config.defaultExecPath, 'ipfs') + // Remove default exec dir at start, if any + rimraf.sync(config.defaultExecPath) + expect(fs.existsSync(execPath)).to.be.false + const binary = Binary() + expect(binary.path()).to.be.equal(execPath) + expect(binary.checked).to.be.false + + binary.check((err) => { + if (err) throw err + expect(fs.existsSync(execPath)).to.be.true + expect(binary.checked).to.be.true + done() + }) + }) +}) diff --git a/test/index.spec.js b/test/index.spec.js index 00926db5..d997dac5 100644 --- a/test/index.spec.js +++ b/test/index.spec.js @@ -2,61 +2,26 @@ 'use strict' const ipfsd = require('../src') -const assert = require('assert') +const config = require('../src/config') +const exec = require('../src/exec') +const expect = require('chai').expect const ipfsApi = require('ipfs-api') -const run = require('subcomandante') const fs = require('fs') +const series = require('run-series') const rimraf = require('rimraf') -const mkdirp = require('mkdirp') const path = require('path') -describe('ipfs executable path', function () { - this.timeout(2000) - let Node - - it('has the correct path when installed with npm3', (done) => { - process.env.testpath = '/tmp/ipfsd-ctl-test/node_modules/ipfsd-ctl/lib' // fake __dirname - let npm3Path = '/tmp/ipfsd-ctl-test/node_modules/go-ipfs-dep/go-ipfs' - - mkdirp(npm3Path, (err) => { - if (err) { - throw err - } - - fs.writeFileSync(path.join(npm3Path, 'ipfs')) - delete require.cache[require.resolve('../src/node.js')] - Node = require('../src/node.js') - var node = new Node() - assert.equal(node.exec, '/tmp/ipfsd-ctl-test/node_modules/go-ipfs-dep/go-ipfs/ipfs') - rimraf('/tmp/ipfsd-ctl-test', done) - }) - }) - - it('has the correct path when installed with npm2', (done) => { - process.env.testpath = '/tmp/ipfsd-ctl-test/node_modules/ipfsd-ctl/lib' // fake __dirname - let npm2Path = '/tmp/ipfsd-ctl-test/node_modules/ipfsd-ctl/node_modules/go-ipfs-dep/go-ipfs' - - mkdirp(npm2Path, (err) => { - if (err) { - throw err - } - - fs.writeFileSync(path.join(npm2Path, 'ipfs')) - delete require.cache[require.resolve('../src/node.js')] - Node = require('../src/node.js') - var node = new Node() - assert.equal(node.exec, '/tmp/ipfsd-ctl-test/node_modules/ipfsd-ctl/node_modules/go-ipfs-dep/go-ipfs/ipfs') - rimraf('/tmp/ipfsd-ctl-test', done) - }) - }) -}) - describe('disposable node with local api', function () { - this.timeout(20000) + this.timeout(30000) let ipfs + let node + before((done) => { - ipfsd.disposable((err, node) => { + // Remove default exec dir at start, if any + rimraf.sync(config.defaultExecPath) + ipfsd.create((err, ipfsNode) => { if (err) throw err + node = ipfsNode node.startDaemon((err, ignore) => { if (err) throw err ipfs = ipfsApi(node.apiAddr) @@ -66,107 +31,151 @@ describe('disposable node with local api', function () { }) it('should have started the daemon and returned an api', () => { - assert(ipfs) - assert(ipfs.id) + expect(ipfs).to.be.ok + expect(ipfs.id).to.be.ok + }) + + it('should have downloaded the binary to the default directory', () => { + expect(fs.existsSync(config.defaultExecPath)).to.be.true }) let store, retrieve before((done) => { const blorb = Buffer('blorb') - ipfs.block.put(blorb, (err, res) => { - if (err) throw err - store = res.Key - - ipfs.block.get(res.Key, (err, res) => { - if (err) throw err - let buf = '' - res - .on('data', (data) => { - buf += data - }) - .on('end', () => { - retrieve = buf - done() - }) - }) + series([ + (cb) => { + ipfs.block.put(blorb, (err, res) => { + if (err) return cb(err) + store = res.Key + cb() + }) + }, + (cb) => ipfs.block.get(store, cb) + ], (err, results) => { + if (err) return done(err) + const res = results[1] + let buf = '' + res + .on('data', (data) => { + buf += data + }) + .on('end', () => { + retrieve = buf + done() + }) }) }) + it('should be able to store objects', () => { - assert.equal(store, 'QmPv52ekjS75L4JmHpXVeuJ5uX2ecSfSZo88NSyxwA3rAQ') + expect(store).to.be.equal('QmPv52ekjS75L4JmHpXVeuJ5uX2ecSfSZo88NSyxwA3rAQ') }) + it('should be able to retrieve objects', () => { - assert.equal(retrieve, 'blorb') + expect(retrieve).to.be.equal('blorb') + }) +}) + +describe('disposable node without being initialized', function () { + this.timeout(30000) + let node + + it('node should be clean', (done) => { + ipfsd.create({init: false}, (err, ipfsNode) => { + node = ipfsNode + expect(err).to.not.exist + expect(node.clean).to.be.true + done() + }) + }) + + it('node should not be clean', (done) => { + node.init((err, ignore) => { + expect(err).to.not.exist + expect(node.clean).to.be.false + expect(fs.existsSync(config.defaultExecPath)).to.be.true + done() + }) }) }) -describe('disposableApi node', function () { - this.timeout(20000) +describe('disposable node API', function () { + this.timeout(30000) let ipfs before((done) => { - ipfsd.disposableApi((err, api) => { + ipfsd.create((err, node) => { if (err) throw err - ipfs = api - done() + node.startDaemon((err) => { + if (err) throw err + ipfs = node.apiCtl() + done() + }) }) }) it('should have started the daemon and returned an api with host/port', () => { - assert(ipfs) - assert(ipfs.id) - assert(ipfs.apiHost) - assert(ipfs.apiPort) + expect(ipfs).to.be.ok + expect(ipfs.id).to.be.ok + expect(ipfs.apiHost).to.be.ok + expect(ipfs.apiPort).to.be.ok }) let store, retrieve before((done) => { const blorb = Buffer('blorb') - ipfs.block.put(blorb, (err, res) => { - if (err) throw err - store = res.Key - - ipfs.block.get(res.Key, (err, res) => { - if (err) throw err - let buf = '' - res - .on('data', (data) => { - buf += data - }) - .on('end', () => { - retrieve = buf - done() - }) - }) + series([ + (cb) => { + ipfs.block.put(blorb, (err, res) => { + if (err) return cb(err) + store = res.Key + cb() + }) + }, + (cb) => ipfs.block.get(store, cb) + ], (err, results) => { + if (err) return done(err) + const res = results[1] + let buf = '' + res + .on('data', (data) => { + buf += data + }) + .on('end', () => { + retrieve = buf + done() + }) }) }) + it('should be able to store objects', () => { - assert.equal(store, 'QmPv52ekjS75L4JmHpXVeuJ5uX2ecSfSZo88NSyxwA3rAQ') + expect(store).to.be.equal('QmPv52ekjS75L4JmHpXVeuJ5uX2ecSfSZo88NSyxwA3rAQ') }) + it('should be able to retrieve objects', () => { - assert.equal(retrieve, 'blorb') + expect(retrieve).to.be.equal('blorb') }) }) describe('starting and stopping', function () { - this.timeout(20000) + this.timeout(30000) let node describe('init', () => { before((done) => { - ipfsd.disposable((err, res) => { + ipfsd.create((err, res) => { if (err) throw err node = res done() }) }) - it('should returned a node', () => { - assert(node) + it('should have returned a node', () => { + expect(node).to.be.ok }) it('daemon should not be running', () => { - assert(!node.daemonPid()) + expect(node.subprocess).to.not.be.ok }) }) @@ -175,21 +184,19 @@ describe('starting and stopping', function () { describe('starting', () => { let ipfs before((done) => { - node.startDaemon((err, res) => { + node.startDaemon((err) => { if (err) throw err - pid = node.daemonPid() - ipfs = res + pid = node.subprocess.pid + ipfs = node.apiCtl() // actually running? - run('kill', ['-0', pid]) - .on(err, (err) => { throw err }) - .on('end', () => { done() }) + exec('kill', ['-0', pid], {cleanup: true}, done) }) }) it('should be running', () => { - assert(ipfs.id) + expect(ipfs.id).to.be.ok }) }) @@ -202,18 +209,19 @@ describe('starting and stopping', function () { }) // make sure it's not still running const poll = setInterval(() => { - run('kill', ['-0', pid]) - .on('error', () => { + exec('kill', ['-0', pid], {cleanup: true}, { + error: () => { clearInterval(poll) done() done = () => {} // so it does not get called again - }) + } + }) }, 100) }) it('should be stopped', () => { - assert(!node.daemonPid()) - assert(stopped) + expect(node.subprocess).to.not.be.ok + expect(stopped).to.be.true }) }) }) @@ -227,7 +235,7 @@ describe('setting up and initializing a local node', () => { }) it('should not have a directory', () => { - assert.equal(fs.existsSync('/tmp/ipfstestpath1'), false) + expect(fs.existsSync('/tmp/ipfstestpath1')).to.be.false }) }) @@ -242,45 +250,40 @@ describe('setting up and initializing a local node', () => { }) it('should have returned a node', () => { - assert(node) + expect(node).to.be.ok }) it('should not be initialized', () => { - assert.equal(node.initialized, false) + expect(node.initialized).to.be.false }) describe('initialize', function () { - this.timeout(10000) + this.timeout(30000) - before((done) => { - node.init((err) => { - if (err) throw err - done() - }) - }) + before((done) => node.init(done)) it('should have made a directory', () => { - assert.equal(fs.existsSync(testpath1), true) + expect(fs.existsSync(testpath1)).to.be.true }) it('should be initialized', () => { - assert.equal(node.initialized, true) + expect(node.initialized).to.be.true }) it('should be initialized', () => { - assert.equal(node.initialized, true) + expect(node.initialized).to.be.true }) }) }) }) describe('change config values of a disposable node', function () { - this.timeout(20000) + this.timeout(30000) let ipfsNode before((done) => { - ipfsd.disposable((err, node) => { + ipfsd.create((err, node) => { if (err) { throw err } @@ -289,65 +292,75 @@ describe('change config values of a disposable node', function () { }) }) - it('Should return a config value', (done) => { + it('should return a config value', (done) => { ipfsNode.getConfig('Bootstrap', (err, config) => { if (err) { throw err } - assert(config) + expect(config).to.be.ok done() }) }) - it('Should set a config value', (done) => { + it('should set a config value', (done) => { ipfsNode.setConfig('Bootstrap', null, (err) => { if (err) { throw err } ipfsNode.getConfig('Bootstrap', (err, config) => { - if (err) { - throw err - } - assert.equal(config, 'null') + expect(err).to.not.exist + expect(config).to.be.null done() }) }) }) -}) -describe('external ipfs binaray', () => { - it('allows passing via $IPFS_EXEC', (done) => { - process.env.IPFS_EXEC = '/some/path' - ipfsd.local((err, node) => { - if (err) throw err + it('should replace the config with new file', (done) => { + const configPath = path.join(__dirname, 'test-data', 'config.json') + const expectedConfig = JSON.parse(fs.readFileSync(configPath, 'utf8')) - assert.equal(node.exec, '/some/path') + ipfsNode.replaceConf(configPath, (err) => { + expect(err).to.not.exist - process.env.IPFS_EXEC = '' + ipfsNode.getConfig('Bootstrap', (err, config) => { + expect(err).to.not.exist + expect(config).to.be.deep.equal(expectedConfig.Bootstrap) + done() + }) + }) + }) + + it('should fail to read a bad config file', (done) => { + const configPath = path.join(__dirname, 'test-data', 'badconfig') + + ipfsNode.replaceConf(configPath, (err, result) => { + expect(err).to.exist done() }) }) }) describe('version', () => { - it('prints the version', (done) => { - ipfsd.version((err, version) => { + it('prints the version of a node', (done) => { + ipfsd.create((err, node) => { if (err) throw err - - assert(version) - done() + node.version((err, version) => { + if (err) throw err + expect(version).to.be.ok + done() + }) }) }) }) describe('ipfs-api version', function () { - this.timeout(20000) + this.timeout(30000) let ipfs before((done) => { - ipfsd.disposable((err, node) => { + ipfsd.create((err, node) => { if (err) throw err node.startDaemon((err, ignore) => { if (err) throw err @@ -363,8 +376,8 @@ describe('ipfs-api version', function () { if (err) throw err const added = res[res.length - 1] - assert(added) - assert.equal(added.Hash, 'Qmafmh1Cw3H1bwdYpaaj5AbCW4LkYyUWaM7Nykpn5NZoYL') + expect(added).to.be.ok + expect(added.Hash).to.be.equal('Qmf83JawSYirKchqGUj3Do5Zkt9XtbncA4TjrfAucBJFWZ') done() }) }) diff --git a/test/test-data/badconfig b/test/test-data/badconfig new file mode 100644 index 00000000..b55c6462 --- /dev/null +++ b/test/test-data/badconfig @@ -0,0 +1,3 @@ +{ + bad config +} diff --git a/test/test-data/config.json b/test/test-data/config.json new file mode 100644 index 00000000..98680709 --- /dev/null +++ b/test/test-data/config.json @@ -0,0 +1,69 @@ +{ + "Identity": { + "PeerID": "QmQ2zigjQikYnyYUSXZydNXrDRhBut2mubwJBaLXobMt3A" + }, + "Datastore": { + "Type": "", + "Path": "", + "StorageMax": "", + "StorageGCWatermark": 0, + "GCPeriod": "", + "Params": null, + "NoSync": false + }, + "Addresses": { + "Swarm": ["/ip4/0.0.0.0/tcp/4001", "/ip6/::/tcp/4001"], + "API": "/ip4/127.0.0.1/tcp/6001", + "Gateway": "/ip4/127.0.0.1/tcp/9080" + }, + "Mounts": { + "IPFS": "/ipfs", + "IPNS": "/ipns", + "FuseAllowOther": false + }, + "Version": { + "Current": "0.4.0-dev", + "Check": "error", + "CheckDate": "0001-01-01T00:00:00Z", + "CheckPeriod": "172800000000000", + "AutoUpdate": "minor" + }, + "Discovery": { + "MDNS": { + "Enabled": true, + "Interval": 10 + } + }, + "Ipns": { + "RepublishPeriod": "", + "RecordLifetime": "", + "ResolveCacheSize": 128 + }, + "Bootstrap": ["/ip4/104.131.131.82/tcp/4001/ipfs/QmaCpDMGvV2BGHeYERUEnRQAwe3N8SzbUtfsmvsqQLuvuJ", "/ip4/104.236.176.52/tcp/4001/ipfs/QmSoLnSGccFuZQJzRadHn95W2CrSFmZuTdDWP8HXaHca9z", "/ip4/104.236.179.241/tcp/4001/ipfs/QmSoLPppuBtQSGwKDZT2M73ULpjvfd3aZ6ha4oFGL1KrGM", "/ip4/162.243.248.213/tcp/4001/ipfs/QmSoLueR4xBeUbY9WZ9xGUUxunbKWcrNFTDAadQJmocnWm", "/ip4/128.199.219.111/tcp/4001/ipfs/QmSoLSafTMBsPKadTEgaXctDQVcqN88CNLHXMkTNwMKPnu", "/ip4/104.236.76.40/tcp/4001/ipfs/QmSoLV4Bbm51jM9C4gDYZQ9Cy3U6aXMJDAbzgu2fzaDs64", "/ip4/178.62.158.247/tcp/4001/ipfs/QmSoLer265NRgSp2LA3dPaeykiS1J6DifTC88f5uVQKNAd", "/ip4/178.62.61.185/tcp/4001/ipfs/QmSoLMeWqB7YGVLJN3pNLQpmmEk35v6wYtsMGLzSr5QBU3", "/ip4/104.236.151.122/tcp/4001/ipfs/QmSoLju6m7xTh3DuokvT3886QRYqxAzb1kShaanJgW36yx"], + "Tour": { + "Last": "" + }, + "Gateway": { + "HTTPHeaders": null, + "RootRedirect": "", + "Writable": false + }, + "SupernodeRouting": { + "Servers": ["/ip4/104.236.176.52/tcp/4002/ipfs/QmXdb7tWTxdFEQEFgWBqkuYSrZd3mXrC7HxkD4krGNYx2U", "/ip4/104.236.179.241/tcp/4002/ipfs/QmVRqViDByUxjUMoPnjurjKvZhaEMFDtK35FJXHAM4Lkj6", "/ip4/104.236.151.122/tcp/4002/ipfs/QmSZwGx8Tn8tmcM4PtDJaMeUQNRhNFdBLVGPzRiNaRJtFH", "/ip4/162.243.248.213/tcp/4002/ipfs/QmbHVEEepCi7rn7VL7Exxpd2Ci9NNB6ifvqwhsrbRMgQFP", "/ip4/128.199.219.111/tcp/4002/ipfs/Qmb3brdCYmKG1ycwqCbo6LUwWxTuo3FisnJV2yir7oN92R", "/ip4/104.236.76.40/tcp/4002/ipfs/QmdRBCV8Cz2dGhoKLkD3YjPwVFECmqADQkx5ZteF2c6Fy4", "/ip4/178.62.158.247/tcp/4002/ipfs/QmUdiMPci7YoEUBkyFZAh2pAbjqcPr7LezyiPD2artLw3v", "/ip4/178.62.61.185/tcp/4002/ipfs/QmVw6fGNqBixZE4bewRLT2VXX7fAHUHs8JyidDiJ1P7RUN"] + }, + "API": { + "HTTPHeaders": { + "Access-Control-Allow-Origin": [ + "http://example.com" + ] + } + }, + "Swarm": { + "AddrFilters": null + }, + "Log": { + "MaxSizeMB": 250, + "MaxBackups": 1, + "MaxAgeDays": 0 + } +}