diff --git a/API.md b/API.md
new file mode 100644
index 000000000..f0a8f410e
--- /dev/null
+++ b/API.md
@@ -0,0 +1,781 @@
+ - running against version 0.3.9
+# TOC
+ - [IPFS Node.js API wrapper tests](#ipfs-nodejs-api-wrapper-tests)
+ - [.send](#ipfs-nodejs-api-wrapper-tests-send)
+ - [.add](#ipfs-nodejs-api-wrapper-tests-add)
+ - [.cat](#ipfs-nodejs-api-wrapper-tests-cat)
+ - [.ls](#ipfs-nodejs-api-wrapper-tests-ls)
+ - [.config](#ipfs-nodejs-api-wrapper-tests-config)
+ - [.update (currently disabled, wait for IPFS 0.4.0 release](#ipfs-nodejs-api-wrapper-tests-update-currently-disabled-wait-for-ipfs-040-release)
+ - [.version](#ipfs-nodejs-api-wrapper-tests-version)
+ - [.commands](#ipfs-nodejs-api-wrapper-tests-commands)
+ - [.mount](#ipfs-nodejs-api-wrapper-tests-mount)
+ - [.diag](#ipfs-nodejs-api-wrapper-tests-diag)
+ - [.block](#ipfs-nodejs-api-wrapper-tests-block)
+ - [.object](#ipfs-nodejs-api-wrapper-tests-object)
+ - [.swarm](#ipfs-nodejs-api-wrapper-tests-swarm)
+ - [.ping](#ipfs-nodejs-api-wrapper-tests-ping)
+ - [.id](#ipfs-nodejs-api-wrapper-tests-id)
+ - [.pin](#ipfs-nodejs-api-wrapper-tests-pin)
+ - [.log](#ipfs-nodejs-api-wrapper-tests-log)
+ - [.name](#ipfs-nodejs-api-wrapper-tests-name)
+ - [.refs](#ipfs-nodejs-api-wrapper-tests-refs)
+ - [.dht](#ipfs-nodejs-api-wrapper-tests-dht)
+
+
+
+# IPFS Node.js API wrapper tests
+connect Node a to b and c.
+
+```js
+this.timeout(5000)
+const addrs = {}
+let counter = 0
+collectAddr('b', finish)
+collectAddr('c', finish)
+function finish () {
+ counter++
+ if (counter === 2) {
+ dial()
+ }
+}
+function collectAddr (key, cb) {
+ apiClients[key].id((err, id) => {
+ if (err) {
+ throw err
+ }
+ // note to self: HTTP API port !== Node port
+ addrs[key] = id.Addresses[0]
+ cb()
+ })
+}
+function dial () {
+ apiClients['a'].swarm.connect(addrs['b'], (err, res) => {
+ if (err) {
+ throw err
+ }
+ apiClients['a'].swarm.connect(addrs['c'], err => {
+ if (err) {
+ throw err
+ }
+ done()
+ })
+ })
+}
+```
+
+has the api object.
+
+```js
+assert(apiClients['a'])
+assert(apiClients['a'].id)
+```
+
+
+## .send
+
+## .add
+add file.
+
+```js
+if (!isNode) {
+ return done()
+}
+this.timeout(10000)
+const file = new File({
+ cwd: path.dirname(testfilePath),
+ base: path.dirname(testfilePath),
+ path: testfilePath,
+ contents: new Buffer(testfile)
+})
+apiClients['a'].add(file, (err, res) => {
+ if (err) throw err
+ const added = res[0] != null ? res[0] : res
+ assert.equal(added.Hash, 'Qma4hjFTnCasJ8PVp3mZbZK5g2vGDT4LByLJ7m8ciyRFZP')
+ assert.equal(added.Name, path.basename(testfilePath))
+ done()
+})
+```
+
+add buffer.
+
+```js
+this.timeout(10000)
+let buf = new Buffer(testfile)
+apiClients['a'].add(buf, (err, res) => {
+ if (err) throw err
+ // assert.equal(res.length, 1)
+ const added = res[0] !== null ? res[0] : res
+ assert.equal(added.Hash, 'Qma4hjFTnCasJ8PVp3mZbZK5g2vGDT4LByLJ7m8ciyRFZP')
+ done()
+})
+```
+
+add BIG buffer.
+
+```js
+if (!isNode) {
+ return done()
+}
+this.timeout(10000)
+apiClients['a'].add(testfileBig, (err, res) => {
+ if (err) throw err
+ // assert.equal(res.length, 1)
+ const added = res[0] !== null ? res[0] : res
+ assert.equal(added.Hash, 'Qme79tX2bViL26vNjPsF3DP1R9rMKMvnPYJiKTTKPrXJjq')
+ done()
+})
+```
+
+add path.
+
+```js
+if (!isNode) {
+ return done()
+}
+this.timeout(10000)
+apiClients['a'].add(testfilePath, (err, res) => {
+ if (err) throw err
+ const added = res[0] != null ? res[0] : res
+ assert.equal(added.Hash, 'Qma4hjFTnCasJ8PVp3mZbZK5g2vGDT4LByLJ7m8ciyRFZP')
+ done()
+})
+```
+
+add a nested dir.
+
+```js
+this.timeout(10000)
+apiClients['a'].add(__dirname + '/test-folder', { recursive: true }, (err, res) => {
+ if (isNode) {
+ if (err) throw err
+ const added = res[res.length - 1]
+ assert.equal(added.Hash, 'QmSzLpCVbWnEm3XoTWnv6DT6Ju5BsVoLhzvxKXZeQ2cmdg')
+ done()
+ } else {
+ assert.equal(err.message, 'Recursive uploads are not supported in the browser')
+ done()
+ }
+})
+```
+
+add stream.
+
+```js
+this.timeout(10000)
+const stream = new Readable()
+stream.push('Hello world')
+stream.push(null)
+apiClients['a'].add(stream, (err, res) => {
+ if (err) throw err
+ const added = res[0] != null ? res[0] : res
+ assert.equal(added.Hash, 'QmNRCQWfgze6AbBCaT1rkrkV5tJ2aP4oTNPb5JZcXYywve')
+ done()
+})
+```
+
+add url.
+
+```js
+this.timeout(10000)
+const url = 'https://raw.githubusercontent.com/ipfs/js-ipfs-api/2a9cc63d7427353f2145af6b1a768a69e67c0588/README.md'
+apiClients['a'].add(url, (err, res) => {
+ if (err) throw err
+ const added = res[0] != null ? res[0] : res
+ assert.equal(added.Hash, 'QmZmHgEX9baxUn3qMjsEXQzG6DyNcrVnwieQQTrpDdrFvt')
+ done()
+})
+```
+
+
+## .cat
+cat.
+
+```js
+this.timeout(10000)
+apiClients['a'].cat('Qma4hjFTnCasJ8PVp3mZbZK5g2vGDT4LByLJ7m8ciyRFZP', (err, res) => {
+ if (err) {
+ throw err
+ }
+ let buf = ''
+ res
+ .on('error', err => { throw err })
+ .on('data', data => buf += data)
+ .on('end', () => {
+ assert.equal(buf, testfile)
+ done()
+ })
+})
+```
+
+cat BIG file.
+
+```js
+if (!isNode) {
+ return done()
+}
+this.timeout(1000000)
+apiClients['a'].cat('Qme79tX2bViL26vNjPsF3DP1R9rMKMvnPYJiKTTKPrXJjq', (err, res) => {
+ if (err) {
+ throw err
+ }
+ testfileBig = require('fs').createReadStream(__dirname + '/15mb.random', { bufferSize: 128 })
+ // Do not blow out the memory of nodejs :)
+ streamEqual(res, testfileBig, (err, equal) => {
+ if (err) throw err
+ assert(equal)
+ done()
+ })
+})
+```
+
+
+## .ls
+ls.
+
+```js
+if (!isNode) {
+ return done()
+}
+this.timeout(100000)
+apiClients['a'].ls(folder, (err, res) => {
+ if (err) {
+ throw err
+ }
+ const objs = {
+ Hash: 'QmSzLpCVbWnEm3XoTWnv6DT6Ju5BsVoLhzvxKXZeQ2cmdg',
+ Links: [{
+ Name: 'add.js',
+ Hash: 'QmcUYKmQxmTcFom4R4UZP7FWeQzgJkwcFn51XrvsMy7PE9',
+ Size: 487,
+ Type: 2
+ }, {
+ Name: 'cat.js',
+ Hash: 'QmNeHxDfQfjVFyYj2iruvysLH9zpp78v3cu1s3BZq1j5hY',
+ Size: 368,
+ Type: 2
+ }, {
+ Name: 'files',
+ Hash: 'QmTYFLz5vsdMpq4XXw1a1pSxujJc9Z5V3Aw1Qg64d849Zy',
+ Size: 132,
+ Type: 1
+ }, {
+ Name: 'ipfs-add.js',
+ Hash: 'QmU7wetVaAqc3Meurif9hcYBHGvQmL5QdpPJYBoZizyTNL',
+ Size: 333,
+ Type: 2
+ }, {
+ Name: 'ls.js',
+ Hash: 'QmctZfSuegbi2TMFY2y3VQjxsH5JbRBu7XmiLfHNvshhio',
+ Size: 432,
+ Type: 2
+ }, {
+ Hash: 'QmTDH2RXGn8XyDAo9YyfbZAUXwL1FCr44YJCN9HBZmL9Gj',
+ Name: 'test-folder',
+ Size: 2212,
+ Type: 1
+ }, {
+ Name: 'version.js',
+ Hash: 'QmbkMNB6rwfYAxRvnG9CWJ6cKKHEdq2ZKTozyF5FQ7H8Rs',
+ Size: 155,
+ Type: 2 }]
+ }
+ assert.deepEqual(res.Objects[0], objs)
+ done()
+})
+```
+
+
+## .config
+.config.{set, get}.
+
+```js
+this.timeout(10000)
+const confKey = 'arbitraryKey'
+const confVal = 'arbitraryVal'
+apiClients['a'].config.set(confKey, confVal, (err, res) => {
+ if (err) throw err
+ apiClients['a'].config.get(confKey, (err, res) => {
+ if (err) throw err
+ assert.equal(res.Value, confVal)
+ done()
+ })
+})
+```
+
+.config.show.
+
+```js
+this.timeout(10000)
+apiClients['c'].config.show((err, res) => {
+ if (err) {
+ throw err
+ }
+ assert(res)
+ done()
+})
+```
+
+.config.replace.
+
+```js
+this.timeout(10000)
+if (!isNode) {
+ return done()
+}
+apiClients['c'].config.replace(__dirname + '/r-config.json', (err, res) => {
+ if (err) {
+ throw err
+ }
+ assert.equal(res, null)
+ done()
+})
+```
+
+
+## .update (currently disabled, wait for IPFS 0.4.0 release
+
+## .version
+checks the version.
+
+```js
+this.timeout(10000)
+apiClients['a'].version((err, res) => {
+ if (err) {
+ throw err
+ }
+ assert(res)
+ assert(res.Version)
+ console.log(' - running against version', res.Version)
+ done()
+})
+```
+
+
+## .commands
+lists commands.
+
+```js
+this.timeout(10000)
+apiClients['a'].commands((err, res) => {
+ if (err) {
+ throw err
+ }
+ assert(res)
+ done()
+})
+```
+
+
+## .diag
+.diag.net.
+
+```js
+this.timeout(1000000)
+apiClients['a'].diag.net((err, res) => {
+ if (err) {
+ throw err
+ }
+ assert(res)
+ done()
+})
+```
+
+.diag.sys.
+
+```js
+apiClients['a'].diag.sys((err, res) => {
+ if (err) {
+ throw err
+ }
+ assert(res)
+ assert(res.memory)
+ assert(res.diskinfo)
+ done()
+})
+```
+
+
+## .block
+block.put.
+
+```js
+this.timeout(10000)
+apiClients['a'].block.put(blorb, (err, res) => {
+ if (err) throw err
+ const store = res.Key
+ assert.equal(store, 'QmPv52ekjS75L4JmHpXVeuJ5uX2ecSfSZo88NSyxwA3rAQ')
+ done()
+})
+```
+
+block.get.
+
+```js
+this.timeout(10000)
+apiClients['a'].block.get(blorbKey, (err, res) => {
+ if (err) throw err
+ let buf = ''
+ res
+ .on('data', function (data) { buf += data })
+ .on('end', function () {
+ assert.equal(buf, 'blorb')
+ done()
+ })
+})
+```
+
+
+## .object
+object.put.
+
+```js
+apiClients['a'].object.put(testObject, 'json', (err, res) => {
+ if (err) throw err
+ const obj = res
+ assert.equal(obj.Hash, testObjectHash)
+ assert.equal(obj.Links.length, 0)
+ done()
+})
+```
+
+object.get.
+
+```js
+apiClients['a'].object.get(testObjectHash, (err, res) => {
+ if (err) {
+ throw err
+ }
+ const obj = res
+ assert.equal(obj.Data, 'testdata')
+ assert.equal(obj.Links.length, 0)
+ done()
+})
+```
+
+object.data.
+
+```js
+this.timeout(10000)
+apiClients['a'].object.data(testObjectHash, (err, res) => {
+ if (err) throw err
+ let buf = ''
+ res
+ .on('error', err => { throw err })
+ .on('data', data => buf += data)
+ .on('end', () => {
+ assert.equal(buf, 'testdata')
+ done()
+ })
+})
+```
+
+object.stat.
+
+```js
+this.timeout(10000)
+apiClients['a'].object.stat(testObjectHash, (err, res) => {
+ if (err) {
+ throw err
+ }
+ assert.deepEqual(res, {
+ Hash: 'QmPTkMuuL6PD8L2SwTwbcs1NPg14U8mRzerB1ZrrBrkSDD',
+ NumLinks: 0,
+ BlockSize: 10,
+ LinksSize: 2,
+ DataSize: 8,
+ CumulativeSize: 10
+ })
+ done()
+})
+```
+
+object.links.
+
+```js
+this.timeout(10000)
+apiClients['a'].object.links(testObjectHash, (err, res) => {
+ if (err) {
+ throw err
+ }
+ assert.deepEqual(res, {
+ Hash: 'QmPTkMuuL6PD8L2SwTwbcs1NPg14U8mRzerB1ZrrBrkSDD',
+ Links: []
+ })
+ done()
+})
+```
+
+object.patch.
+
+```js
+this.timeout(10000)
+apiClients['a'].object.put(testPatchObject, 'json', (err, res) => {
+ if (err) {
+ throw err
+ }
+ apiClients['a'].object.patch(testObjectHash, ['add-link', 'next', testPatchObjectHash], (err, res) => {
+ if (err) {
+ throw err
+ }
+ assert.deepEqual(res, {
+ Hash: 'QmZFdJ3CQsY4kkyQtjoUo8oAzsEs5BNguxBhp8sjQMpgkd',
+ Links: null
+ })
+ apiClients['a'].object.get(res.Hash, (err, res2) => {
+ if (err) {
+ throw err
+ }
+ assert.deepEqual(res2, {
+ Data: 'testdata',
+ Links: [{
+ Name: 'next',
+ Hash: 'QmWJDtdQWQSajQPx1UVAGWKaSGrHVWdjnrNhbooHP7LuF2',
+ Size: 15
+ }]
+ })
+ done()
+ })
+ })
+})
+```
+
+
+## .swarm
+.swarm.peers.
+
+```js
+this.timeout(5000)
+apiClients['a'].swarm.peers((err, res) => {
+ if (err) {
+ throw err
+ }
+ assert(res.Strings.length >= 2)
+ done()
+})
+```
+
+.swarm.connect.
+
+```js
+// Done in the 'before' segment
+done()
+```
+
+
+## .ping
+ping another peer.
+
+```js
+apiClients['b'].id((err, id) => {
+ if (err) {
+ throw err
+ }
+ apiClients['a'].ping(id.ID, (err, res) => {
+ if (err) {
+ throw err
+ }
+ assert(res)
+ assert(res.Success)
+ done()
+ })
+})
+```
+
+
+## .id
+id.
+
+```js
+this.timeout(10000)
+apiClients['a'].id((err, res) => {
+ if (err) throw err
+ const id = res
+ assert(id.ID)
+ assert(id.PublicKey)
+ done()
+})
+```
+
+
+## .pin
+.pin.add.
+
+```js
+this.timeout(5000)
+apiClients['b'].pin.add('Qma4hjFTnCasJ8PVp3mZbZK5g2vGDT4LByLJ7m8ciyRFZP', {recursive: false}, (err, res) => {
+ if (err) {
+ throw err
+ }
+ assert.equal(res.Pinned[0], 'Qma4hjFTnCasJ8PVp3mZbZK5g2vGDT4LByLJ7m8ciyRFZP')
+ done()
+})
+```
+
+.pin.list.
+
+```js
+this.timeout(5000)
+apiClients['b'].pin.list((err, res) => {
+ if (err) {
+ throw err
+ }
+ assert(res)
+ done()
+})
+```
+
+.pin.remove.
+
+```js
+this.timeout(5000)
+apiClients['b'].pin.remove('Qma4hjFTnCasJ8PVp3mZbZK5g2vGDT4LByLJ7m8ciyRFZP', {recursive: false}, (err, res) => {
+ if (err) {
+ throw err
+ }
+ assert(res)
+ apiClients['b'].pin.list('direct', (err, res) => {
+ if (err) {
+ throw err
+ }
+ assert(res)
+ assert.equal(Object.keys(res.Keys).length, 0)
+ done()
+ })
+})
+```
+
+
+## .log
+.log.tail.
+
+```js
+this.timeout(20000)
+apiClients['a'].log.tail((err, res) => {
+ if (err) {
+ throw err
+ }
+ res.once('data', obj => {
+ assert(obj)
+ assert.equal(typeof obj, 'object')
+ done()
+ })
+})
+```
+
+
+## .name
+.name.publish.
+
+```js
+apiClients['a'].name.publish('Qma4hjFTnCasJ8PVp3mZbZK5g2vGDT4LByLJ7m8ciyRFZP', (err, res) => {
+ if (err) {
+ throw err
+ }
+ assert(res)
+ name = res
+ done()
+})
+```
+
+.name.resolve.
+
+```js
+apiClients['a'].name.resolve(name.Name, (err, res) => {
+ if (err) {
+ throw err
+ }
+ assert(res)
+ assert.deepEqual(res, { Path: '/ipfs/' + name.Value })
+ done()
+})
+```
+
+
+## .refs
+refs.
+
+```js
+if (!isNode) {
+ return done()
+}
+this.timeout(10000)
+apiClients['a'].refs(folder, {'format': ' '}, (err, objs) => {
+ if (err) {
+ throw err
+ }
+ const result = [{
+ Ref: 'QmSzLpCVbWnEm3XoTWnv6DT6Ju5BsVoLhzvxKXZeQ2cmdg QmcUYKmQxmTcFom4R4UZP7FWeQzgJkwcFn51XrvsMy7PE9 add.js',
+ Err: ''
+ }, {
+ Ref: 'QmSzLpCVbWnEm3XoTWnv6DT6Ju5BsVoLhzvxKXZeQ2cmdg QmNeHxDfQfjVFyYj2iruvysLH9zpp78v3cu1s3BZq1j5hY cat.js',
+ Err: ''
+ }, {
+ Ref: 'QmSzLpCVbWnEm3XoTWnv6DT6Ju5BsVoLhzvxKXZeQ2cmdg QmTYFLz5vsdMpq4XXw1a1pSxujJc9Z5V3Aw1Qg64d849Zy files',
+ Err: ''
+ }, {
+ Ref: 'QmSzLpCVbWnEm3XoTWnv6DT6Ju5BsVoLhzvxKXZeQ2cmdg QmU7wetVaAqc3Meurif9hcYBHGvQmL5QdpPJYBoZizyTNL ipfs-add.js',
+ Err: ''
+ }, {
+ Ref: 'QmSzLpCVbWnEm3XoTWnv6DT6Ju5BsVoLhzvxKXZeQ2cmdg QmctZfSuegbi2TMFY2y3VQjxsH5JbRBu7XmiLfHNvshhio ls.js',
+ Err: ''
+ }, {
+ Ref: 'QmSzLpCVbWnEm3XoTWnv6DT6Ju5BsVoLhzvxKXZeQ2cmdg QmTDH2RXGn8XyDAo9YyfbZAUXwL1FCr44YJCN9HBZmL9Gj test-folder',
+ Err: ''
+ }, {
+ Ref: 'QmSzLpCVbWnEm3XoTWnv6DT6Ju5BsVoLhzvxKXZeQ2cmdg QmbkMNB6rwfYAxRvnG9CWJ6cKKHEdq2ZKTozyF5FQ7H8Rs version.js',
+ Err: ''
+ }]
+ assert.deepEqual(objs, result)
+ done()
+})
+```
+
+
+## .dht
+returns an error when getting a non-existent key from the DHT.
+
+```js
+this.timeout(20000)
+apiClients['a'].dht.get('non-existent', {timeout: '100ms'}, (err, value) => {
+ assert(err)
+ done()
+})
+```
+
+puts and gets a key value pair in the DHT.
+
+```js
+this.timeout(20000)
+apiClients['a'].dht.put('scope', 'interplanetary', (err, res) => {
+ if (err) {
+ throw err
+ }
+ assert.equal(typeof res, 'object')
+ return done()
+ // non ipns or pk hashes fail to fetch, known bug
+ // bug: https://github.com/ipfs/go-ipfs/issues/1923#issuecomment-152932234
+ // apiClients['a'].dht.get('scope', (err, value) => {
+ // console.log('->>', err, value)
+ // if (err) {
+ // throw err
+ // }
+ // assert.equal(value, 'interplanetary')
+ // done()
+ // })
+})
+```
+
+.dht.findprovs.
+
+```js
+apiClients['a'].dht.findprovs('Qma4hjFTnCasJ8PVp3mZbZK5g2vGDT4LByLJ7m8ciyRFZP', (err, res) => {
+ if (err) {
+ throw err
+ }
+ assert.equal(typeof res, 'object')
+ assert(res)
+ done()
+})
+```
+
diff --git a/package.json b/package.json
index 51bba7808..9808e4703 100644
--- a/package.json
+++ b/package.json
@@ -34,8 +34,8 @@
"gulp": "^3.9.0",
"gulp-eslint": "^1.0.0",
"gulp-load-plugins": "^1.0.0",
- "gulp-mocha": "^2.1.3",
"gulp-size": "^2.0.0",
+ "gulp-spawn-mocha": "^2.2.1",
"gulp-util": "^3.0.7",
"https-browserify": "0.0.1",
"ipfsd-ctl": "^0.6.1",
diff --git a/tasks/test.js b/tasks/test.js
index 1e4ac8b07..e1024355f 100644
--- a/tasks/test.js
+++ b/tasks/test.js
@@ -24,6 +24,15 @@ gulp.task('test:node', done => {
)
})
+gulp.task('docs', done => {
+ runSequence(
+ 'daemons:start',
+ 'mocha:docs',
+ 'daemons:stop',
+ done
+ )
+})
+
gulp.task('test:browser', done => {
runSequence(
'daemons:start',
@@ -35,7 +44,15 @@ gulp.task('test:browser', done => {
gulp.task('mocha', () => {
return gulp.src('test/tests.js')
- .pipe($.mocha())
+ .pipe($.spawnMocha())
+})
+
+gulp.task('mocha:docs', function () {
+ return gulp.src('test/tests.js')
+ .pipe($.spawnMocha({
+ reporter: 'markdown',
+ output: 'API.md'
+ }))
})
gulp.task('karma', done => {