diff --git a/package.json b/package.json index 1ae81a34e1..35c9eefb6c 100644 --- a/package.json +++ b/package.json @@ -68,7 +68,7 @@ "webpack": "^2.0.7-beta" }, "dependencies": { - "bl": "^1.0.0", + "bl": "^1.1.2", "boom": "^3.1.1", "bs58": "^3.0.0", "debug": "^2.2.0", @@ -76,7 +76,7 @@ "ipfs-api": "^2.13.1", "ipfs-blocks": "^0.1.0", "ipfs-merkle-dag": "^0.2.1", - "ipfs-multipart": "0.0.1", + "ipfs-multipart": "^0.1.0", "ipfs-repo": "^0.5.0", "joi": "^8.0.2", "lodash.get": "^4.0.0", diff --git a/src/cli/commands/object/data.js b/src/cli/commands/object/data.js new file mode 100644 index 0000000000..98a9e32e73 --- /dev/null +++ b/src/cli/commands/object/data.js @@ -0,0 +1,41 @@ +'use strict' + +const Command = require('ronin').Command +const utils = require('../../utils') +const bs58 = require('bs58') +const debug = require('debug') +const log = debug('cli:object') +log.error = debug('cli:object:error') + +module.exports = Command.extend({ + desc: 'Outputs the raw bytes in an IPFS object', + + options: {}, + + run: (key) => { + if (!key) { + throw new Error("Argument 'key' is required") + } + + var ipfs = utils.getIPFS() + + const mh = utils.isDaemonOn() + ? key + : new Buffer(bs58.decode(key)) + + ipfs.object.data(mh, (err, data) => { + if (err) { + log.error(err) + throw err + } + + if (data instanceof Buffer) { + console.log(data.toString()) + return + } + + // js-ipfs-api output (http stream) + data.pipe(process.stdout) + }) + } +}) diff --git a/src/cli/commands/object/get.js b/src/cli/commands/object/get.js new file mode 100644 index 0000000000..d22e6f1502 --- /dev/null +++ b/src/cli/commands/object/get.js @@ -0,0 +1,50 @@ +'use strict' + +const Command = require('ronin').Command +const utils = require('../../utils') +const bs58 = require('bs58') +const debug = require('debug') +const log = debug('cli:object') +log.error = debug('cli:object:error') + +module.exports = Command.extend({ + desc: 'Get and serialize the DAG node named by ', + + options: {}, + + run: (key) => { + if (!key) { + throw new Error("Argument 'key' is required") + } + + var ipfs = utils.getIPFS() + + if (utils.isDaemonOn()) { + return ipfs.object.get(key, (err, obj) => { + if (err) { + log.error(err) + throw err + } + + console.log(JSON.stringify(obj)) + }) + } + + const mh = new Buffer(bs58.decode(key)) + ipfs.object.get(mh, (err, obj) => { + if (err) { + log.error(err) + throw err + } + + console.log(JSON.stringify({ + Links: obj.links.map((link) => ({ + Name: link.name, + Hash: bs58.encode(link.hash).toString(), + Size: link.size + })), + Data: obj.data.toString() + })) + }) + } +}) diff --git a/src/cli/commands/object/links.js b/src/cli/commands/object/links.js new file mode 100644 index 0000000000..d2973a04c4 --- /dev/null +++ b/src/cli/commands/object/links.js @@ -0,0 +1,44 @@ +'use strict' + +const Command = require('ronin').Command +const utils = require('../../utils') +const bs58 = require('bs58') +const debug = require('debug') +const log = debug('cli:object') +log.error = debug('cli:object:error') + +module.exports = Command.extend({ + desc: 'Outputs the links pointed to by the specified object', + + options: {}, + + run: (key) => { + if (!key) { + throw new Error("Argument 'key' is required") + } + + var ipfs = utils.getIPFS() + + const mh = utils.isDaemonOn() + ? key + : new Buffer(bs58.decode(key)) + + ipfs.object.links(mh, (err, links) => { + if (err) { + log.error(err) + throw err + } + + if (links.Links) { // js-ipfs-api output + links.Links.forEach((link) => { + console.log(link.Hash, link.Size, link.Name) + }) + return + } + + links.forEach((link) => { + console.log(bs58.encode(link.hash).toString(), link.size, link.name) + }) + }) + } +}) diff --git a/src/cli/commands/object/new.js b/src/cli/commands/object/new.js new file mode 100644 index 0000000000..e6191550e1 --- /dev/null +++ b/src/cli/commands/object/new.js @@ -0,0 +1,32 @@ +'use strict' + +const Command = require('ronin').Command +const utils = require('../../utils') +const bs58 = require('bs58') +const debug = require('debug') +const log = debug('cli:object') +log.error = debug('cli:object:error') + +module.exports = Command.extend({ + desc: 'Create new ipfs objects', + + options: {}, + + run: (template) => { + var ipfs = utils.getIPFS() + + ipfs.object.new(template, (err, obj) => { + if (err) { + log.error(err) + throw err + } + + if (typeof obj.Hash === 'string') { // js-ipfs-api output + console.log(obj.Hash) + return + } + + console.log(bs58.encode(obj.Hash).toString()) + }) + } +}) diff --git a/src/cli/commands/object/put.js b/src/cli/commands/object/put.js new file mode 100644 index 0000000000..0608016cf3 --- /dev/null +++ b/src/cli/commands/object/put.js @@ -0,0 +1,76 @@ +'use strict' + +const Command = require('ronin').Command +const utils = require('../../utils') +const bs58 = require('bs58') +const bl = require('bl') +const fs = require('fs') +const mDAG = require('ipfs-merkle-dag') +const DAGNode = mDAG.DAGNode +const debug = require('debug') +const log = debug('cli:object') +log.error = debug('cli:object:error') + +function parseJSONBuffer (buf) { + try { + const parsed = JSON.parse(buf.toString()) + return { + data: new Buffer(parsed.Data), + links: parsed.Links ? parsed.Links.map((link) => ({ + name: link.Name, + hash: new Buffer(bs58.decode(link.Hash)), + size: link.Size + })) : [] + } + } catch (err) { + log.error(err) + throw new Error('failed to parse JSON: ' + err) + } +} + +function parseAndAddNode (buf) { + var ipfs = utils.getIPFS() + + if (utils.isDaemonOn()) { + return ipfs.object.put(buf, 'json', (err, obj) => { + if (err) { + log.error(err) + throw err + } + + console.log('added', obj.Hash) + }) + } + + const parsed = parseJSONBuffer(buf) + const dagNode = new DAGNode(parsed.data, parsed.links) + ipfs.object.put(dagNode, (err, obj) => { + if (err) { + log.error(err) + throw err + } + + console.log('added', bs58.encode(dagNode.multihash()).toString()) + }) +} + +module.exports = Command.extend({ + desc: 'Stores input as a DAG object, outputs its key', + + options: {}, + + run: (filePath) => { + if (filePath) { + return parseAndAddNode(fs.readFileSync(filePath)) + } + + process.stdin.pipe(bl((err, input) => { + if (err) { + log.error(err) + throw err + } + + parseAndAddNode(input) + })) + } +}) diff --git a/src/cli/commands/object/stat.js b/src/cli/commands/object/stat.js new file mode 100644 index 0000000000..7056eb0a9c --- /dev/null +++ b/src/cli/commands/object/stat.js @@ -0,0 +1,39 @@ +'use strict' + +const Command = require('ronin').Command +const utils = require('../../utils') +const bs58 = require('bs58') +const debug = require('debug') +const log = debug('cli:object') +log.error = debug('cli:object:error') + +module.exports = Command.extend({ + desc: 'Get stats for the DAG node named by ', + + options: {}, + + run: (key) => { + if (!key) { + throw new Error("Argument 'key' is required") + } + + var ipfs = utils.getIPFS() + + const mh = utils.isDaemonOn() + ? key + : new Buffer(bs58.decode(key)) + + ipfs.object.stat(mh, (err, stats) => { + if (err) { + log.error(err) + throw err + } + + delete stats.Hash // only for js-ipfs-api output + + Object.keys(stats).forEach((key) => { + console.log(`${key}: ${stats[key]}`) + }) + }) + } +}) diff --git a/src/http-api/resources/object.js b/src/http-api/resources/object.js index e69de29bb2..cc15e1cb69 100644 --- a/src/http-api/resources/object.js +++ b/src/http-api/resources/object.js @@ -0,0 +1,245 @@ +'use strict' + +const ipfs = require('./../index.js').ipfs +const bs58 = require('bs58') +const multipart = require('ipfs-multipart') +const mDAG = require('ipfs-merkle-dag') +const debug = require('debug') +const log = debug('http-api:object') +log.error = debug('http-api:object:error') +const DAGNode = mDAG.DAGNode + +exports = module.exports + +// common pre request handler that parses the args and returns `key` which is assigned to `request.pre.args` +exports.parseKey = (request, reply) => { + if (!request.query.arg) { + return reply("Argument 'key' is required").code(400).takeover() + } + + try { + return reply({ + key: new Buffer(bs58.decode(request.query.arg)) + }) + } catch (err) { + log.error(err) + return reply({ + Message: 'invalid ipfs ref path', + Code: 0 + }).code(500).takeover() + } +} + +exports.new = { + // pre request handler that parses the args and returns `template` which is assigned to `request.pre.args` + parseArgs: (request, reply) => { + // TODO improve this validation once ipfs.object.new supports templates + if (request.query.arg === '') { + return reply({ + Message: `template \'${request.query.arg}\' not found`, + Code: 0 + }).code(500).takeover() + } + + return reply({ + template: request.query.arg + }) + }, + + // main route handler which is called after the above `parseArgs`, but only if the args were valid + handler: (request, reply) => { + const template = request.pre.args.template + + ipfs.object.new(template, (err, obj) => { + if (err) { + log.error(err) + return reply({ + Message: 'Failed to create object: ' + err, + Code: 0 + }).code(500) + } + + return reply({ + Hash: bs58.encode(obj.Hash).toString(), + Links: obj.Links || null + }) + }) + } +} + +exports.get = { + // uses common parseKey method that returns a `key` + parseArgs: exports.parseKey, + + // main route handler which is called after the above `parseArgs`, but only if the args were valid + handler: (request, reply) => { + const key = request.pre.args.key + + ipfs.object.get(key, (err, obj) => { + if (err) { + log.error(err) + return reply({ + Message: 'Failed to get object: ' + err, + Code: 0 + }).code(500) + } + + return reply({ + Links: obj.links.map((link) => ({ + Name: link.name, + Hash: bs58.encode(link.hash).toString(), + Size: link.size + })), + Data: obj.data.toString() + }) + }) + } +} + +exports.put = { + // pre request handler that parses the args and returns `node` which is assigned to `request.pre.args` + parseArgs: (request, reply) => { + if (!request.payload) { + return reply("File argument 'data' is required").code(400).takeover() + } + + const parser = multipart.reqParser(request.payload) + let file + + parser.on('file', (fileName, fileStream) => { + fileStream.on('data', (data) => { + file = data + }) + }) + + parser.on('end', () => { + if (!file) { + return reply("File argument 'data' is required").code(400).takeover() + } + + try { + return reply({ + node: JSON.parse(file.toString()) + }) + } catch (err) { + return reply({ + Message: 'Failed to parse the JSON: ' + err, + Code: 0 + }).code(500).takeover() + } + }) + }, + + // main route handler which is called after the above `parseArgs`, but only if the args were valid + handler: (request, reply) => { + const node = request.pre.args.node + + const data = new Buffer(node.Data) + const links = node.Links.map((link) => ({ + name: link.Name, + hash: new Buffer(bs58.decode(link.Hash)), + size: link.Size + })) + + const dagNode = new DAGNode(data, links) + + ipfs.object.put(dagNode, (err, obj) => { + if (err) { + log.error(err) + return reply({ + Message: 'Failed to put object: ' + err, + Code: 0 + }).code(500) + } + + return reply({ + Hash: bs58.encode(dagNode.multihash()).toString(), + Links: dagNode.links.map((link) => ({ + Name: link.name, + Hash: bs58.encode(link.hash).toString(), + Size: link.size + })) + }) + }) + } +} + +exports.stat = { + // uses common parseKey method that returns a `key` + parseArgs: exports.parseKey, + + // main route handler which is called after the above `parseArgs`, but only if the args were valid + handler: (request, reply) => { + const key = request.pre.args.key + + ipfs.object.stat(key, (err, stats) => { + if (err) { + log.error(err) + return reply({ + Message: 'Failed to get object: ' + err, + Code: 0 + }).code(500) + } + + return reply({ + Hash: bs58.encode(key).toString(), + NumLinks: stats.NumLinks, + BlockSize: stats.BlockSize, + LinksSize: stats.LinksSize, + DataSize: stats.DataSize + // CumulativeSize: stats.CumulativeSize + }) + }) + } +} + +exports.data = { + // uses common parseKey method that returns a `key` + parseArgs: exports.parseKey, + + // main route handler which is called after the above `parseArgs`, but only if the args were valid + handler: (request, reply) => { + const key = request.pre.args.key + + ipfs.object.data(key, (err, data) => { + if (err) { + log.error(err) + return reply({ + Message: 'Failed to get object: ' + err, + Code: 0 + }).code(500) + } + + return reply(data.toString()) + }) + } +} + +exports.links = { + // uses common parseKey method that returns a `key` + parseArgs: exports.parseKey, + + // main route handler which is called after the above `parseArgs`, but only if the args were valid + handler: (request, reply) => { + const key = request.pre.args.key + + ipfs.object.links(key, (err, links) => { + if (err) { + log.error(err) + return reply({ + Message: 'Failed to get object: ' + err, + Code: 0 + }).code(500) + } + + return reply({ + Hash: bs58.encode(key).toString(), + Links: links.map((link) => ({ + Name: link.name, + Hash: bs58.encode(link.hash).toString(), + Size: link.size + })) + }) + }) + } +} diff --git a/src/http-api/routes/index.js b/src/http-api/routes/index.js index 2608f4fcbf..0c9bb64fea 100644 --- a/src/http-api/routes/index.js +++ b/src/http-api/routes/index.js @@ -3,7 +3,7 @@ module.exports = (server) => { require('./id')(server) require('./bootstrap')(server) // require('./block')(server) - // require('./object')(server) + require('./object')(server) // require('./repo')(server) require('./config')(server) } diff --git a/src/http-api/routes/object.js b/src/http-api/routes/object.js index fd57859261..502ead9f30 100644 --- a/src/http-api/routes/object.js +++ b/src/http-api/routes/object.js @@ -1,12 +1,79 @@ const resources = require('./../resources') -// TODO +// TODO: add `object patch` endpoints, once spec is finished, check +// https://github.com/ipfs/js-ipfs/issues/58 & https://github.com/ipfs/http-api-spec/pull/32 +// for more info + module.exports = (server) => { const api = server.select('API') api.route({ - method: 'GET', - path: '/api/v0/object', - handler: resources.object + method: '*', + path: '/api/v0/object/new', + config: { + pre: [ + { method: resources.object.new.parseArgs, assign: 'args' } + ], + handler: resources.object.new.handler + } + }) + + api.route({ + method: '*', + path: '/api/v0/object/get', + config: { + pre: [ + { method: resources.object.get.parseArgs, assign: 'args' } + ], + handler: resources.object.get.handler + } + }) + + api.route({ + method: '*', + path: '/api/v0/object/put', + config: { + payload: { + parse: false, + output: 'stream' + }, + pre: [ + { method: resources.object.put.parseArgs, assign: 'args' } + ], + handler: resources.object.put.handler + } + }) + + api.route({ + method: '*', + path: '/api/v0/object/stat', + config: { + pre: [ + { method: resources.object.stat.parseArgs, assign: 'args' } + ], + handler: resources.object.stat.handler + } + }) + + api.route({ + method: '*', + path: '/api/v0/object/data', + config: { + pre: [ + { method: resources.object.data.parseArgs, assign: 'args' } + ], + handler: resources.object.data.handler + } + }) + + api.route({ + method: '*', + path: '/api/v0/object/links', + config: { + pre: [ + { method: resources.object.links.parseArgs, assign: 'args' } + ], + handler: resources.object.links.handler + } }) } diff --git a/tests/badnode.json b/tests/badnode.json new file mode 100644 index 0000000000..b55c646210 --- /dev/null +++ b/tests/badnode.json @@ -0,0 +1,3 @@ +{ + bad config +} diff --git a/tests/node.json b/tests/node.json new file mode 100644 index 0000000000..21f06bb5ab --- /dev/null +++ b/tests/node.json @@ -0,0 +1 @@ +{ "Data": "another", "Links": [ { "Name": "some link", "Hash": "QmXg9Pp2ytZ14xgmQjYEiHjVjMFXzCVVEcRTWJBmLgR39V", "Size": 8 } ] } diff --git a/tests/test-cli/test-commands.js b/tests/test-cli/test-commands.js index b6ab3bee22..467c55cd16 100644 --- a/tests/test-cli/test-commands.js +++ b/tests/test-cli/test-commands.js @@ -9,7 +9,7 @@ describe('commands', () => { .run((err, stdout, exitcode) => { expect(err).to.not.exist expect(exitcode).to.equal(0) - expect(stdout.length).to.equal(26) + expect(stdout.length).to.equal(32) done() }) }) diff --git a/tests/test-cli/test-object.js b/tests/test-cli/test-object.js new file mode 100644 index 0000000000..d701db9d42 --- /dev/null +++ b/tests/test-cli/test-object.js @@ -0,0 +1,171 @@ +/* eslint-env mocha */ + +const expect = require('chai').expect +const nexpect = require('nexpect') +const httpAPI = require('../../src/http-api') + +describe('object', () => { + describe('api offline', () => { + it('new', (done) => { + nexpect.spawn('node', [process.cwd() + '/src/cli/bin.js', 'object', 'new']) + .run((err, stdout, exitcode) => { + expect(err).to.not.exist + expect(exitcode).to.equal(0) + expect(stdout[0]) + .to.equal('QmdfTbBqBPQ7VNxZEYEj14VmRuZBkqFbiwReogJgS1zR1n') + done() + }) + }) + + it('get', (done) => { + nexpect.spawn('node', [process.cwd() + '/src/cli/bin.js', 'object', 'get', 'QmdfTbBqBPQ7VNxZEYEj14VmRuZBkqFbiwReogJgS1zR1n']) + .run((err, stdout, exitcode) => { + expect(err).to.not.exist + expect(exitcode).to.equal(0) + const result = JSON.parse(stdout[0]) + expect(result.Links) + .to.deep.equal([]) + expect(result.Data) + .to.equal('') + done() + }) + }) + + it('put', (done) => { + nexpect.spawn('node', [process.cwd() + '/src/cli/bin.js', 'object', 'put', process.cwd() + '/tests/node.json']) + .run((err, stdout, exitcode) => { + expect(err).to.not.exist + expect(exitcode).to.equal(0) + expect(stdout[0]) + .to.equal('added QmZZmY4KCu9r3e7M2Pcn46Fc5qbn6NpzaAGaYb22kbfTqm') + done() + }) + }) + + it('stat', (done) => { + nexpect.spawn('node', [process.cwd() + '/src/cli/bin.js', 'object', 'stat', 'QmZZmY4KCu9r3e7M2Pcn46Fc5qbn6NpzaAGaYb22kbfTqm']) + .run((err, stdout, exitcode) => { + expect(err).to.not.exist + expect(exitcode).to.equal(0) + expect(stdout[0]) + .to.equal('NumLinks: 1') + expect(stdout[1]) + .to.equal('BlockSize: 60') + expect(stdout[2]) + .to.equal('LinksSize: 8') + expect(stdout[3]) + .to.equal('DataSize: 7') + done() + }) + }) + + it('data', (done) => { + nexpect.spawn('node', [process.cwd() + '/src/cli/bin.js', 'object', 'data', 'QmZZmY4KCu9r3e7M2Pcn46Fc5qbn6NpzaAGaYb22kbfTqm']) + .run((err, stdout, exitcode) => { + expect(err).to.not.exist + expect(exitcode).to.equal(0) + expect(stdout[0]).to.equal('another') + done() + }) + }) + + it('links', (done) => { + nexpect.spawn('node', [process.cwd() + '/src/cli/bin.js', 'object', 'links', 'QmZZmY4KCu9r3e7M2Pcn46Fc5qbn6NpzaAGaYb22kbfTqm']) + .run((err, stdout, exitcode) => { + expect(err).to.not.exist + expect(exitcode).to.equal(0) + expect(stdout[0]).to.equal('QmXg9Pp2ytZ14xgmQjYEiHjVjMFXzCVVEcRTWJBmLgR39V 8 some link') + done() + }) + }) + }) + + describe('api running', () => { + before((done) => { + httpAPI.start((err) => { + expect(err).to.not.exist + done() + }) + }) + + after((done) => { + httpAPI.stop((err) => { + expect(err).to.not.exist + done() + }) + }) + + it('new', (done) => { + nexpect.spawn('node', [process.cwd() + '/src/cli/bin.js', 'object', 'new']) + .run((err, stdout, exitcode) => { + expect(err).to.not.exist + expect(exitcode).to.equal(0) + expect(stdout[0]) + .to.equal('QmdfTbBqBPQ7VNxZEYEj14VmRuZBkqFbiwReogJgS1zR1n') + done() + }) + }) + + it('get', (done) => { + nexpect.spawn('node', [process.cwd() + '/src/cli/bin.js', 'object', 'get', 'QmdfTbBqBPQ7VNxZEYEj14VmRuZBkqFbiwReogJgS1zR1n']) + .run((err, stdout, exitcode) => { + expect(err).to.not.exist + expect(exitcode).to.equal(0) + const result = JSON.parse(stdout[0]) + expect(result.Links) + .to.deep.equal([]) + expect(result.Data) + .to.equal('') + done() + }) + }) + + it('put', (done) => { + nexpect.spawn('node', [process.cwd() + '/src/cli/bin.js', 'object', 'put', process.cwd() + '/tests/node.json']) + .run((err, stdout, exitcode) => { + expect(err).to.not.exist + expect(exitcode).to.equal(0) + expect(stdout[0]) + .to.equal('added QmZZmY4KCu9r3e7M2Pcn46Fc5qbn6NpzaAGaYb22kbfTqm') + done() + }) + }) + + it('stat', (done) => { + nexpect.spawn('node', [process.cwd() + '/src/cli/bin.js', 'object', 'stat', 'QmZZmY4KCu9r3e7M2Pcn46Fc5qbn6NpzaAGaYb22kbfTqm']) + .run((err, stdout, exitcode) => { + expect(err).to.not.exist + expect(exitcode).to.equal(0) + expect(stdout[0]) + .to.equal('NumLinks: 1') + expect(stdout[1]) + .to.equal('BlockSize: 60') + expect(stdout[2]) + .to.equal('LinksSize: 8') + expect(stdout[3]) + .to.equal('DataSize: 7') + done() + }) + }) + + it('data', (done) => { + nexpect.spawn('node', [process.cwd() + '/src/cli/bin.js', 'object', 'data', 'QmZZmY4KCu9r3e7M2Pcn46Fc5qbn6NpzaAGaYb22kbfTqm']) + .run((err, stdout, exitcode) => { + expect(err).to.not.exist + expect(exitcode).to.equal(0) + expect(stdout[0]).to.equal('another') + done() + }) + }) + + it('links', (done) => { + nexpect.spawn('node', [process.cwd() + '/src/cli/bin.js', 'object', 'links', 'QmZZmY4KCu9r3e7M2Pcn46Fc5qbn6NpzaAGaYb22kbfTqm']) + .run((err, stdout, exitcode) => { + expect(err).to.not.exist + expect(exitcode).to.equal(0) + expect(stdout[0]).to.equal('QmXg9Pp2ytZ14xgmQjYEiHjVjMFXzCVVEcRTWJBmLgR39V 8 some link') + done() + }) + }) + }) +}) diff --git a/tests/test-http-api/test-object.js b/tests/test-http-api/test-object.js index 5fc532a5da..64952b1608 100644 --- a/tests/test-http-api/test-object.js +++ b/tests/test-http-api/test-object.js @@ -1,21 +1,429 @@ /* eslint-env mocha */ -// const expect = require('chai').expect -// const APIctl = require('ipfs-api') +const expect = require('chai').expect +const APIctl = require('ipfs-api') +const fs = require('fs') +const FormData = require('form-data') +const streamToPromise = require('stream-to-promise') describe('object', () => { describe('api', () => { - // TODO - }) + var api + + it('api', (done) => { + api = require('../../src/http-api').server.select('API') + done() + }) + + describe('/object/new', () => { + it('returns 500 for request with invalid argument', (done) => { + api.inject({ + method: 'GET', + url: '/api/v0/object/new?arg' + }, (res) => { + expect(res.statusCode).to.equal(500) + expect(res.result.Code).to.equal(0) + expect(res.result.Message).to.be.a('string') + done() + }) + }) + + it('returns value', (done) => { + api.inject({ + method: 'GET', + url: '/api/v0/object/new' + }, (res) => { + expect(res.statusCode).to.equal(200) + expect(res.result.Hash) + .to.equal('QmdfTbBqBPQ7VNxZEYEj14VmRuZBkqFbiwReogJgS1zR1n') + expect(res.result.Links) + .to.equal(null) + done() + }) + }) + }) + + describe('/object/get', () => { + it('returns 400 for request without argument', (done) => { + api.inject({ + method: 'GET', + url: '/api/v0/object/get' + }, (res) => { + expect(res.statusCode).to.equal(400) + expect(res.result).to.be.a('string') + done() + }) + }) + + it('returns 500 for request with invalid argument', (done) => { + api.inject({ + method: 'GET', + url: '/api/v0/object/get?arg=invalid' + }, (res) => { + expect(res.statusCode).to.equal(500) + expect(res.result.Code).to.equal(0) + expect(res.result.Message).to.be.a('string') + done() + }) + }) + + it('returns value', (done) => { + api.inject({ + method: 'GET', + url: '/api/v0/object/get?arg=QmdfTbBqBPQ7VNxZEYEj14VmRuZBkqFbiwReogJgS1zR1n' + }, (res) => { + expect(res.statusCode).to.equal(200) + expect(res.result.Links) + .to.deep.equal([]) + expect(res.result.Data) + .to.equal('') + done() + }) + }) + }) + + describe('/object/put', () => { + it('returns 400 if no node is provided', (done) => { + const form = new FormData() + const headers = form.getHeaders() + + streamToPromise(form).then((payload) => { + api.inject({ + method: 'POST', + url: '/api/v0/object/put', + headers: headers, + payload: payload + }, (res) => { + expect(res.statusCode).to.equal(400) + done() + }) + }) + }) + + it('returns 500 if the node is invalid', (done) => { + const form = new FormData() + const filePath = 'tests/badnode.json' + form.append('file', fs.createReadStream(filePath)) + const headers = form.getHeaders() + + streamToPromise(form).then((payload) => { + api.inject({ + method: 'POST', + url: '/api/v0/object/put', + headers: headers, + payload: payload + }, (res) => { + expect(res.statusCode).to.equal(500) + done() + }) + }) + }) + + it('updates value', (done) => { + const form = new FormData() + const filePath = 'tests/node.json' + form.append('data', fs.createReadStream(filePath)) + const headers = form.getHeaders() + const expectedResult = { + Hash: 'QmZZmY4KCu9r3e7M2Pcn46Fc5qbn6NpzaAGaYb22kbfTqm', + Links: [{ + Name: 'some link', + Hash: 'QmXg9Pp2ytZ14xgmQjYEiHjVjMFXzCVVEcRTWJBmLgR39V', + Size: 8 + }] + } + + streamToPromise(form).then((payload) => { + api.inject({ + method: 'POST', + url: '/api/v0/object/put', + headers: headers, + payload: payload + }, (res) => { + expect(res.statusCode).to.equal(200) + expect(res.result).to.deep.equal(expectedResult) + done() + }) + }) + }) + }) + + describe('/object/stat', () => { + it('returns 400 for request without argument', (done) => { + api.inject({ + method: 'GET', + url: '/api/v0/object/stat' + }, (res) => { + expect(res.statusCode).to.equal(400) + expect(res.result).to.be.a('string') + done() + }) + }) + + it('returns 500 for request with invalid argument', (done) => { + api.inject({ + method: 'GET', + url: '/api/v0/object/stat?arg=invalid' + }, (res) => { + expect(res.statusCode).to.equal(500) + expect(res.result.Code).to.equal(0) + expect(res.result.Message).to.be.a('string') + done() + }) + }) + + it('returns value', (done) => { + api.inject({ + method: 'GET', + url: '/api/v0/object/stat?arg=QmZZmY4KCu9r3e7M2Pcn46Fc5qbn6NpzaAGaYb22kbfTqm' + }, (res) => { + expect(res.statusCode).to.equal(200) + expect(res.result.Hash).to.equal('QmZZmY4KCu9r3e7M2Pcn46Fc5qbn6NpzaAGaYb22kbfTqm') + expect(res.result.NumLinks).to.equal(1) + expect(res.result.BlockSize).to.equal(60) + expect(res.result.LinksSize).to.equal(8) + expect(res.result.DataSize).to.equal(7) + done() + }) + }) + }) + + describe('/object/data', () => { + it('returns 400 for request without argument', (done) => { + api.inject({ + method: 'GET', + url: '/api/v0/object/data' + }, (res) => { + expect(res.statusCode).to.equal(400) + expect(res.result).to.be.a('string') + done() + }) + }) + + it('returns 500 for request with invalid argument', (done) => { + api.inject({ + method: 'GET', + url: '/api/v0/object/data?arg=invalid' + }, (res) => { + expect(res.statusCode).to.equal(500) + expect(res.result.Code).to.equal(0) + expect(res.result.Message).to.be.a('string') + done() + }) + }) + + it('returns value', (done) => { + api.inject({ + method: 'GET', + url: '/api/v0/object/data?arg=QmZZmY4KCu9r3e7M2Pcn46Fc5qbn6NpzaAGaYb22kbfTqm' + }, (res) => { + expect(res.statusCode).to.equal(200) + expect(res.result).to.equal('another') + done() + }) + }) + }) + + describe('/object/links', () => { + it('returns 400 for request without argument', (done) => { + api.inject({ + method: 'GET', + url: '/api/v0/object/links' + }, (res) => { + expect(res.statusCode).to.equal(400) + expect(res.result).to.be.a('string') + done() + }) + }) + + it('returns 500 for request with invalid argument', (done) => { + api.inject({ + method: 'GET', + url: '/api/v0/object/links?arg=invalid' + }, (res) => { + expect(res.statusCode).to.equal(500) + expect(res.result.Code).to.equal(0) + expect(res.result.Message).to.be.a('string') + done() + }) + }) - describe('gateway', () => {}) + it('returns value', (done) => { + const expectedResult = { + Hash: 'QmZZmY4KCu9r3e7M2Pcn46Fc5qbn6NpzaAGaYb22kbfTqm', + Links: [ + { Name: 'some link', Hash: 'QmXg9Pp2ytZ14xgmQjYEiHjVjMFXzCVVEcRTWJBmLgR39V', Size: 8 } + ] + } + + api.inject({ + method: 'GET', + url: '/api/v0/object/links?arg=QmZZmY4KCu9r3e7M2Pcn46Fc5qbn6NpzaAGaYb22kbfTqm' + }, (res) => { + expect(res.statusCode).to.equal(200) + expect(res.result).to.deep.equal(expectedResult) + done() + }) + }) + }) + }) describe('using js-ipfs-api', () => { -// var ctl + var ctl it('start IPFS API ctl', (done) => { -// ctl = APIctl('/ip4/127.0.0.1/tcp/6001') + ctl = APIctl('/ip4/127.0.0.1/tcp/6001') done() }) + + it('ipfs.object.new', (done) => { + ctl.object.new(null, (err, result) => { + expect(err).to.not.exist + expect(result.Hash) + .to.equal('QmdfTbBqBPQ7VNxZEYEj14VmRuZBkqFbiwReogJgS1zR1n') + expect(result.Links) + .to.equal(null) + done() + }) + }) + + describe('ipfs.object.get', () => { + it('returns error for request without argument', (done) => { + ctl.object.get(null, (err, result) => { + expect(err).to.exist + done() + }) + }) + + it('returns error for request with invalid argument', (done) => { + ctl.object.get('invalid', (err, result) => { + expect(err).to.exist + done() + }) + }) + + it('returns value', (done) => { + ctl.object.get('QmdfTbBqBPQ7VNxZEYEj14VmRuZBkqFbiwReogJgS1zR1n', (err, result) => { + expect(err).to.not.exist + expect(result.Links) + .to.deep.equal([]) + expect(result.Data) + .to.equal('') + done() + }) + }) + }) + + describe('ipfs.object.put', () => { + it('returns error if the node is invalid', (done) => { + const filePath = 'tests/badnode.json' + + ctl.object.put(filePath, 'json', (err) => { + expect(err).to.exist + done() + }) + }) + + it('updates value', (done) => { + const filePath = 'tests/node.json' + const expectedResult = { + Hash: 'QmZZmY4KCu9r3e7M2Pcn46Fc5qbn6NpzaAGaYb22kbfTqm', + Links: [{ + Name: 'some link', + Hash: 'QmXg9Pp2ytZ14xgmQjYEiHjVjMFXzCVVEcRTWJBmLgR39V', + Size: 8 + }] + } + + ctl.object.put(filePath, 'json', (err, res) => { + expect(err).not.to.exist + expect(res).to.deep.equal(expectedResult) + done() + }) + }) + }) + + describe('ipfs.object.stat', () => { + it('returns error for request without argument', (done) => { + ctl.object.stat(null, (err, result) => { + expect(err).to.exist + done() + }) + }) + + it('returns error for request with invalid argument', (done) => { + ctl.object.stat('invalid', (err, result) => { + expect(err).to.exist + done() + }) + }) + + it('returns value', (done) => { + ctl.object.stat('QmZZmY4KCu9r3e7M2Pcn46Fc5qbn6NpzaAGaYb22kbfTqm', (err, result) => { + expect(err).to.not.exist + expect(result.Hash).to.equal('QmZZmY4KCu9r3e7M2Pcn46Fc5qbn6NpzaAGaYb22kbfTqm') + expect(result.NumLinks).to.equal(1) + expect(result.BlockSize).to.equal(60) + expect(result.LinksSize).to.equal(8) + expect(result.DataSize).to.equal(7) + done() + }) + }) + }) + + describe('ipfs.object.data', () => { + it('returns error for request without argument', (done) => { + ctl.object.data(null, (err, result) => { + expect(err).to.exist + done() + }) + }) + + it('returns error for request with invalid argument', (done) => { + ctl.object.data('invalid', (err, result) => { + expect(err).to.exist + done() + }) + }) + + it('returns value', (done) => { + ctl.object.data('QmZZmY4KCu9r3e7M2Pcn46Fc5qbn6NpzaAGaYb22kbfTqm', (err, result) => { + expect(err).to.not.exist + expect(result.toString()).to.equal('another') + done() + }) + }) + }) + + describe('ipfs.object.links', () => { + it('returns error for request without argument', (done) => { + ctl.object.links(null, (err, result) => { + expect(err).to.exist + done() + }) + }) + + it('returns error for request with invalid argument', (done) => { + ctl.object.links('invalid', (err, result) => { + expect(err).to.exist + done() + }) + }) + + it('returns value', (done) => { + const expectedResult = { + Hash: 'QmZZmY4KCu9r3e7M2Pcn46Fc5qbn6NpzaAGaYb22kbfTqm', + Links: [ + { Name: 'some link', Hash: 'QmXg9Pp2ytZ14xgmQjYEiHjVjMFXzCVVEcRTWJBmLgR39V', Size: 8 } + ] + } + + ctl.object.links('QmZZmY4KCu9r3e7M2Pcn46Fc5qbn6NpzaAGaYb22kbfTqm', (err, result) => { + expect(err).to.not.exist + expect(result).to.deep.equal(expectedResult) + done() + }) + }) + }) }) })