diff --git a/packages/ipfs/package.json b/packages/ipfs/package.json index dfb36a5f5d..30ca6ddd56 100644 --- a/packages/ipfs/package.json +++ b/packages/ipfs/package.json @@ -44,7 +44,6 @@ "test:node": "cross-env ECHO_SERVER_PORT=37481 aegir test -t node", "test:browser": "cross-env ECHO_SERVER_PORT=37482 aegir test -t browser", "test:browser:http": "cross-env ECHO_SERVER_PORT=37489 aegir test -t browser -f test/http-api/index.js", - "test:browser:interface:http": "cross-env ECHO_SERVER_PORT=37489 aegir test -t browser -f test/http-api/interface.js", "test:webworker": "cross-env ECHO_SERVER_PORT=37483 aegir test -t webworker", "test:electron": "cross-env ECHO_SERVER_PORT=37484 aegir test -t electron-main -t electron-renderer", "test:electron-main": "cross-env ECHO_SERVER_PORT=37485 aegir test -t electron-main", @@ -53,7 +52,8 @@ "test:node:core": "cross-env ECHO_SERVER_PORT=37488 aegir test -t node -f test/core/**/*.js", "test:node:http": "cross-env ECHO_SERVER_PORT=37489 aegir test -t node -f test/http-api/index.js", "test:node:gateway": "cross-env ECHO_SERVER_PORT=37490 aegir test -t node -f test/gateway/index.js", - "test:node:interface": "cross-env ECHO_SERVER_PORT=37491 aegir test -t node -f test/core/interface.spec.js", + "test:interface": "cross-env ECHO_SERVER_PORT=37491 aegir test -f test/core/interface.spec.js", + "test:interface:http": "cross-env ECHO_SERVER_PORT=37489 aegir test -f test/http-api/interface.js", "test:bootstrapers": "cross-env ECHO_SERVER_PORT=37492 IPFS_TEST=bootstrapers aegir test -t browser -f test/bootstrapers.js", "test:interop": "cross-env IPFS_JS_EXEC=$PWD/src/cli/bin.js IPFS_JS_MODULE=$PWD IPFS_REUSEPORT=false ipfs-interop", "test:interop:node": "cross-env IPFS_JS_EXEC=$PWD/src/cli/bin.js IPFS_JS_MODULE=$PWD IPFS_REUSEPORT=false ipfs-interop -- -t node", @@ -145,7 +145,7 @@ "libp2p-record": "^0.7.0", "libp2p-secio": "^0.12.2", "libp2p-tcp": "^0.14.3", - "libp2p-webrtc-star": "^0.17.6", + "libp2p-webrtc-star": "^0.17.9", "libp2p-websockets": "^0.13.3", "mafmt": "^7.0.0", "merge-options": "^2.0.0", @@ -185,10 +185,11 @@ "form-data": "^3.0.0", "go-ipfs-dep": "0.4.23-3", "interface-ipfs-core": "^0.134.0", - "ipfs-interop": "ipfs/interop#fix/name-pubsub", + "ipfs-interop": "^1.0.1", "ipfsd-ctl": "^3.0.0", "iso-random-stream": "^1.1.1", "it-first": "^1.0.1", + "it-to-buffer": "^1.0.0", "nanoid": "^3.0.2", "ncp": "^2.0.0", "p-event": "^4.1.0", diff --git a/packages/ipfs/src/http/api/resources/dag.js b/packages/ipfs/src/http/api/resources/dag.js index baa44668f6..5915dfc81d 100644 --- a/packages/ipfs/src/http/api/resources/dag.js +++ b/packages/ipfs/src/http/api/resources/dag.js @@ -173,7 +173,7 @@ exports.put = { query: Joi.object().keys({ format: Joi.string().default('cbor'), 'input-enc': Joi.string().default('json'), - pin: Joi.boolean(), + pin: Joi.boolean().default(false), hash: Joi.string().valid(...Object.keys(mh.names)).default('sha2-256'), 'cid-base': Joi.string().valid(...multibase.names) }).unknown() @@ -245,16 +245,13 @@ exports.put = { try { cid = await ipfs.dag.put(node, { format: format, - hashAlg: hashAlg + hashAlg: hashAlg, + pin: request.query.pin }) } catch (err) { throw Boom.boomify(err, { message: 'Failed to put node' }) } - if (request.query.pin) { - await ipfs.pin.add(cid) - } - return h.response({ Cid: { '/': cidToString(cid, { @@ -287,15 +284,13 @@ exports.resolve = { let lastRemainderPath = path if (path) { - const result = ipfs.dag.resolve(lastCid, path) - while (true) { - const resolveResult = (await result.next()).value - if (!CID.isCID(resolveResult.value)) { + for await (const { value, remainderPath } of ipfs.dag.resolve(lastCid, path)) { + if (!CID.isCID(value)) { break } - lastRemainderPath = resolveResult.remainderPath - lastCid = resolveResult.value + lastRemainderPath = remainderPath + lastCid = value } } diff --git a/packages/ipfs/src/http/api/resources/dht.js b/packages/ipfs/src/http/api/resources/dht.js index 0997e0d4f0..8b6414cf51 100644 --- a/packages/ipfs/src/http/api/resources/dht.js +++ b/packages/ipfs/src/http/api/resources/dht.js @@ -21,7 +21,7 @@ exports.findPeer = { let res try { - res = await ipfs.dht.findPeer(arg) + res = await ipfs.dht.findPeer(new CID(arg)) } catch (err) { if (err.code === 'ERR_LOOKUP_FAILED') { throw Boom.notFound(err.toString()) @@ -52,9 +52,9 @@ exports.findProvs = { const ipfs = request.server.app.ipfs const { arg } = request.query - request.query.maxNumProviders = request.query['num-providers'] - - const res = await all(ipfs.dht.findProvs(arg, { numProviders: request.query['num-providers'] })) + const res = await all(ipfs.dht.findProvs(new CID(arg), { + numProviders: request.query['num-providers'] + })) return h.response({ Responses: res.map(({ id, addrs }) => ({ diff --git a/packages/ipfs/src/http/api/resources/dns.js b/packages/ipfs/src/http/api/resources/dns.js index aed6dc753f..646acb752d 100644 --- a/packages/ipfs/src/http/api/resources/dns.js +++ b/packages/ipfs/src/http/api/resources/dns.js @@ -1,22 +1,37 @@ 'use strict' -const Boom = require('@hapi/boom') +const Joi = require('@hapi/joi') -module.exports = async (request, h) => { - const domain = request.query.arg +module.exports = { + validate: { + options: { + allowUnknown: true, + stripUnknown: true + }, + query: Joi.object().keys({ + arg: Joi.string().required(), + format: Joi.string(), + recursive: Joi.boolean().default(false) + }) + .rename('r', 'recursive', { + override: true, + ignoreUndefined: true + }) + }, + async handler (request, h) { + const { + arg, + format, + recursive + } = request.query - if (!domain) { - throw Boom.badRequest("Argument 'domain' is required") - } - - const format = request.query.format + const path = await request.server.app.ipfs.dns(arg, { + recursive, + format + }) - // query parameters are passed as strings and need to be parsed to expected type - let recursive = request.query.recursive || request.query.r - recursive = !(recursive && recursive === 'false') - - const path = await request.server.app.ipfs.dns(domain, { recursive, format }) - return h.response({ - Path: path - }) + return h.response({ + Path: path + }) + } } diff --git a/packages/ipfs/src/http/api/resources/files-regular.js b/packages/ipfs/src/http/api/resources/files-regular.js index 1645f40983..9d8a77d0ad 100644 --- a/packages/ipfs/src/http/api/resources/files-regular.js +++ b/packages/ipfs/src/http/api/resources/files-regular.js @@ -249,10 +249,13 @@ exports.add = { exports.ls = { validate: { - query: Joi.object().keys({ - 'cid-base': Joi.string().valid(...multibase.names), - stream: Joi.boolean() - }).unknown() + query: Joi.object() + .keys({ + arg: Joi.string().required(), + 'cid-base': Joi.string().valid(...multibase.names), + stream: Joi.boolean().default(false), + recursive: Joi.boolean().default(false) + }).unknown() }, // uses common parseKey method that returns a `key` @@ -262,8 +265,11 @@ exports.ls = { async handler (request, h) { const { ipfs } = request.server.app const { key } = request.pre.args - const recursive = request.query && request.query.recursive === 'true' - const cidBase = request.query['cid-base'] + const { + recursive, + stream, + 'cid-base': cidBase + } = request.query const mapLink = link => { const output = { @@ -286,15 +292,16 @@ exports.ls = { return output } - if (!request.query.stream) { - let links + if (!stream) { try { - links = await all(ipfs.ls(key, { recursive })) + const links = await all(ipfs.ls(key, { + recursive + })) + + return h.response({ Objects: [{ Hash: key, Links: links.map(mapLink) }] }) } catch (err) { throw Boom.boomify(err, { message: 'Failed to list dir' }) } - - return h.response({ Objects: [{ Hash: key, Links: links.map(mapLink) }] }) } return streamResponse(request, h, () => pipe( @@ -334,17 +341,22 @@ exports.refs = { handler (request, h) { const { ipfs } = request.server.app const { key } = request.pre.args - - const options = { - recursive: request.query.recursive, - format: request.query.format, - edges: request.query.edges, - unique: request.query.unique, - maxDepth: request.query['max-depth'] - } + const { + recursive, + format, + edges, + unique, + 'max-depth': maxDepth + } = request.query return streamResponse(request, h, () => pipe( - ipfs.refs(key, options), + ipfs.refs(key, { + recursive, + format, + edges, + unique, + maxDepth + }), map(({ ref, err }) => ({ Ref: ref, Err: err })), ndjson.stringify )) diff --git a/packages/ipfs/src/http/api/resources/files/flush.js b/packages/ipfs/src/http/api/resources/files/flush.js index d178359c9a..a40457a032 100644 --- a/packages/ipfs/src/http/api/resources/files/flush.js +++ b/packages/ipfs/src/http/api/resources/files/flush.js @@ -12,7 +12,7 @@ const mfsFlush = { cidBase } = request.query - let cid = await ipfs.files.flush(arg || '/', {}) + let cid = await ipfs.files.flush(arg || '/') if (cidBase && cidBase !== 'base58btc' && cid.version === 0) { cid = cid.toV1() diff --git a/packages/ipfs/src/http/api/resources/key.js b/packages/ipfs/src/http/api/resources/key.js index 9eafd808d9..f3cb24b985 100644 --- a/packages/ipfs/src/http/api/resources/key.js +++ b/packages/ipfs/src/http/api/resources/key.js @@ -35,7 +35,10 @@ exports.rename = async (request, h) => { exports.gen = async (request, h) => { const { ipfs } = request.server.app const { arg, type, size } = request.query - const key = await ipfs.key.gen(arg, { type, size: parseInt(size) }) + const key = await ipfs.key.gen(arg, { + type, + size: parseInt(size) + }) return h.response(toKeyInfo(key)) } diff --git a/packages/ipfs/src/http/api/resources/name.js b/packages/ipfs/src/http/api/resources/name.js index a36175d2cc..398a0b3d8c 100644 --- a/packages/ipfs/src/http/api/resources/name.js +++ b/packages/ipfs/src/http/api/resources/name.js @@ -18,10 +18,18 @@ exports.resolve = { }, async handler (request, h) { const { ipfs } = request.server.app - const { arg, stream } = request.query + const { + arg, + nocache, + recursive, + stream + } = request.query if (!stream) { - const value = await last(ipfs.name.resolve(arg, request.query)) + const value = await last(ipfs.name.resolve(arg, { + nocache, + recursive + })) return h.response({ Path: value }) } @@ -39,14 +47,29 @@ exports.publish = { arg: Joi.string().required(), resolve: Joi.boolean().default(true), lifetime: Joi.string().default('24h'), - key: Joi.string().default('self') + ttl: Joi.string(), + key: Joi.string().default('self'), + 'allow-offline': Joi.boolean() }).unknown() }, async handler (request, h) { const { ipfs } = request.server.app - const { arg } = request.query + const { + arg, + resolve, + lifetime, + ttl, + key, + 'allow-offline': allowOffline + } = request.query - const res = await ipfs.name.publish(arg, request.query) + const res = await ipfs.name.publish(arg, { + resolve, + lifetime, + ttl, + key, + allowOffline + }) return h.response({ Name: res.name, diff --git a/packages/ipfs/src/http/api/resources/object.js b/packages/ipfs/src/http/api/resources/object.js index 1c79942cf4..f303a9d140 100644 --- a/packages/ipfs/src/http/api/resources/object.js +++ b/packages/ipfs/src/http/api/resources/object.js @@ -83,7 +83,9 @@ exports.get = { let node, cid try { - node = await ipfs.object.get(key, { enc }) + node = await ipfs.object.get(key, { + enc + }) cid = await dagPB.util.cid(dagPB.util.serialize(node)) } catch (err) { throw Boom.boomify(err, { message: 'Failed to get object' }) @@ -490,7 +492,9 @@ exports.patchRmLink = { let cid, node try { - cid = await ipfs.object.patch.rmLink(root, { name: link }) + cid = await ipfs.object.patch.rmLink(root, { + name: link + }) node = await ipfs.object.get(cid) } catch (err) { throw Boom.boomify(err, { message: 'Failed to remove link from object' }) diff --git a/packages/ipfs/src/http/api/resources/pin.js b/packages/ipfs/src/http/api/resources/pin.js index c853f9bdda..b304e20e8b 100644 --- a/packages/ipfs/src/http/api/resources/pin.js +++ b/packages/ipfs/src/http/api/resources/pin.js @@ -60,7 +60,9 @@ exports.ls = { if (!request.query.stream) { const res = await pipe( - ipfs.pin.ls(path, { type }), + ipfs.pin.ls(path, { + type + }), reduce((res, { type, cid }) => { res.Keys[cidToString(cid, { base: request.query['cid-base'] })] = { Type: type } return res @@ -93,7 +95,9 @@ exports.add = { let result try { - result = await ipfs.pin.add(path, { recursive }) + result = await ipfs.pin.add(path, { + recursive + }) } catch (err) { if (err.message.includes('already pinned recursively')) { throw Boom.boomify(err, { statusCode: 400 }) @@ -122,7 +126,9 @@ exports.rm = { let result try { - result = await ipfs.pin.rm(path, { recursive }) + result = await ipfs.pin.rm(path, { + recursive + }) } catch (err) { throw Boom.boomify(err, { message: 'Failed to remove pin' }) } diff --git a/packages/ipfs/src/http/api/resources/ping.js b/packages/ipfs/src/http/api/resources/ping.js index 44bcacdf6f..6842fc1fa7 100644 --- a/packages/ipfs/src/http/api/resources/ping.js +++ b/packages/ipfs/src/http/api/resources/ping.js @@ -27,7 +27,9 @@ module.exports = { const count = request.query.n || request.query.count || 10 return streamResponse(request, h, () => pipe( - ipfs.ping(peerId, { count }), + ipfs.ping(peerId, { + count + }), map(pong => ({ Success: pong.success, Time: pong.time, Text: pong.text })), ndjson.stringify )) diff --git a/packages/ipfs/src/http/api/resources/pubsub.js b/packages/ipfs/src/http/api/resources/pubsub.js index ad1a66d5f1..bee1c6796d 100644 --- a/packages/ipfs/src/http/api/resources/pubsub.js +++ b/packages/ipfs/src/http/api/resources/pubsub.js @@ -32,13 +32,16 @@ exports.subscribe = { res.write('{}\n') const unsubscribe = () => { - ipfs.pubsub.unsubscribe(topic, handler, () => res.end()) + ipfs.pubsub.unsubscribe(topic, handler) + res.end() } request.events.once('disconnect', unsubscribe) request.events.once('finish', unsubscribe) - await ipfs.pubsub.subscribe(topic, handler, { discover: discover }) + await ipfs.pubsub.subscribe(topic, handler, { + discover: discover + }) return h.response(res) .header('X-Chunked-Output', '1') diff --git a/packages/ipfs/src/http/api/resources/resolve.js b/packages/ipfs/src/http/api/resources/resolve.js index 58bcbc4385..522175f236 100644 --- a/packages/ipfs/src/http/api/resources/resolve.js +++ b/packages/ipfs/src/http/api/resources/resolve.js @@ -22,7 +22,10 @@ module.exports = { const cidBase = request.query['cid-base'] log(name, { recursive, cidBase }) - const res = await ipfs.resolve(name, { recursive, cidBase }) + const res = await ipfs.resolve(name, { + recursive, + cidBase + }) return h.response({ Path: res }) } diff --git a/packages/ipfs/src/http/api/resources/swarm.js b/packages/ipfs/src/http/api/resources/swarm.js index 101cee8ba5..8c5951f4ab 100644 --- a/packages/ipfs/src/http/api/resources/swarm.js +++ b/packages/ipfs/src/http/api/resources/swarm.js @@ -26,7 +26,9 @@ exports.peers = { const verbose = rawVerbose === 'true' const { ipfs } = request.server.app - const peers = await ipfs.swarm.peers({ verbose }) + const peers = await ipfs.swarm.peers({ + verbose + }) return h.response({ Peers: peers.map((p) => { diff --git a/packages/ipfs/src/http/api/routes/dns.js b/packages/ipfs/src/http/api/routes/dns.js index 1ecd0bc66a..a347c60dd5 100644 --- a/packages/ipfs/src/http/api/routes/dns.js +++ b/packages/ipfs/src/http/api/routes/dns.js @@ -5,5 +5,8 @@ const resources = require('../resources') module.exports = { method: 'POST', path: '/api/v0/dns', - handler: resources.dns + options: { + validate: resources.dns.validate + }, + handler: resources.dns.handler } diff --git a/packages/ipfs/src/http/api/routes/files-regular.js b/packages/ipfs/src/http/api/routes/files-regular.js index 310af8d5b2..5ca46a4328 100644 --- a/packages/ipfs/src/http/api/routes/files-regular.js +++ b/packages/ipfs/src/http/api/routes/files-regular.js @@ -42,7 +42,8 @@ module.exports = [ options: { pre: [ { method: resources.filesRegular.ls.parseArgs, assign: 'args' } - ] + ], + validate: resources.filesRegular.ls.validate }, handler: resources.filesRegular.ls.handler }, diff --git a/packages/ipfs/test/fixtures/test-data/otherconfig b/packages/ipfs/test/fixtures/test-data/otherconfig deleted file mode 100644 index 4dd17e1b04..0000000000 --- a/packages/ipfs/test/fixtures/test-data/otherconfig +++ /dev/null @@ -1,76 +0,0 @@ -{ - "Identity": { - "PeerID": "QmQ2zigjQikYnyYUSXZydNXrDRhBut2mubwJBaLXobMt3A", - "PrivKey": "CAASpgkwggSiAgEAAoIBAQC2SKo/HMFZeBml1AF3XijzrxrfQXdJzjePBZAbdxqKR1Mc6juRHXij6HXYPjlAk01BhF1S3Ll4Lwi0cAHhggf457sMg55UWyeGKeUv0ucgvCpBwlR5cQ020i0MgzjPWOLWq1rtvSbNcAi2ZEVn6+Q2EcHo3wUvWRtLeKz+DZSZfw2PEDC+DGPJPl7f8g7zl56YymmmzH9liZLNrzg/qidokUv5u1pdGrcpLuPNeTODk0cqKB+OUbuKj9GShYECCEjaybJDl9276oalL9ghBtSeEv20kugatTvYy590wFlJkkvyl+nPxIH0EEYMKK9XRWlu9XYnoSfboiwcv8M3SlsjAgMBAAECggEAZtju/bcKvKFPz0mkHiaJcpycy9STKphorpCT83srBVQi59CdFU6Mj+aL/xt0kCPMVigJw8P3/YCEJ9J+rS8BsoWE+xWUEsJvtXoT7vzPHaAtM3ci1HZd302Mz1+GgS8Epdx+7F5p80XAFLDUnELzOzKftvWGZmWfSeDnslwVONkL/1VAzwKy7Ce6hk4SxRE7l2NE2OklSHOzCGU1f78ZzVYKSnS5Ag9YrGjOAmTOXDbKNKN/qIorAQ1bovzGoCwx3iGIatQKFOxyVCyO1PsJYT7JO+kZbhBWRRE+L7l+ppPER9bdLFxs1t5CrKc078h+wuUr05S1P1JjXk68pk3+kQKBgQDeK8AR11373Mzib6uzpjGzgNRMzdYNuExWjxyxAzz53NAR7zrPHvXvfIqjDScLJ4NcRO2TddhXAfZoOPVH5k4PJHKLBPKuXZpWlookCAyENY7+Pd55S8r+a+MusrMagYNljb5WbVTgN8cgdpim9lbbIFlpN6SZaVjLQL3J8TWH6wKBgQDSChzItkqWX11CNstJ9zJyUE20I7LrpyBJNgG1gtvz3ZMUQCn3PxxHtQzN9n1P0mSSYs+jBKPuoSyYLt1wwe10/lpgL4rkKWU3/m1Myt0tveJ9WcqHh6tzcAbb/fXpUFT/o4SWDimWkPkuCb+8j//2yiXk0a/T2f36zKMuZvujqQKBgC6B7BAQDG2H2B/ijofp12ejJU36nL98gAZyqOfpLJ+FeMz4TlBDQ+phIMhnHXA5UkdDapQ+zA3SrFk+6yGk9Vw4Hf46B+82SvOrSbmnMa+PYqKYIvUzR4gg34rL/7AhwnbEyD5hXq4dHwMNsIDq+l2elPjwm/U9V0gdAl2+r50HAoGALtsKqMvhv8HucAMBPrLikhXP/8um8mMKFMrzfqZ+otxfHzlhI0L08Bo3jQrb0Z7ByNY6M8epOmbCKADsbWcVre/AAY0ZkuSZK/CaOXNX/AhMKmKJh8qAOPRY02LIJRBCpfS4czEdnfUhYV/TYiFNnKRj57PPYZdTzUsxa/yVTmECgYBr7slQEjb5Onn5mZnGDh+72BxLNdgwBkhO0OCdpdISqk0F0Pxby22DFOKXZEpiyI9XYP1C8wPiJsShGm2yEwBPWXnrrZNWczaVuCbXHrZkWQogBDG3HGXNdU4MAWCyiYlyinIBpPpoAJZSzpGLmWbMWh28+RJS6AQX6KHrK1o2uw==" - }, - "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": false, - "Interval": 10 - }, - "webRTCStar": { - "Enabled": false - } - }, - "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 - } -} diff --git a/packages/ipfs/test/gateway/index.js b/packages/ipfs/test/gateway/index.js index cd67caf186..5fff519bc1 100644 --- a/packages/ipfs/test/gateway/index.js +++ b/packages/ipfs/test/gateway/index.js @@ -98,7 +98,7 @@ describe('HTTP Gateway', function () { url: '/ipfs' }) - expect(res.statusCode).to.equal(400) + expect(res).to.have.property('statusCode', 400) expect(res.headers['cache-control']).to.equal('no-cache') expect(res.headers.etag).to.equal(undefined) expect(res.headers['x-ipfs-path']).to.equal(undefined) @@ -111,7 +111,7 @@ describe('HTTP Gateway', function () { url: '/ipfs/invalid' }) - expect(res.statusCode).to.equal(400) + expect(res).to.have.property('statusCode', 400) expect(res.headers['cache-control']).to.equal('no-cache') expect(res.headers.etag).to.equal(undefined) expect(res.headers['x-ipfs-path']).to.equal(undefined) @@ -127,7 +127,7 @@ describe('HTTP Gateway', function () { // Expect 400 Bad Request // https://github.com/ipfs/go-ipfs/issues/4025#issuecomment-342250616 - expect(res.statusCode).to.equal(400) + expect(res).to.have.property('statusCode', 400) }) it('valid CIDv0', async () => { @@ -136,7 +136,7 @@ describe('HTTP Gateway', function () { url: '/ipfs/QmT78zSuBmuS4z925WZfrqQ1qHaJ56DQaTfyMUF7F8ff5o' }) - expect(res.statusCode).to.equal(200) + expect(res).to.have.property('statusCode', 200) expect(res.rawPayload).to.eql(Buffer.from('hello world' + '\n')) expect(res.payload).to.equal('hello world' + '\n') expect(res.headers['cache-control']).to.equal('public, max-age=29030400, immutable') @@ -158,7 +158,7 @@ describe('HTTP Gateway', function () { } }) - expect(res.statusCode).to.equal(200) + expect(res).to.have.property('statusCode', 200) expect(res.headers['access-control-allow-origin']).to.equal('http://example.com') expect(res.headers['access-control-allow-methods']).to.equal('GET') }) @@ -169,7 +169,7 @@ describe('HTTP Gateway', function () { method: 'GET', url: '/ipfs/TO-DO' }, (res) => { - expect(res.statusCode).to.equal(200) + expect(res).to.have.property('statusCode', 200) expect(res.rawPayload).to.eql(Buffer.from('hello world' + '\n')) expect(res.payload).to.equal('hello world' + '\n') expect(res.headers.etag).to.equal(TO-DO) @@ -255,7 +255,7 @@ describe('HTTP Gateway', function () { url: '/ipfs/' + bigFileHash }) - expect(res.statusCode).to.equal(200) + expect(res).to.have.property('statusCode', 200) expect(res.rawPayload).to.eql(bigFile) expect(res.headers['content-length']).to.equal(res.rawPayload.length).to.equal(15000000) expect(res.headers['x-ipfs-path']).to.equal(`/ipfs/${bigFileHash}`) @@ -432,7 +432,7 @@ describe('HTTP Gateway', function () { url: '/ipfs/' + kitty }) - expect(res.statusCode).to.equal(200) + expect(res).to.have.property('statusCode', 200) expect(res.headers['content-type']).to.equal('image/jpeg') expect(res.headers['content-length']).to.equal(res.rawPayload.length).to.equal(443230) expect(res.headers['x-ipfs-path']).to.equal('/ipfs/' + kitty) @@ -455,7 +455,7 @@ describe('HTTP Gateway', function () { url: '/ipfs/' + hexagons }) - expect(res.statusCode).to.equal(200) + expect(res).to.have.property('statusCode', 200) expect(res.headers['content-type']).to.equal('image/svg+xml') }) @@ -467,7 +467,7 @@ describe('HTTP Gateway', function () { url: '/ipfs/' + hexagons }) - expect(res.statusCode).to.equal(200) + expect(res).to.have.property('statusCode', 200) expect(res.headers['content-type']).to.equal('image/svg+xml') }) @@ -479,7 +479,7 @@ describe('HTTP Gateway', function () { url: '/ipfs/' + dir }) - expect(res.statusCode).to.equal(200) + expect(res).to.have.property('statusCode', 200) expect(res.headers['content-type']).to.equal('text/html; charset=utf-8') expect(res.headers['x-ipfs-path']).to.equal('/ipfs/' + dir) expect(res.headers['cache-control']).to.equal('no-cache') @@ -502,7 +502,7 @@ describe('HTTP Gateway', function () { url: '/ipfs/' + dir }) - expect(res.statusCode).to.equal(200) + expect(res).to.have.property('statusCode', 200) expect(res.headers['content-type']).to.equal('text/html; charset=utf-8') expect(res.headers['x-ipfs-path']).to.equal('/ipfs/' + dir) expect(res.headers['cache-control']).to.equal('public, max-age=29030400, immutable') @@ -521,7 +521,7 @@ describe('HTTP Gateway', function () { url: '/ipfs/' + dir }) - expect(res.statusCode).to.equal(200) + expect(res).to.have.property('statusCode', 200) expect(res.headers['content-type']).to.equal('text/html; charset=utf-8') expect(res.headers['x-ipfs-path']).to.equal('/ipfs/' + dir) expect(res.headers['cache-control']).to.equal('public, max-age=29030400, immutable') @@ -568,7 +568,7 @@ describe('HTTP Gateway', function () { }) // confirm payload is index.html - expect(res.statusCode).to.equal(200) + expect(res).to.have.property('statusCode', 200) expect(res.headers['content-type']).to.equal('text/html; charset=utf-8') expect(res.headers['x-ipfs-path']).to.equal('/ipfs/' + dir) expect(res.headers['cache-control']).to.equal('public, max-age=29030400, immutable') @@ -588,7 +588,7 @@ describe('HTTP Gateway', function () { url: escapedPath }) - expect(res.statusCode).to.equal(200) + expect(res).to.have.property('statusCode', 200) expect(res.headers['content-type']).to.equal('image/jpeg') expect(res.headers['x-ipfs-path']).to.equal(escapedPath) expect(res.headers['cache-control']).to.equal('public, max-age=29030400, immutable') @@ -609,7 +609,7 @@ describe('HTTP Gateway', function () { const kittyDirectCid = 'Qmd286K6pohQcTKYqnS1YhWrCiS4gz7Xi34sdwMe9USZ7u' - expect(res.statusCode).to.equal(200) + expect(res).to.have.property('statusCode', 200) expect(res.headers['content-type']).to.equal('image/jpeg') expect(res.headers['content-length']).to.equal(res.rawPayload.length).to.equal(443230) expect(res.headers['x-ipfs-path']).to.equal(ipnsPath) @@ -633,7 +633,7 @@ describe('HTTP Gateway', function () { url: ipnsPath }) - expect(res.statusCode).to.equal(200) + expect(res).to.have.property('statusCode', 200) expect(res.headers['content-type']).to.equal('text/html; charset=utf-8') expect(res.headers['x-ipfs-path']).to.equal(ipnsPath) expect(res.headers['cache-control']).to.equal('no-cache') diff --git a/packages/ipfs/test/http-api/inject/bitswap.js b/packages/ipfs/test/http-api/inject/bitswap.js index 46409e312d..71b0758736 100644 --- a/packages/ipfs/test/http-api/inject/bitswap.js +++ b/packages/ipfs/test/http-api/inject/bitswap.js @@ -3,129 +3,141 @@ const { expect } = require('interface-ipfs-core/src/utils/mocha') const CID = require('cids') -const waitFor = require('../../utils/wait-for') +const sinon = require('sinon') const testHttpMethod = require('../../utils/test-http-method') +const http = require('../../utils/http') -module.exports = (http) => { - describe('/bitswap', () => { - const wantedCid0 = 'QmUBdnXXPyoDFXj3Hj39dNJ5VkN3QFRskXxcGaYFBB8CNR' - const wantedCid1 = 'zb2rhafnd6kEUujnoMkozHnWXY7XpWttyVDWKXfChqA42VTDU' - let api +describe('/bitswap', () => { + const cid = new CID('QmUBdnXXPyoDFXj3Hj39dNJ5VkN3QFRskXxcGaYFBB8CNR') + let ipfs - before(() => { - api = http.api._httpApi._apiServers[0] - }) + beforeEach(() => { + ipfs = { + bitswap: { + wantlist: sinon.stub(), + stat: sinon.stub() + } + } + }) - before(function () { - this.timeout(120 * 1000) + describe('/wantlist', () => { + it('only accepts POST', () => { + return testHttpMethod('/api/v0/bitswap/wantlist') + }) - // Add a CID to the wantlist - api.inject({ method: 'POST', url: `/api/v0/block/get?arg=${wantedCid0}` }) - api.inject({ method: 'POST', url: `/api/v0/block/get?arg=${wantedCid1}` }) + it('/wantlist', async () => { + ipfs.bitswap.wantlist.returns([ + cid + ]) - const test = async () => { - const res = await api.inject({ - method: 'POST', - url: '/api/v0/bitswap/wantlist' - }) + const res = await http({ + method: 'POST', + url: '/api/v0/bitswap/wantlist' + }, { ipfs }) - if (res.statusCode !== 200) { - throw new Error(`unexpected status ${res.statusCode}`) - } + expect(res).to.have.property('statusCode', 200) + expect(res).to.have.nested.property('result.Keys').that.deep.includes({ '/': cid.toString() }) + }) - const isWanted0 = res.result.Keys.some(k => k['/'] === wantedCid0) - const isWanted1 = res.result.Keys.some(k => k['/'] === wantedCid1) + // TODO: unskip after switch to v1 CIDs by default + it.skip('/wantlist?cid-base=base64', async () => { + ipfs.bitswap.wantlist.returns([ + cid + ]) - return isWanted0 && isWanted1 - } + const res = await http({ + method: 'POST', + url: '/api/v0/bitswap/wantlist?cid-base=base64' + }, { ipfs }) - return waitFor(test, { - name: `${wantedCid0} and ${wantedCid1} to be wanted`, - timeout: 60 * 1000 - }) + expect(res).to.have.property('statusCode', 200) + expect(res).to.have.nested.property('result.Keys').that.deep.includes({ '/': cid.toV1().toString('base64') }) }) - describe('/wantlist', () => { - it('only accepts POST', () => { - return testHttpMethod('/api/v0/bitswap/wantlist') - }) + it('/wantlist?cid-base=invalid', async () => { + ipfs.bitswap.wantlist.returns([ + cid + ]) - it('/wantlist', async () => { - const res = await api.inject({ - method: 'POST', - url: '/api/v0/bitswap/wantlist' - }) + const res = await http({ + method: 'POST', + url: '/api/v0/bitswap/wantlist?cid-base=invalid' + }, { ipfs }) - expect(res.statusCode).to.equal(200) - expect(res.result).to.have.property('Keys') - expect(res.result.Keys).to.deep.include({ '/': wantedCid0 }) - }) + expect(res).to.have.property('statusCode', 400) + expect(res).to.have.nested.property('result.Message').that.includes('Invalid request query input') + }) + }) - it('/wantlist?cid-base=base64', async () => { - const base64Cid = new CID(wantedCid1).toString('base64') - const res = await api.inject({ - method: 'POST', - url: '/api/v0/bitswap/wantlist?cid-base=base64' - }) + describe('/stat', () => { + it('only accepts POST', () => { + return testHttpMethod('/api/v0/bitswap/stat') + }) - expect(res.statusCode).to.equal(200) - expect(res.result.Keys).to.deep.include({ '/': base64Cid }) + it('/stat', async () => { + ipfs.bitswap.stat.returns({ + provideBufLen: 'provideBufLen', + blocksReceived: 'blocksReceived', + wantlist: [ + cid + ], + peers: 'peers', + dupBlksReceived: 'dupBlksReceived', + dupDataReceived: 'dupDataReceived', + dataReceived: 'dataReceived', + blocksSent: 'blocksSent', + dataSent: 'dataSent' }) - it('/wantlist?cid-base=invalid', async () => { - const res = await api.inject({ - method: 'POST', - url: '/api/v0/bitswap/wantlist?cid-base=invalid' - }) - - expect(res.statusCode).to.equal(400) - expect(res.result.Message).to.include('Invalid request query input') - }) + const res = await http({ + method: 'POST', + url: '/api/v0/bitswap/stat' + }, { ipfs }) + + expect(res).to.have.property('statusCode', 200) + expect(res).to.have.nested.property('result.ProvideBufLen', 'provideBufLen') + expect(res).to.have.nested.property('result.BlocksReceived', 'blocksReceived') + expect(res).to.have.nested.property('result.Wantlist').that.deep.includes({ '/': cid.toString() }) + expect(res).to.have.nested.property('result.Peers', 'peers') + expect(res).to.have.nested.property('result.DupBlksReceived', 'dupBlksReceived') + expect(res).to.have.nested.property('result.DupDataReceived', 'dupDataReceived') + expect(res).to.have.nested.property('result.DataReceived', 'dataReceived') + expect(res).to.have.nested.property('result.BlocksSent', 'blocksSent') + expect(res).to.have.nested.property('result.DataSent', 'dataSent') }) - describe('/stat', () => { - it('only accepts POST', () => { - return testHttpMethod('/api/v0/bitswap/stat') + it('/stat?cid-base=base64', async () => { + ipfs.bitswap.stat.returns({ + provideBufLen: 'provideBufLen', + blocksReceived: 'blocksReceived', + wantlist: [ + cid.toV1() + ], + peers: 'peers', + dupBlksReceived: 'dupBlksReceived', + dupDataReceived: 'dupDataReceived', + dataReceived: 'dataReceived', + blocksSent: 'blocksSent', + dataSent: 'dataSent' }) - it('/stat', async () => { - const res = await api.inject({ - method: 'POST', - url: '/api/v0/bitswap/stat' - }) - - expect(res.statusCode).to.equal(200) - expect(res.result).to.have.property('ProvideBufLen') - expect(res.result).to.have.property('BlocksReceived') - expect(res.result).to.have.property('Wantlist') - expect(res.result).to.have.property('Peers') - expect(res.result).to.have.property('DupBlksReceived') - expect(res.result).to.have.property('DupDataReceived') - expect(res.result).to.have.property('DataReceived') - expect(res.result).to.have.property('BlocksSent') - expect(res.result).to.have.property('DataSent') - }) + const res = await http({ + method: 'POST', + url: '/api/v0/bitswap/stat?cid-base=base64' + }, { ipfs }) - it('/stat?cid-base=base64', async () => { - const base64Cid = new CID(wantedCid1).toString('base64') - const res = await api.inject({ - method: 'POST', - url: '/api/v0/bitswap/stat?cid-base=base64' - }) - - expect(res.statusCode).to.equal(200) - expect(res.result.Wantlist).to.deep.include({ '/': base64Cid }) - }) + expect(res).to.have.property('statusCode', 200) + expect(res).to.have.nested.property('result.Wantlist').that.deep.includes({ '/': cid.toV1().toString('base64') }) + }) - it('/stat?cid-base=invalid', async () => { - const res = await api.inject({ - method: 'POST', - url: '/api/v0/bitswap/stat?cid-base=invalid' - }) + it('/stat?cid-base=invalid', async () => { + const res = await http({ + method: 'POST', + url: '/api/v0/bitswap/stat?cid-base=invalid' + }, { ipfs }) - expect(res.statusCode).to.equal(400) - expect(res.result.Message).to.include('Invalid request query input') - }) + expect(res).to.have.property('statusCode', 400) + expect(res).to.have.nested.property('result.Message').that.includes('Invalid request query input') }) }) -} +}) diff --git a/packages/ipfs/test/http-api/inject/block.js b/packages/ipfs/test/http-api/inject/block.js index 9b824f873d..146c031f54 100644 --- a/packages/ipfs/test/http-api/inject/block.js +++ b/packages/ipfs/test/http-api/inject/block.js @@ -3,240 +3,268 @@ 'use strict' const { expect } = require('interface-ipfs-core/src/utils/mocha') -const fs = require('fs') const FormData = require('form-data') const streamToPromise = require('stream-to-promise') const multibase = require('multibase') const testHttpMethod = require('../../utils/test-http-method') +const http = require('../../utils/http') +const sinon = require('sinon') +const CID = require('cids') + +describe('/block', () => { + const cid = new CID('QmZjTnYw2TFhn9Nn7tjmPSoTBoY7YRkwPzwSrSbabY24Kp') + const data = Buffer.from('hello world\n') + let ipfs + + beforeEach(() => { + ipfs = { + block: { + put: sinon.stub(), + get: sinon.stub(), + stat: sinon.stub(), + rm: sinon.stub() + } + } + }) -module.exports = (http) => { - describe('/block', () => { - let api - - before(() => { - api = http.api._httpApi._apiServers[0] + describe('/put', () => { + it('only accepts POST', () => { + return testHttpMethod('/api/v0/block/put') }) - describe('/put', () => { - it('only accepts POST', () => { - return testHttpMethod('/api/v0/block/put') - }) + it('returns 400 if no node is provided', async () => { + const form = new FormData() + const headers = form.getHeaders() + const payload = await streamToPromise(form) - it('returns 400 if no node is provided', async () => { - const form = new FormData() - const headers = form.getHeaders() - const payload = await streamToPromise(form) + const res = await http({ + method: 'POST', + url: '/api/v0/block/put', + headers, + payload + }, { ipfs }) - const res = await api.inject({ - method: 'POST', - url: '/api/v0/block/put', - headers, - payload - }) + expect(res).to.have.property('statusCode', 400) + }) - expect(res.statusCode).to.equal(400) + it('updates value', async () => { + ipfs.block.put.withArgs(data).returns({ + cid, + data }) - it('updates value', async () => { - const form = new FormData() - const filePath = 'test/fixtures/test-data/hello' - form.append('data', fs.createReadStream(filePath)) - const headers = form.getHeaders() - const expectedResult = { - Key: 'QmZjTnYw2TFhn9Nn7tjmPSoTBoY7YRkwPzwSrSbabY24Kp', - Size: 12 - } - - const payload = await streamToPromise(form) - const res = await api.inject({ - method: 'POST', - url: '/api/v0/block/put', - headers, - payload - }) - - expect(res.statusCode).to.equal(200) - expect(res.result).to.deep.equal(expectedResult) - }) + const form = new FormData() + form.append('data', data) + const headers = form.getHeaders() + const expectedResult = { + Key: cid.toString(), + Size: 12 + } + + const payload = await streamToPromise(form) + const res = await http({ + method: 'POST', + url: '/api/v0/block/put', + headers, + payload + }, { ipfs }) + + expect(res).to.have.property('statusCode', 200) + expect(res).to.have.deep.property('result', expectedResult) + }) - it('should put a value and return a base64 encoded CID', async () => { - const form = new FormData() - form.append('data', Buffer.from('TEST' + Date.now())) - const headers = form.getHeaders() - - const payload = await streamToPromise(form) - const res = await api.inject({ - method: 'POST', - url: '/api/v0/block/put?cid-base=base64', - headers, - payload - }) - - expect(res.statusCode).to.equal(200) - expect(multibase.isEncoded(res.result.Key)).to.deep.equal('base64') + it('should put a value and return a base64 encoded CID', async () => { + ipfs.block.put.withArgs(data).returns({ + cid, + data }) - it('should not put a value for invalid cid-base option', async () => { - const form = new FormData() - form.append('data', Buffer.from('TEST' + Date.now())) - const headers = form.getHeaders() - - const payload = await streamToPromise(form) - const res = await api.inject({ - method: 'POST', - url: '/api/v0/block/put?cid-base=invalid', - headers, - payload - }) - - expect(res.statusCode).to.equal(400) - expect(res.result.Message).to.include('Invalid request query input') - }) + const form = new FormData() + form.append('data', data) + const headers = form.getHeaders() + + const payload = await streamToPromise(form) + const res = await http({ + method: 'POST', + url: '/api/v0/block/put?cid-base=base64', + headers, + payload + }, { ipfs }) + + expect(res).to.have.property('statusCode', 200) + expect(multibase.isEncoded(res.result.Key)).to.equal('base64') }) - describe('/block/get', () => { - it('only accepts POST', () => { - return testHttpMethod('/api/v0/block/get') - }) + it('should not put a value for invalid cid-base option', async () => { + const form = new FormData() + form.append('data', data) + const headers = form.getHeaders() + + const payload = await streamToPromise(form) + const res = await http({ + method: 'POST', + url: '/api/v0/block/put?cid-base=invalid', + headers, + payload + }, { ipfs }) + + expect(res).to.have.property('statusCode', 400) + expect(res).to.have.nested.property('result.Message').that.includes('Invalid request query input') + }) + }) - it('returns 400 for request without argument', async () => { - const res = await api.inject({ - method: 'POST', - url: '/api/v0/block/get' - }) + describe('/block/get', () => { + it('only accepts POST', () => { + return testHttpMethod('/api/v0/block/get') + }) - expect(res.statusCode).to.equal(400) - expect(res.result.Message).to.be.a('string') - }) + it('returns 400 for request without argument', async () => { + const res = await http({ + method: 'POST', + url: '/api/v0/block/get' + }, { ipfs }) - it('returns 400 for request with invalid argument', async () => { - const res = await api.inject({ - method: 'POST', - url: '/api/v0/block/get?arg=invalid' - }) + expect(res).to.have.property('statusCode', 400) + expect(res).to.have.nested.property('result.Message').that.is.a('string') + }) - expect(res.statusCode).to.equal(400) - expect(res.result.Code).to.equal(1) - expect(res.result.Message).to.be.a('string') - }) + it('returns 400 for request with invalid argument', async () => { + const res = await http({ + method: 'POST', + url: '/api/v0/block/get?arg=invalid' + }, { ipfs }) - it('returns value', async () => { - const res = await api.inject({ - method: 'POST', - url: '/api/v0/block/get?arg=QmZjTnYw2TFhn9Nn7tjmPSoTBoY7YRkwPzwSrSbabY24Kp' - }) + expect(res).to.have.property('statusCode', 400) + expect(res).to.have.nested.property('result.Code', 1) + expect(res).to.have.nested.property('result.Message').that.is.a('string') + }) - expect(res.statusCode).to.equal(200) - expect(res.result).to.equal('hello world\n') + it('returns value', async () => { + ipfs.block.get.withArgs(cid).returns({ + cid, + data }) + + const res = await http({ + method: 'POST', + url: `/api/v0/block/get?arg=${cid}` + }, { ipfs }) + + expect(res).to.have.property('statusCode', 200) + expect(res).to.have.nested.property('result', 'hello world\n') }) + }) - describe('/block/stat', () => { - it('only accepts POST', () => { - return testHttpMethod('/api/v0/block/stat') - }) + describe('/block/stat', () => { + it('only accepts POST', () => { + return testHttpMethod('/api/v0/block/stat') + }) - it('returns 400 for request without argument', async () => { - const res = await api.inject({ - method: 'POST', - url: '/api/v0/block/stat' - }) + it('returns 400 for request without argument', async () => { + const res = await http({ + method: 'POST', + url: '/api/v0/block/stat' + }, { ipfs }) - expect(res.statusCode).to.equal(400) - expect(res.result.Message).to.be.a('string') - }) + expect(res).to.have.property('statusCode', 400) + expect(res).to.have.nested.property('result.Message').that.is.a('string') + }) - it('returns 400 for request with invalid argument', async () => { - const res = await api.inject({ - method: 'POST', - url: '/api/v0/block/stat?arg=invalid' - }) + it('returns 400 for request with invalid argument', async () => { + const res = await http({ + method: 'POST', + url: '/api/v0/block/stat?arg=invalid' + }, { ipfs }) + + expect(res).to.have.property('statusCode', 400) + expect(res).to.have.nested.property('result.Code', 1) + expect(res).to.have.nested.property('result.Message').that.is.a('string') + }) - expect(res.statusCode).to.equal(400) - expect(res.result.Code).to.equal(1) - expect(res.result.Message).to.be.a('string') + it('returns value', async () => { + ipfs.block.stat.withArgs(cid).returns({ + cid, + size: data.byteLength }) - it('returns value', async () => { - const res = await api.inject({ - method: 'POST', - url: '/api/v0/block/stat?arg=QmZjTnYw2TFhn9Nn7tjmPSoTBoY7YRkwPzwSrSbabY24Kp' - }) + const res = await http({ + method: 'POST', + url: `/api/v0/block/stat?arg=${cid}` + }, { ipfs }) - expect(res.statusCode).to.equal(200) - expect(res.result.Key) - .to.equal('QmZjTnYw2TFhn9Nn7tjmPSoTBoY7YRkwPzwSrSbabY24Kp') - expect(res.result.Size).to.equal(12) - }) + expect(res).to.have.property('statusCode', 200) + expect(res.result.Key) + .to.equal(cid.toString()) + expect(res.result.Size).to.equal(12) + }) - it('should stat a block and return a base64 encoded CID', async () => { - const form = new FormData() - form.append('data', Buffer.from('TEST' + Date.now())) - const headers = form.getHeaders() - - const payload = await streamToPromise(form) - const res = await api.inject({ - method: 'POST', - url: '/api/v0/block/put?cid-base=base64', - headers, - payload - }) - - expect(res.statusCode).to.equal(200) - expect(multibase.isEncoded(res.result.Key)).to.deep.equal('base64') + it('should stat a block and return a base64 encoded CID', async () => { + ipfs.block.stat.withArgs(cid).returns({ + cid, + size: data.byteLength }) - it('should not stat a block for invalid cid-base option', async () => { - const form = new FormData() - form.append('data', Buffer.from('TEST' + Date.now())) - const headers = form.getHeaders() - - const payload = await streamToPromise(form) - const res = await api.inject({ - method: 'POST', - url: '/api/v0/block/put?cid-base=invalid', - headers, - payload - }) - expect(res.statusCode).to.equal(400) - expect(res.result.Message).to.include('Invalid request query input') - }) + const res = await http({ + method: 'POST', + url: `/api/v0/block/stat?arg=${cid}&cid-base=base64` + }, { ipfs }) + + expect(res).to.have.property('statusCode', 200) + expect(multibase.isEncoded(res.result.Key)).to.deep.equal('base64') }) - describe('/block/rm', () => { - it('only accepts POST', () => { - return testHttpMethod('/api/v0/block/rm') - }) + it('should not stat a block for invalid cid-base option', async () => { + const form = new FormData() + form.append('data', Buffer.from('TEST' + Date.now())) + const headers = form.getHeaders() + + const payload = await streamToPromise(form) + const res = await http({ + method: 'POST', + url: '/api/v0/block/put?cid-base=invalid', + headers, + payload + }, { ipfs }) + expect(res).to.have.property('statusCode', 400) + expect(res).to.have.nested.property('result.Message').that.includes('Invalid request query input') + }) + }) - it('returns 400 for request without argument', async () => { - const res = await api.inject({ - method: 'POST', - url: '/api/v0/block/rm' - }) + describe('/block/rm', () => { + it('only accepts POST', () => { + return testHttpMethod('/api/v0/block/rm') + }) - expect(res.statusCode).to.equal(400) - expect(res.result.Message).to.be.a('string') - }) + it('returns 400 for request without argument', async () => { + const res = await http({ + method: 'POST', + url: '/api/v0/block/rm' + }, { ipfs }) + + expect(res).to.have.property('statusCode', 400) + expect(res).to.have.nested.property('result.Message').that.is.a('string') + }) - it('returns 400 for request with invalid argument', async () => { - const res = await api.inject({ - method: 'POST', - url: '/api/v0/block/rm?arg=invalid' - }) + it('returns 400 for request with invalid argument', async () => { + const res = await http({ + method: 'POST', + url: '/api/v0/block/rm?arg=invalid' + }, { ipfs }) - expect(res.statusCode).to.equal(400) - expect(res.result.Code).to.equal(1) - expect(res.result.Message).to.be.a('string') - }) + expect(res).to.have.property('statusCode', 400) + expect(res).to.have.nested.property('result.Code', 1) + expect(res).to.have.nested.property('result.Message').that.is.a('string') + }) - it('returns 200', async () => { - const res = await api.inject({ - method: 'POST', - url: '/api/v0/block/rm?arg=QmZjTnYw2TFhn9Nn7tjmPSoTBoY7YRkwPzwSrSbabY24Kp' - }) + it('returns 200', async () => { + ipfs.block.rm.withArgs([cid]).returns([{ cid }]) - expect(res.statusCode).to.equal(200) - }) + const res = await http({ + method: 'POST', + url: `/api/v0/block/rm?arg=${cid}` + }, { ipfs }) + + expect(res).to.have.property('statusCode', 200) }) }) -} +}) diff --git a/packages/ipfs/test/http-api/inject/bootstrap.js b/packages/ipfs/test/http-api/inject/bootstrap.js index 83cf490c86..21c1061e35 100644 --- a/packages/ipfs/test/http-api/inject/bootstrap.js +++ b/packages/ipfs/test/http-api/inject/bootstrap.js @@ -5,112 +5,142 @@ const { expect } = require('interface-ipfs-core/src/utils/mocha') const qs = require('qs') const defaultList = require('../../../src/core/runtime/config-nodejs.js')().Bootstrap const testHttpMethod = require('../../utils/test-http-method') +const http = require('../../utils/http') +const sinon = require('sinon') + +describe('/bootstrap', () => { + const validIp4 = '/ip4/101.236.176.52/tcp/4001/p2p/QmSoLnSGccFuZQJzRadHn95W2CrSFmZuTdDWP8HXaHca9z' + let ipfs + + beforeEach(() => { + ipfs = { + bootstrap: { + list: sinon.stub(), + add: sinon.stub(), + rm: sinon.stub() + } + } + }) -module.exports = (http) => { - describe('/bootstrap', () => { - const validIp4 = '/ip4/101.236.176.52/tcp/4001/p2p/QmSoLnSGccFuZQJzRadHn95W2CrSFmZuTdDWP8HXaHca9z' - let api - - before(() => { - api = http.api._httpApi._apiServers[0] - return api.inject({ - method: 'POST', - url: '/api/v0/bootstrap/add/default' - }) + describe('/list', () => { + it('only accepts POST', () => { + return testHttpMethod('/api/v0/bootstrap/list') }) - describe('/list', () => { - it('only accepts POST', () => { - return testHttpMethod('/api/v0/bootstrap/list') + it('returns a list', async () => { + ipfs.bootstrap.list.returns({ + Peers: defaultList }) - it('returns a list', async () => { - const res = await api.inject({ - method: 'POST', - url: '/api/v0/bootstrap/list' - }) + const res = await http({ + method: 'POST', + url: '/api/v0/bootstrap/list' + }, { ipfs }) + + expect(res).to.have.property('statusCode', 200) + expect(res).to.have.deep.nested.property('result.Peers', defaultList) + }) - expect(res.statusCode).to.be.eql(200) - expect(res.result.Peers).to.deep.equal(defaultList) + it('alias', async () => { + ipfs.bootstrap.list.returns({ + Peers: defaultList }) - it('alias', async () => { - const res = await api.inject({ - method: 'POST', - url: '/api/v0/bootstrap' - }) + const res = await http({ + method: 'POST', + url: '/api/v0/bootstrap' + }, { ipfs }) - expect(res.statusCode).to.be.eql(200) - expect(res.result.Peers).to.deep.equal(defaultList) - }) + expect(res).to.have.property('statusCode', 200) + expect(res).to.have.deep.nested.property('result.Peers', defaultList) }) + }) - describe('/add', () => { - it('only accepts POST', () => { - const query = { - arg: validIp4 - } + describe('/add', () => { + it('only accepts POST', () => { + const query = { + arg: validIp4 + } - return testHttpMethod(`/api/v0/bootstrap/add?${qs.stringify(query)}`) + return testHttpMethod(`/api/v0/bootstrap/add?${qs.stringify(query)}`) + }) + + it('adds a bootstrapper', async () => { + ipfs.bootstrap.add.withArgs(validIp4).returns({ + Peers: [ + validIp4 + ] }) - it('adds a bootstrapper', async () => { - const query = { - arg: validIp4 - } + const query = { + arg: validIp4 + } - const res = await api.inject({ - method: 'POST', - url: `/api/v0/bootstrap/add?${qs.stringify(query)}` - }) + const res = await http({ + method: 'POST', + url: `/api/v0/bootstrap/add?${qs.stringify(query)}` + }, { ipfs }) - expect(res.statusCode).to.be.eql(200) - expect(res.result.Peers).to.be.eql([validIp4]) + expect(res).to.have.property('statusCode', 200) + expect(res).to.have.deep.nested.property('result.Peers', [validIp4]) + }) + + it('restores default', async () => { + ipfs.bootstrap.add.withArgs(null).returns({ + Peers: defaultList }) - it('restores default', async () => { - const res = await api.inject({ - method: 'POST', - url: '/api/v0/bootstrap/add/default' - }) + const res = await http({ + method: 'POST', + url: '/api/v0/bootstrap/add/default' + }, { ipfs }) - expect(res.statusCode).to.be.eql(200) - expect(res.result.Peers).to.be.eql(defaultList) - }) + expect(res).to.have.property('statusCode', 200) + expect(res).to.have.deep.nested.property('result.Peers', defaultList) }) + }) - describe('/rm', () => { - it('only accepts POST', () => { - const query = { - arg: validIp4 - } + describe('/rm', () => { + it('only accepts POST', () => { + const query = { + arg: validIp4 + } - return testHttpMethod(`/api/v0/bootstrap/rm?${qs.stringify(query)}`) + return testHttpMethod(`/api/v0/bootstrap/rm?${qs.stringify(query)}`) + }) + + it('removes a bootstrapper', async () => { + ipfs.bootstrap.rm.withArgs(validIp4).returns({ + Peers: [ + validIp4 + ] }) - it('removes a bootstrapper', async () => { - const query = { - arg: validIp4 - } + const query = { + arg: validIp4 + } + + const res = await http({ + method: 'POST', + url: `/api/v0/bootstrap/rm?${qs.stringify(query)}` + }, { ipfs }) - const res = await api.inject({ - method: 'POST', - url: `/api/v0/bootstrap/rm?${qs.stringify(query)}` - }) + expect(res).to.have.property('statusCode', 200) + expect(res).to.have.deep.nested.property('result.Peers', [validIp4]) + }) - expect(res.statusCode).to.be.eql(200) - expect(res.result.Peers).to.be.eql([validIp4]) + it('removes all bootstrappers', async () => { + ipfs.bootstrap.rm.withArgs(null).returns({ + Peers: defaultList }) - it('removes all bootstrappers', async () => { - const res = await api.inject({ - method: 'POST', - url: '/api/v0/bootstrap/rm/all' - }) + const res = await http({ + method: 'POST', + url: '/api/v0/bootstrap/rm/all' + }, { ipfs }) - expect(res.statusCode).to.be.eql(200) - expect(res.result.Peers).to.be.eql(defaultList) - }) + expect(res).to.have.property('statusCode', 200) + expect(res).to.have.deep.nested.property('result.Peers', defaultList) }) }) -} +}) diff --git a/packages/ipfs/test/http-api/inject/browser-headers.js b/packages/ipfs/test/http-api/inject/browser-headers.js index e914826a53..3ccb99694e 100644 --- a/packages/ipfs/test/http-api/inject/browser-headers.js +++ b/packages/ipfs/test/http-api/inject/browser-headers.js @@ -36,7 +36,7 @@ module.exports = () => { } }, { ipfs }) - expect(res.statusCode).to.equal(405) + expect(res).to.have.property('statusCode', 405) }) it('should not block a Mozilla* browser that provides an allowed Origin', async () => { @@ -49,7 +49,7 @@ module.exports = () => { } }, { ipfs }) - expect(res.statusCode).to.equal(200) + expect(res).to.have.property('statusCode', 200) }) }) } diff --git a/packages/ipfs/test/http-api/inject/config.js b/packages/ipfs/test/http-api/inject/config.js index d11449ec63..e3cf3c5989 100644 --- a/packages/ipfs/test/http-api/inject/config.js +++ b/packages/ipfs/test/http-api/inject/config.js @@ -5,279 +5,370 @@ const { expect } = require('interface-ipfs-core/src/utils/mocha') const fs = require('fs') const FormData = require('form-data') const streamToPromise = require('stream-to-promise') -const path = require('path') const { profiles } = require('../../../src/core/components/config') const testHttpMethod = require('../../utils/test-http-method') +const http = require('../../utils/http') +const sinon = require('sinon') + +describe('/config', () => { + let ipfs + + beforeEach(() => { + ipfs = { + config: { + get: sinon.stub(), + replace: sinon.stub(), + profiles: { + apply: sinon.stub(), + list: sinon.stub() + } + } + } + }) -module.exports = (http) => { - describe('/config', () => { - const configPath = path.join(__dirname, '../../repo-tests-run/config') - const originalConfigPath = path.join(__dirname, '../../fixtures/go-ipfs-repo/config') + it('only accepts POST', () => { + return testHttpMethod('/api/v0/config') + }) - let updatedConfig - let api + it('returns 400 for request without arguments', async () => { + const res = await http({ + method: 'POST', + url: '/api/v0/config' + }, { ipfs }) - before(() => { - updatedConfig = () => JSON.parse(fs.readFileSync(configPath, 'utf8')) - api = http.api._httpApi._apiServers[0] - }) + expect(res).to.have.property('statusCode', 400) + }) - after(() => { - fs.writeFileSync(configPath, fs.readFileSync(originalConfigPath, 'utf8'), 'utf8') - }) + it('404 for request with missing args', async () => { + const res = await http({ + method: 'POST', + url: '/api/v0/config?arg=kitten' + }, { ipfs }) - it('only accepts POST', () => { - return testHttpMethod('/api/v0/config') + expect(res).to.have.property('statusCode', 404) + expect(res).to.have.nested.property('result.Code', 3) + expect(res).to.have.nested.property('result.Message').that.is.a('string') + }) + + it('returns value for request with valid arg', async () => { + ipfs.config.get.returns({ + API: { + HTTPHeaders: 'value' + } }) - it('returns 400 for request without arguments', async () => { - const res = await api.inject({ - method: 'POST', - url: '/api/v0/config' - }) + const res = await http({ + method: 'POST', + url: '/api/v0/config?arg=API.HTTPHeaders' + }, { ipfs }) + + expect(res).to.have.property('statusCode', 200) + expect(res).to.have.nested.property('result.Key', 'API.HTTPHeaders') + expect(res).to.have.nested.property('result.Value', 'value') + }) - expect(res.statusCode).to.equal(400) + it('returns value for request as subcommand', async () => { + ipfs.config.get.returns({ + API: { + HTTPHeaders: 'value' + } }) - it('404 for request with missing args', async () => { - const res = await api.inject({ - method: 'POST', - url: '/api/v0/config?arg=kitten' - }) + const res = await http({ + method: 'POST', + url: '/api/v0/config/API.HTTPHeaders' + }, { ipfs }) + + expect(res).to.have.property('statusCode', 200) + expect(res).to.have.nested.property('result.Key', 'API.HTTPHeaders') + expect(res).to.have.nested.property('result.Value', 'value') + }) - expect(res.statusCode).to.equal(404) - expect(res.result.Code).to.equal(3) - expect(res.result.Message).to.be.a('string') + it('updates value for request with both args', async () => { + ipfs.config.get.returns({ + Datastore: { + Path: 'not-kitten' + } }) - it('returns value for request with valid arg', async () => { - const res = await api.inject({ - method: 'POST', - url: '/api/v0/config?arg=API.HTTPHeaders' - }) + const res = await http({ + method: 'POST', + url: '/api/v0/config?arg=Datastore.Path&arg=kitten' + }, { ipfs }) + + expect(res).to.have.property('statusCode', 200) + expect(res).to.have.nested.property('result.Key', 'Datastore.Path') + expect(res).to.have.nested.property('result.Value', 'kitten') + expect(ipfs.config.replace.calledWith({ + Datastore: { + Path: 'kitten' + } + })).to.be.true() + }) - expect(res.statusCode).to.equal(200) - expect(res.result.Key).to.equal('API.HTTPHeaders') - expect(res.result.Value).to.equal(null) + it('returns 400 value for request with both args and JSON flag with invalid JSON argument', async () => { + ipfs.config.get.returns({ + Datastore: { + Path: 'not-kitten' + } }) - it('returns value for request as subcommand', async () => { - const res = await api.inject({ - method: 'POST', - url: '/api/v0/config/API.HTTPHeaders' - }) + const res = await http({ + method: 'POST', + url: '/api/v0/config?arg=Datastore.Path&arg=kitten&json' + }, { ipfs }) + + expect(res).to.have.property('statusCode', 400) + expect(res).to.have.nested.property('result.Code', 1) + expect(res).to.have.nested.property('result.Message').that.is.a('string') + expect(ipfs.config.replace.called).to.be.false() + }) - expect(res.statusCode).to.equal(200) - expect(res.result.Key).to.equal('API.HTTPHeaders') - expect(res.result.Value).to.equal(null) + it('updates value for request with both args and JSON flag with valid JSON argument', async () => { + ipfs.config.get.returns({ + Datastore: { + Path: 'not-kitten' + } }) - it('updates value for request with both args', async () => { - const res = await api.inject({ - method: 'POST', - url: '/api/v0/config?arg=Datastore.Path&arg=kitten' - }) + const res = await http({ + method: 'POST', + url: '/api/v0/config?arg=Datastore.Path&arg={"kitten": true}&json' + }, { ipfs }) + + expect(res).to.have.property('statusCode', 200) + expect(res).to.have.nested.property('result.Key', 'Datastore.Path') + expect(res).to.have.deep.nested.property('result.Value', { kitten: true }) + expect(ipfs.config.replace.calledWith({ + Datastore: { + Path: { + kitten: true + } + } + })).to.be.true() + }) - expect(res.statusCode).to.equal(200) - expect(res.result.Key).to.equal('Datastore.Path') - expect(res.result.Value).to.equal('kitten') - expect(updatedConfig().Datastore.Path).to.equal('kitten') + it('updates value for request with both args and bool flag and true argument', async () => { + ipfs.config.get.returns({ + Datastore: { + Path: 'not-kitten' + } }) - it('returns 400 value for request with both args and JSON flag with invalid JSON argument', async () => { - const res = await api.inject({ - method: 'POST', - url: '/api/v0/config?arg=Datastore.Path&arg=kitten&json' - }) + const res = await http({ + method: 'POST', + url: '/api/v0/config?arg=Datastore.Path&arg=true&bool' + }, { ipfs }) + + expect(res).to.have.property('statusCode', 200) + expect(res).to.have.nested.property('result.Key', 'Datastore.Path') + expect(res).to.have.nested.property('result.Value', true) + expect(ipfs.config.replace.calledWith({ + Datastore: { + Path: true + } + })).to.be.true() + }) - expect(res.statusCode).to.equal(400) - expect(res.result.Code).to.equal(1) - expect(res.result.Message).to.be.a('string') + it('updates value for request with both args and bool flag and false argument', async () => { + ipfs.config.get.returns({ + Datastore: { + Path: 'not-kitten' + } }) - it('updates value for request with both args and JSON flag with valid JSON argument', async () => { - const res = await api.inject({ - method: 'POST', - url: '/api/v0/config?arg=Datastore.Path&arg={"kitten": true}&json' - }) + const res = await http({ + method: 'POST', + url: '/api/v0/config?arg=Datastore.Path&arg=false&bool' + }, { ipfs }) + + expect(res).to.have.property('statusCode', 200) + expect(res).to.have.nested.property('result.Key', 'Datastore.Path') + expect(res).to.have.nested.property('result.Value', false) + expect(ipfs.config.replace.calledWith({ + Datastore: { + Path: false + } + })).to.be.true() + }) - expect(res.statusCode).to.equal(200) - expect(res.result.Key).to.equal('Datastore.Path') - expect(res.result.Value).to.deep.equal({ kitten: true }) - expect(updatedConfig().Datastore.Path).to.deep.equal({ kitten: true }) + describe('/show', () => { + it('only accepts POST', () => { + return testHttpMethod('/api/v0/config/show') }) - it('updates value for request with both args and bool flag and true argument', async () => { - const res = await api.inject({ - method: 'POST', - url: '/api/v0/config?arg=Datastore.Path&arg=true&bool' - }) + it('shows config', async () => { + const config = { + Datastore: { + Path: 'not-kitten' + } + } - expect(res.statusCode).to.equal(200) - expect(res.result.Key).to.equal('Datastore.Path') - expect(res.result.Value).to.deep.equal(true) - expect(updatedConfig().Datastore.Path).to.deep.equal(true) - }) + ipfs.config.get.returns(config) - it('updates value for request with both args and bool flag and false argument', async () => { - const res = await api.inject({ + const res = await http({ method: 'POST', - url: '/api/v0/config?arg=Datastore.Path&arg=false&bool' - }) + url: '/api/v0/config/show' + }, { ipfs }) - expect(res.statusCode).to.equal(200) - expect(res.result.Key).to.equal('Datastore.Path') - expect(res.result.Value).to.deep.equal(false) - expect(updatedConfig().Datastore.Path).to.deep.equal(false) + expect(res).to.have.property('statusCode', 200) + expect(res).to.have.deep.property('result', config) }) + }) - describe('/show', () => { - it('only accepts POST', () => { - return testHttpMethod('/api/v0/config/show') - }) - - it('shows config', async () => { - const res = await api.inject({ - method: 'POST', - url: '/api/v0/config/show' - }) - - expect(res.statusCode).to.equal(200) - expect(res.result).to.deep.equal(updatedConfig()) - }) + describe('/replace', () => { + it('only accepts POST', () => { + return testHttpMethod('/api/v0/config/replace') }) - describe('/replace', () => { - it('only accepts POST', () => { - return testHttpMethod('/api/v0/config/replace') - }) - - it('returns 400 if no config is provided', async () => { - const form = new FormData() - const headers = form.getHeaders() + it('returns 400 if no config is provided', async () => { + const form = new FormData() + const headers = form.getHeaders() - const payload = await streamToPromise(form) - const res = await api.inject({ - method: 'POST', - url: '/api/v0/config/replace', - headers, - payload - }) + const payload = await streamToPromise(form) + const res = await http({ + method: 'POST', + url: '/api/v0/config/replace', + headers, + payload + }, { ipfs }) - expect(res.statusCode).to.equal(400) - }) + expect(res).to.have.property('statusCode', 400) + }) - it('returns 500 if the config is invalid', async () => { - const form = new FormData() - const filePath = 'test/fixtures/test-data/badconfig' - form.append('file', fs.createReadStream(filePath)) - const headers = form.getHeaders() + it('returns 500 if the config is invalid', async () => { + const form = new FormData() + const filePath = 'test/fixtures/test-data/badconfig' + form.append('file', fs.createReadStream(filePath)) + const headers = form.getHeaders() - const payload = await streamToPromise(form) - const res = await api.inject({ - method: 'POST', - url: '/api/v0/config/replace', - headers, - payload - }) + const payload = await streamToPromise(form) + const res = await http({ + method: 'POST', + url: '/api/v0/config/replace', + headers, + payload + }, { ipfs }) - expect(res.statusCode).to.equal(500) - }) + expect(res).to.have.property('statusCode', 500) + }) - it('updates value', async () => { - const form = new FormData() - const filePath = 'test/fixtures/test-data/otherconfig' - form.append('file', fs.createReadStream(filePath)) - const headers = form.getHeaders() - const expectedConfig = JSON.parse(fs.readFileSync(filePath, 'utf8')) + it('updates value', async () => { + const expectedConfig = { + Key: 'value', + OtherKey: 'otherValue', + Deep: { + Key: { + Is: 'value' + } + }, + Array: [ + 'va1', + 'val2', + 'val3' + ] + } + const form = new FormData() + form.append('file', Buffer.from(JSON.stringify(expectedConfig))) + const headers = form.getHeaders() + + const payload = await streamToPromise(form) + const res = await http({ + method: 'POST', + url: '/api/v0/config/replace', + headers, + payload + }, { ipfs }) - const payload = await streamToPromise(form) - const res = await api.inject({ - method: 'POST', - url: '/api/v0/config/replace', - headers, - payload - }) + expect(res).to.have.property('statusCode', 200) + expect(ipfs.config.replace.calledWith(expectedConfig)).to.be.true() + }) + }) - expect(res.statusCode).to.equal(200) - expect(updatedConfig()).to.deep.equal(expectedConfig) - }) + describe('/profile/apply', () => { + it('only accepts POST', () => { + return testHttpMethod('/api/v0/config/profile/apply') }) - describe('/profile/apply', () => { - let originalConfig + it('returns 400 if no config profile is provided', async () => { + const res = await http({ + method: 'POST', + url: '/api/v0/config/profile/apply' + }, { ipfs }) - beforeEach(() => { - originalConfig = JSON.parse(fs.readFileSync(configPath, 'utf8')) - }) + expect(res).to.have.property('statusCode', 400) + }) - it('only accepts POST', () => { - return testHttpMethod('/api/v0/config/profile/apply') - }) + it('returns 400 if the config profile is invalid', async () => { + const res = await http({ + method: 'POST', + url: '/api/v0/config/profile/apply?arg=derp' + }, { ipfs }) - it('returns 400 if no config profile is provided', async () => { - const res = await api.inject({ - method: 'POST', - url: '/api/v0/config/profile/apply' - }) + expect(res).to.have.property('statusCode', 400) + }) - expect(res.statusCode).to.equal(400) + it('does not apply config profile with dry-run argument', async () => { + ipfs.config.profiles.apply.withArgs('lowpower', sinon.match({ + dryRun: true + })).returns({ + original: {}, + updated: {} }) - it('returns 400 if the config profile is invalid', async () => { - const res = await api.inject({ - method: 'POST', - url: '/api/v0/config/profile/apply?arg=derp' - }) + const res = await http({ + method: 'POST', + url: '/api/v0/config/profile/apply?arg=lowpower&dry-run=true' + }, { ipfs }) - expect(res.statusCode).to.equal(400) - }) + expect(res).to.have.property('statusCode', 200) + }) - it('does not apply config profile with dry-run argument', async () => { - const res = await api.inject({ - method: 'POST', - url: '/api/v0/config/profile/apply?arg=lowpower&dry-run=true' + Object.keys(profiles).forEach(profile => { + it(`applies config profile ${profile}`, async () => { + ipfs.config.profiles.apply.withArgs(profile, sinon.match({ + dryRun: false + })).returns({ + original: {}, + updated: {} }) - expect(res.statusCode).to.equal(200) - expect(updatedConfig()).to.deep.equal(originalConfig) - }) - - Object.keys(profiles).forEach(profile => { - it(`applies config profile ${profile}`, async () => { - const res = await api.inject({ - method: 'POST', - url: `/api/v0/config/profile/apply?arg=${profile}` - }) + const res = await http({ + method: 'POST', + url: `/api/v0/config/profile/apply?arg=${profile}` + }, { ipfs }) - expect(res.statusCode).to.equal(200) - expect(updatedConfig()).to.deep.equal(profiles[profile].transform(originalConfig)) - }) + expect(res).to.have.property('statusCode', 200) }) }) + }) - describe('/profile/list', () => { - it('only accepts POST', () => { - return testHttpMethod('/api/v0/config/profile/list') - }) + describe('/profile/list', () => { + it('only accepts POST', () => { + return testHttpMethod('/api/v0/config/profile/list') + }) - it('lists available profiles', async () => { - const res = await api.inject({ - method: 'POST', - url: '/api/v0/config/profile/list' - }) + it('lists available profiles', async () => { + ipfs.config.profiles.list.returns(Object.keys(profiles).map(name => ({ + name, + description: profiles[name].description + }))) - expect(res.statusCode).to.equal(200) + const res = await http({ + method: 'POST', + url: '/api/v0/config/profile/list' + }, { ipfs }) - const listed = JSON.parse(res.payload) + expect(res).to.have.property('statusCode', 200) + expect(res).to.have.property('payload').that.satisfies(payload => { + const listed = JSON.parse(payload) - Object.keys(profiles).forEach(name => { - const profile = listed.find(profile => profile.Name === name) + return Object.keys(profiles).reduce((acc, name) => { // eslint-disable-line max-nested-callbacks + const profile = listed.find(profile => profile.Name === name) // eslint-disable-line max-nested-callbacks - expect(profile).to.be.ok() - expect(profile.Description).to.equal(profiles[name].description) - }) + return acc && profile && profile.Description === profiles[name].description + }, true) }) }) }) -} +}) diff --git a/packages/ipfs/test/http-api/inject/dag.js b/packages/ipfs/test/http-api/inject/dag.js index 8e80126f71..15898fd436 100644 --- a/packages/ipfs/test/http-api/inject/dag.js +++ b/packages/ipfs/test/http-api/inject/dag.js @@ -2,15 +2,15 @@ /* eslint-env mocha */ 'use strict' -const { nanoid } = require('nanoid') const { expect } = require('interface-ipfs-core/src/utils/mocha') const DAGNode = require('ipld-dag-pb').DAGNode const Readable = require('stream').Readable const FormData = require('form-data') const streamToPromise = require('stream-to-promise') const CID = require('cids') -const all = require('it-all') const testHttpMethod = require('../../utils/test-http-method') +const http = require('../../utils/http') +const sinon = require('sinon') const toHeadersAndPayload = async (thing) => { const stream = new Readable() @@ -26,399 +26,349 @@ const toHeadersAndPayload = async (thing) => { } } -module.exports = (http) => { - describe('/dag', () => { - let api - - before(() => { - api = http.api._httpApi._apiServers[0] - }) +describe('/dag', () => { + const cid = new CID('QmUBdnXXPyoDFXj3Hj39dNJ5VkN3QFRskXxcGaYFBB8CNR') + let ipfs + + beforeEach(() => { + ipfs = { + dag: { + get: sinon.stub(), + put: sinon.stub(), + resolve: sinon.stub() + } + } + }) - describe('/get', () => { - it('only accepts POST', () => { - return testHttpMethod('/api/v0/dag/get') - }) - - it('returns error for request without argument', async () => { - const res = await api.inject({ - method: 'POST', - url: '/api/v0/dag/get' - }) - - expect(res.statusCode).to.equal(400) - expect(res.result.Message).to.include("Argument 'key' is required") - }) - - it('returns error for request with invalid argument', async () => { - const res = await api.inject({ - method: 'POST', - url: '/api/v0/dag/get?arg=5' - }) - - expect(res.statusCode).to.equal(400) - expect(res.result.Message).to.include("invalid 'ipfs ref' path") - }) - - it('returns value', async () => { - const node = new DAGNode(Buffer.from([]), []) - const cid = await http.api._ipfs.dag.put(node, { - format: 'dag-pb', - hashAlg: 'sha2-256' - }) - const res = await api.inject({ - method: 'POST', - url: `/api/v0/dag/get?arg=${cid.toBaseEncodedString()}` - }) - - expect(res.statusCode).to.equal(200) - expect(res.result).to.be.ok() - expect(res.result.links).to.be.empty() - expect(res.result.data).to.be.empty() - }) - - it('uses text encoding for data by default', async () => { - const node = new DAGNode(Buffer.from([0, 1, 2, 3]), []) - const cid = await http.api._ipfs.dag.put(node, { - format: 'dag-pb', - hashAlg: 'sha2-256' - }) - - const res = await api.inject({ - method: 'POST', - url: `/api/v0/dag/get?arg=${cid.toBaseEncodedString()}` - }) - - expect(res.statusCode).to.equal(200) - expect(res.result).to.be.ok() - expect(res.result.links).to.be.empty() - expect(res.result.data).to.equal('\u0000\u0001\u0002\u0003') - }) - - it('overrides data encoding', async () => { - const node = new DAGNode(Buffer.from([0, 1, 2, 3]), []) - const cid = await http.api._ipfs.dag.put(node, { - format: 'dag-pb', - hashAlg: 'sha2-256' - }) - - const res = await api.inject({ - method: 'POST', - url: `/api/v0/dag/get?arg=${cid.toBaseEncodedString()}&data-encoding=base64` - }) - - expect(res.statusCode).to.equal(200) - expect(res.result).to.be.ok() - expect(res.result.links).to.be.empty() - expect(res.result.data).to.equal('AAECAw==') - }) - - it('returns value with a path as part of the cid', async () => { - const cid = await http.api._ipfs.dag.put({ - foo: 'bar' - }, { - format: 'dag-cbor', - hashAlg: 'sha2-256' - }) - - const res = await api.inject({ - method: 'POST', - url: `/api/v0/dag/get?arg=${cid.toBaseEncodedString()}/foo` - }) - - expect(res.statusCode).to.equal(200) - expect(res.result).to.equal('bar') - }) - - it('returns value with a path as part of the cid for dag-pb nodes', async () => { - const node = new DAGNode(Buffer.from([0, 1, 2, 3]), []) - const cid = await http.api._ipfs.dag.put(node, { - format: 'dag-pb', - hashAlg: 'sha2-256' - }) - - const res = await api.inject({ - method: 'POST', - url: `/api/v0/dag/get?arg=${cid.toBaseEncodedString()}/Data&data-encoding=base64` - }) - - expect(res.statusCode).to.equal(200) - expect(res.result).to.equal('AAECAw==') - }) - - it('encodes buffers in arbitrary positions', async () => { - const cid = await http.api._ipfs.dag.put({ - foo: 'bar', - baz: { - qux: Buffer.from([0, 1, 2, 3]) - } - }, { - format: 'dag-cbor', - hashAlg: 'sha2-256' - }) - - const res = await api.inject({ - method: 'POST', - url: `/api/v0/dag/get?arg=${cid.toBaseEncodedString()}&data-encoding=base64` - }) - - expect(res.statusCode).to.equal(200) - expect(res.result.baz.qux).to.equal('AAECAw==') - }) - - it('supports specifying buffer encoding', async () => { - const cid = await http.api._ipfs.dag.put({ - foo: 'bar', - baz: Buffer.from([0, 1, 2, 3]) - }, { - format: 'dag-cbor', - hashAlg: 'sha2-256' - }) - - const res = await api.inject({ - method: 'POST', - url: `/api/v0/dag/get?arg=${cid.toBaseEncodedString()}&data-encoding=hex` - }) - - expect(res.statusCode).to.equal(200) - expect(res.result.baz).to.equal('00010203') - }) + describe('/get', () => { + it('only accepts POST', () => { + return testHttpMethod('/api/v0/dag/get') }) - describe('/put', () => { - it('only accepts POST', () => { - return testHttpMethod('/api/v0/dag/put') - }) - - it('returns error for request without file argument', async () => { - const res = await api.inject({ - method: 'POST', - url: '/api/v0/dag/put' - }) - - expect(res.statusCode).to.equal(400) - expect(res.result.Message).to.include("File argument 'object data' is required") - }) + it('returns error for request without argument', async () => { + const res = await http({ + method: 'POST', + url: '/api/v0/dag/get' + }, { ipfs }) - it('adds a dag-cbor node by default', async () => { - const node = { - foo: 'bar' - } - - const res = await api.inject({ - method: 'POST', - url: '/api/v0/dag/put', - ...await toHeadersAndPayload(JSON.stringify(node)) - }) + expect(res).to.have.property('statusCode', 400) + expect(res).to.have.nested.property('result.Message').that.includes("Argument 'key' is required") + }) - expect(res.statusCode).to.equal(200) + it('returns error for request with invalid argument', async () => { + const res = await http({ + method: 'POST', + url: '/api/v0/dag/get?arg=5' + }, { ipfs }) - const cid = new CID(res.result.Cid['/']) + expect(res).to.have.property('statusCode', 400) + expect(res).to.have.nested.property('result.Message').that.includes("invalid 'ipfs ref' path") + }) - expect(cid.codec).to.equal('dag-cbor') + it('returns value', async () => { + const node = new DAGNode(Buffer.from([]), []) + ipfs.dag.get.withArgs(cid).returns({ value: node }) - const added = await http.api._ipfs.dag.get(cid) + const res = await http({ + method: 'POST', + url: `/api/v0/dag/get?arg=${cid.toString()}` + }, { ipfs }) - expect(added.value).to.deep.equal(node) - }) + expect(res).to.have.property('statusCode', 200) + expect(res).to.have.nested.property('result.links').that.is.empty() + expect(res).to.have.nested.property('result.data').that.is.empty() + }) - it('adds a dag-pb node', async () => { - const node = { - data: [], - links: [] - } + it('uses text encoding for data by default', async () => { + const node = new DAGNode(Buffer.from([0, 1, 2, 3]), []) + ipfs.dag.get.withArgs(cid).returns({ value: node }) - const res = await api.inject({ - method: 'POST', - url: '/api/v0/dag/put?format=dag-pb', - ...await toHeadersAndPayload(JSON.stringify(node)) - }) + const res = await http({ + method: 'POST', + url: `/api/v0/dag/get?arg=${cid.toBaseEncodedString()}` + }, { ipfs }) - expect(res.statusCode).to.equal(200) + expect(res).to.have.property('statusCode', 200) + expect(res.result).to.be.ok() + expect(res).to.have.nested.property('result.links').that.is.empty() + expect(res).to.have.nested.property('result.data', '\u0000\u0001\u0002\u0003') + }) - const cid = new CID(res.result.Cid['/']) + it('overrides data encoding', async () => { + const node = new DAGNode(Buffer.from([0, 1, 2, 3]), []) + ipfs.dag.get.withArgs(cid).returns({ value: node }) - expect(cid.codec).to.equal('dag-pb') + const res = await http({ + method: 'POST', + url: `/api/v0/dag/get?arg=${cid.toString()}&data-encoding=base64` + }, { ipfs }) - const added = await http.api._ipfs.dag.get(cid) + expect(res).to.have.property('statusCode', 200) + expect(res).to.have.nested.property('result.links').that.is.empty() + expect(res).to.have.nested.property('result.data').that.equals('AAECAw==') + }) - expect(added.value.Data).to.be.empty() - expect(added.value.Links).to.be.empty() - }) + it('returns value with a path as part of the cid', async () => { + ipfs.dag.get.withArgs(cid, 'foo').returns({ value: 'bar' }) - it('adds a raw node', async () => { - const node = Buffer.from([0, 1, 2, 3]) + const res = await http({ + method: 'POST', + url: `/api/v0/dag/get?arg=${cid.toString()}/foo` + }, { ipfs }) - const res = await api.inject({ - method: 'POST', - url: '/api/v0/dag/put?format=raw', - ...await toHeadersAndPayload(node) - }) + expect(res).to.have.property('statusCode', 200) + expect(res).to.have.nested.property('result', 'bar') + }) - expect(res.statusCode).to.equal(200) + it('returns value with a path as part of the cid for dag-pb nodes', async () => { + const node = new DAGNode(Buffer.from([0, 1, 2, 3]), []) + ipfs.dag.get.withArgs(cid, 'Data').returns({ value: node.Data }) - const cid = new CID(res.result.Cid['/']) + const res = await http({ + method: 'POST', + url: `/api/v0/dag/get?arg=${cid.toString()}/Data&data-encoding=base64` + }, { ipfs }) - expect(cid.codec).to.equal('raw') + expect(res).to.have.property('statusCode', 200) + expect(res).to.have.property('result', 'AAECAw==') + }) - const added = await http.api._ipfs.dag.get(cid) + it('encodes buffers in arbitrary positions', async () => { + const node = { + foo: 'bar', + baz: { + qux: Buffer.from([0, 1, 2, 3]) + } + } + ipfs.dag.get.withArgs(cid).returns({ value: node }) - expect(added.value).to.deep.equal(node) - }) + const res = await http({ + method: 'POST', + url: `/api/v0/dag/get?arg=${cid.toString()}&data-encoding=base64` + }, { ipfs }) - it('pins a node after adding', async () => { - const node = { - foo: 'bar', - disambiguator: nanoid() - } + expect(res).to.have.property('statusCode', 200) + expect(res).to.have.nested.property('result.baz.qux', 'AAECAw==') + }) - const res = await api.inject({ - method: 'POST', - url: '/api/v0/dag/put?pin=true', - ...await toHeadersAndPayload(JSON.stringify(node)) - }) + it('supports specifying buffer encoding', async () => { + const node = { + foo: 'bar', + baz: Buffer.from([0, 1, 2, 3]) + } + ipfs.dag.get.withArgs(cid).returns({ value: node }) - expect(res.statusCode).to.equal(200) + const res = await http({ + method: 'POST', + url: `/api/v0/dag/get?arg=${cid.toString()}&data-encoding=hex` + }, { ipfs }) - const cid = new CID(res.result.Cid['/']) - const pinset = await all(http.api._ipfs.pin.ls()) + expect(res).to.have.property('statusCode', 200) + expect(res).to.have.nested.property('result.baz', '00010203') + }) + }) - expect(pinset.map(pin => pin.cid.toString())).to.contain(cid.toString()) - }) + describe('/put', () => { + it('only accepts POST', () => { + return testHttpMethod('/api/v0/dag/put') + }) - it('does not pin a node after adding', async () => { - const node = { - foo: 'bar', - disambiguator: nanoid() - } + it('returns error for request without file argument', async () => { + const res = await http({ + method: 'POST', + url: '/api/v0/dag/put' + }, { ipfs }) - const res = await api.inject({ - method: 'POST', - url: '/api/v0/dag/put?pin=false', - ...await toHeadersAndPayload(JSON.stringify(node)) - }) + expect(res).to.have.property('statusCode', 400) + expect(res).to.have.nested.property('result.Message').that.includes("File argument 'object data' is required") + }) - expect(res.statusCode).to.equal(200) + it('adds a dag-cbor node by default', async () => { + const node = { + foo: 'bar' + } + ipfs.dag.put.withArgs(node).returns(cid) - const cid = new CID(res.result.Cid['/']) - const pinset = await all(http.api._ipfs.pin.ls()) + const res = await http({ + method: 'POST', + url: '/api/v0/dag/put', + ...await toHeadersAndPayload(JSON.stringify(node)) + }, { ipfs }) - expect(pinset.map(pin => pin.cid.toString())).to.not.contain(cid.toString('base58btc')) - }) + expect(res).to.have.property('statusCode', 200) + expect(res).to.have.deep.nested.property('result.Cid', { '/': cid.toString() }) }) - describe('/resolve', () => { - it('only accepts POST', () => { - return testHttpMethod('/api/v0/dag/resolve') - }) + it('adds a dag-pb node', async () => { + const node = { + data: [], + links: [] + } + ipfs.dag.put.withArgs(node, sinon.match({ + format: 'dag-pb' + })).returns(cid) + + const res = await http({ + method: 'POST', + url: '/api/v0/dag/put?format=dag-pb', + ...await toHeadersAndPayload(JSON.stringify(node)) + }, { ipfs }) + + expect(res).to.have.property('statusCode', 200) + expect(res).to.have.deep.nested.property('result.Cid', { '/': cid.toString() }) + }) - it('returns error for request without argument', async () => { - const res = await api.inject({ - method: 'POST', - url: '/api/v0/dag/resolve' - }) + it('adds a raw node', async () => { + const node = Buffer.from([0, 1, 2, 3]) + ipfs.dag.put.withArgs(node, sinon.match({ + format: 'raw' + })).returns(cid) - expect(res.statusCode).to.equal(400) - expect(res.result.Message).to.include('argument "ref" is required') - }) + const res = await http({ + method: 'POST', + url: '/api/v0/dag/put?format=raw', + ...await toHeadersAndPayload(node) + }, { ipfs }) - it('resolves a node', async () => { - const node = { - foo: 'bar' - } - const cid = await http.api._ipfs.dag.put(node, { - format: 'dag-cbor', - hashAlg: 'sha2-256' - }) + expect(res).to.have.property('statusCode', 200) + expect(res).to.have.deep.nested.property('result.Cid', { '/': cid.toString() }) + }) - const res = await api.inject({ - method: 'POST', - url: `/api/v0/dag/resolve?arg=${cid.toBaseEncodedString()}` - }) + it('pins a node after adding', async () => { + const node = { + foo: 'bar' + } + ipfs.dag.put.withArgs(node, sinon.match({ + pin: true + })).returns(cid) + + const res = await http({ + method: 'POST', + url: '/api/v0/dag/put?pin=true', + ...await toHeadersAndPayload(JSON.stringify(node)) + }, { ipfs }) + + expect(res).to.have.property('statusCode', 200) + expect(res).to.have.deep.nested.property('result.Cid', { '/': cid.toString() }) + }) - expect(res.statusCode).to.equal(200) - expect(res.result).to.be.ok() + it('does not pin a node after adding', async () => { + const node = { + foo: 'bar' + } + ipfs.dag.put.withArgs(node, sinon.match({ + pin: false + })).returns(cid) + + const res = await http({ + method: 'POST', + url: '/api/v0/dag/put?pin=false', + ...await toHeadersAndPayload(JSON.stringify(node)) + }, { ipfs }) + + expect(res).to.have.property('statusCode', 200) + expect(res).to.have.deep.nested.property('result.Cid', { '/': cid.toString() }) + }) - const returnedCid = new CID(res.result.Cid['/']) - const returnedRemainerPath = res.result.RemPath + it('does not pin a node by default', async () => { + const node = { + foo: 'bar' + } + ipfs.dag.put.withArgs(node, sinon.match({ + pin: false + })).returns(cid) + + const res = await http({ + method: 'POST', + url: '/api/v0/dag/put', + ...await toHeadersAndPayload(JSON.stringify(node)) + }, { ipfs }) + + expect(res).to.have.property('statusCode', 200) + expect(res).to.have.deep.nested.property('result.Cid', { '/': cid.toString() }) + }) + }) - expect(returnedCid).to.deep.equal(cid) - expect(returnedRemainerPath).to.be.empty() - }) + describe('/resolve', () => { + it('only accepts POST', () => { + return testHttpMethod('/api/v0/dag/resolve') + }) - it('returns the remainder path from within the resolved node', async () => { - const node = { - foo: 'bar' - } - const cid = await http.api._ipfs.dag.put(node, { - format: 'dag-cbor', - hashAlg: 'sha2-256' - }) + it('returns error for request without argument', async () => { + const res = await http({ + method: 'POST', + url: '/api/v0/dag/resolve' + }, { ipfs }) - const res = await api.inject({ - method: 'POST', - url: `/api/v0/dag/resolve?arg=${cid.toBaseEncodedString()}/foo` - }) + expect(res).to.have.property('statusCode', 400) + expect(res.result.Message).to.include('argument "ref" is required') + }) - expect(res.statusCode).to.equal(200) - expect(res.result).to.be.ok() + it('resolves a node', async () => { + ipfs.dag.resolve.withArgs(cid).returns([{ + value: cid, + remainderPath: '' + }]) - const returnedCid = new CID(res.result.Cid['/']) - const returnedRemainerPath = res.result.RemPath + const res = await http({ + method: 'POST', + url: `/api/v0/dag/resolve?arg=${cid.toString()}` + }, { ipfs }) - expect(returnedCid).to.deep.equal(cid) - expect(returnedRemainerPath).to.equal('foo') - }) + expect(res).to.have.property('statusCode', 200) + expect(res).to.have.deep.nested.property('result.Cid', { '/': cid.toString() }) + expect(res).to.have.nested.property('result.RemPath', '') + }) - it('returns an error when the path is not available', async () => { - const node = { - foo: 'bar' - } - const cid = await http.api._ipfs.dag.put(node, { - format: 'dag-cbor', - hashAlg: 'sha2-256' - }) - - const res = await api.inject({ - method: 'POST', - url: `/api/v0/dag/resolve?arg=${cid.toBaseEncodedString()}/bar` - }) - - expect(res.statusCode).to.equal(500) - expect(res.result).to.be.ok() - }) - - it('resolves across multiple nodes, returning the CID of the last node traversed', async () => { - const node2 = { - bar: 'baz' + it('returns the remainder path from within the resolved node', async () => { + ipfs.dag.resolve.withArgs(cid, 'foo').returns([{ + value: { + value: cid, + remainderPath: 'foo' } - const cid2 = await http.api._ipfs.dag.put(node2, { - format: 'dag-cbor', - hashAlg: 'sha2-256' - }) + }]) - const node1 = { - foo: cid2 - } + const res = await http({ + method: 'POST', + url: `/api/v0/dag/resolve?arg=${cid.toString()}/foo` + }, { ipfs }) - const cid1 = await http.api._ipfs.dag.put(node1, { - format: 'dag-cbor', - hashAlg: 'sha2-256' - }) + expect(res).to.have.property('statusCode', 200) + expect(res).to.have.deep.nested.property('result.Cid', { '/': cid.toString() }) + expect(res).to.have.nested.property('result.RemPath', 'foo') + }) - const res = await api.inject({ - method: 'POST', - url: `/api/v0/dag/resolve?arg=${cid1.toBaseEncodedString()}/foo/bar` - }) + it('returns an error when the path is not available', async () => { + ipfs.dag.resolve.withArgs(cid, 'bar').throws(new Error('Not found')) - expect(res.statusCode).to.equal(200) - expect(res.result).to.be.ok() + const res = await http({ + method: 'POST', + url: `/api/v0/dag/resolve?arg=${cid.toString()}/bar` + }, { ipfs }) - const returnedCid = new CID(res.result.Cid['/']) - const returnedRemainerPath = res.result.RemPath + expect(res).to.have.property('statusCode', 500) + expect(res.result).to.be.ok() + }) - expect(returnedCid).to.deep.equal(cid2) - expect(returnedRemainerPath).to.equal('bar') - }) + it('resolves across multiple nodes, returning the CID of the last node traversed', async () => { + const cid2 = new CID('QmUBdnXXPyoDFXj3Hj39dNJ5VkN3QFRskXxcGaYFBB8CNA') + + ipfs.dag.resolve.withArgs(cid, 'foo/bar').returns([{ + value: cid, + remainderPath: 'foo' + }, { + value: cid2, + remainderPath: 'bar' + }]) + + const res = await http({ + method: 'POST', + url: `/api/v0/dag/resolve?arg=${cid.toString()}/foo/bar` + }, { ipfs }) + + expect(res).to.have.property('statusCode', 200) + expect(res).to.have.deep.nested.property('result.Cid', { '/': cid2.toString() }) + expect(res).to.have.nested.property('result.RemPath', 'bar') }) }) -} +}) diff --git a/packages/ipfs/test/http-api/inject/dht.js b/packages/ipfs/test/http-api/inject/dht.js index 8e961cbc88..cbd11755bd 100644 --- a/packages/ipfs/test/http-api/inject/dht.js +++ b/packages/ipfs/test/http-api/inject/dht.js @@ -4,180 +4,274 @@ const { expect } = require('interface-ipfs-core/src/utils/mocha') const testHttpMethod = require('../../utils/test-http-method') +const http = require('../../utils/http') +const sinon = require('sinon') +const errCode = require('err-code') +const CID = require('cids') + +// TODO: unskip when DHT is enabled: https://github.com/ipfs/js-ipfs/pull/1994 +describe('/dht', () => { + const peerId = new CID('QmQ2zigjQikYnyYUSXZydNXrDRhBut2mubwJBaLXobMt3A') + const cid = new CID('Qmc77hSNykXJ6Jxp1C6RpD8VENV7RK6JD7eAcWpc7nEZx2') + let ipfs + + beforeEach(() => { + ipfs = { + dht: { + findPeer: sinon.stub(), + findProvs: sinon.stub(), + get: sinon.stub(), + provide: sinon.stub(), + put: sinon.stub(), + query: sinon.stub() + } + } + }) + + describe('/findpeer', () => { + it('only accepts POST', () => { + return testHttpMethod('/api/v0/dht/findpeer') + }) + + it('returns 400 if no peerId is provided', async () => { + const res = await http({ + method: 'POST', + url: '/api/v0/dht/findpeer' + }, { ipfs }) + + expect(res).to.have.property('statusCode', 400) + expect(res).to.have.nested.property('result.Code', 1) + }) + + it('returns 404 if peerId is provided as there is no peers in the routing table', async () => { + ipfs.dht.findPeer.withArgs(peerId).throws(errCode(new Error('Nope'), 'ERR_LOOKUP_FAILED')) + + const res = await http({ + method: 'POST', + url: `/api/v0/dht/findpeer?arg=${peerId}` + }, { ipfs }) + + expect(res).to.have.property('statusCode', 404) + }) + }) + + describe('/findprovs', () => { + it('only accepts POST', async () => { + const res = await http({ + method: 'GET', + url: '/api/v0/dht/findprovs' + }, { ipfs }) + + expect(res).to.have.property('statusCode', 405) + }) + + it('returns 400 if no key is provided', async () => { + const res = await http({ + method: 'POST', + url: '/api/v0/dht/findprovs' + }, { ipfs }) + + expect(res).to.have.property('statusCode', 400) + expect(res).to.have.nested.property('result.Code', 1) + }) + + it('returns 200 if key is provided', async () => { + ipfs.dht.findProvs.withArgs(cid).returns([{ + id: peerId, + addrs: [ + 'addr' + ] + }]) + + const res = await http({ + method: 'POST', + url: `/api/v0/dht/findprovs?arg=${cid}` + }, { ipfs }) + + expect(res).to.have.property('statusCode', 200) + expect(res).to.have.nested.property('result.Type', 4) + expect(res).to.have.nested.property('result.Responses[0].ID', peerId.toString()) + expect(res).to.have.nested.property('result.Responses[0].Addrs[0]', 'addr') + }) + + it('overrides num-providers', async () => { + ipfs.dht.findProvs.withArgs(cid, sinon.match({ + numProviders: 10 + })).returns([{ + id: peerId, + addrs: [ + 'addr' + ] + }]) + + const res = await http({ + method: 'POST', + url: `/api/v0/dht/findprovs?arg=${cid}&num-providers=10` + }, { ipfs }) + + expect(res).to.have.property('statusCode', 200) + expect(res).to.have.nested.property('result.Type', 4) + expect(res).to.have.nested.property('result.Responses[0].ID', peerId.toString()) + expect(res).to.have.nested.property('result.Responses[0].Addrs[0]', 'addr') + }) + }) + + describe('/get', () => { + it('only accepts POST', async () => { + const res = await http({ + method: 'GET', + url: '/api/v0/dht/get' + }, { ipfs }) + + expect(res).to.have.property('statusCode', 405) + }) + + it('returns 400 if no key is provided', async () => { + const res = await http({ + method: 'POST', + url: '/api/v0/dht/get' + }, { ipfs }) + + expect(res).to.have.property('statusCode', 400) + expect(res).to.have.nested.property('result.Code', 1) + }) + + it('returns 200 if key is provided', async () => { + const key = 'key' + const value = 'value' + ipfs.dht.get.withArgs(Buffer.from(key)).returns(value) + + const res = await http({ + method: 'POST', + url: `/api/v0/dht/get?arg=${key}` + }, { ipfs }) + + expect(res).to.have.property('statusCode', 200) + expect(res).to.have.nested.property('result.Type', 5) + expect(res).to.have.nested.property('result.Extra', value) + }) + }) + + describe('/provide', () => { + it('only accepts POST', async () => { + const res = await http({ + method: 'GET', + url: '/api/v0/dht/provide' + }, { ipfs }) + + expect(res.statusCode).to.equal(405) + }) + + it('returns 400 if no key is provided', async () => { + const res = await http({ + method: 'POST', + url: '/api/v0/dht/provide' + }, { ipfs }) + + expect(res).to.have.property('statusCode', 400) + expect(res).to.have.nested.property('result.Code', 1) + }) + + it('returns 500 if key is invalid', async () => { + const res = await http({ + method: 'POST', + url: '/api/v0/dht/provide?arg=derp' + }, { ipfs }) + + expect(res).to.have.property('statusCode', 500) // needs file add + expect(ipfs.dht.provide.called).to.be.false() + }) + + it('returns 500 if key is provided as the file was not added', async () => { + ipfs.dht.provide.withArgs(cid).throws(new Error('wut')) + + const res = await http({ + method: 'POST', + url: `/api/v0/dht/provide?arg=${cid}` + }, { ipfs }) + + expect(res).to.have.property('statusCode', 500) // needs file add + }) + + it('returns 200 if key is provided', async () => { + const res = await http({ + method: 'POST', + url: `/api/v0/dht/provide?arg=${cid}` + }, { ipfs }) + + expect(res).to.have.property('statusCode', 200) // needs file add + expect(ipfs.dht.provide.calledWith(cid)).to.be.true() + }) + }) + + describe('/put', () => { + it('only accepts POST', async () => { + const res = await http({ + method: 'GET', + url: '/api/v0/dht/put' + }, { ipfs }) + + expect(res.statusCode).to.equal(405) + }) + + it('returns 400 if no key or value is provided', async () => { + const res = await http({ + method: 'POST', + url: '/api/v0/dht/put' + }, { ipfs }) + + expect(res).to.have.property('statusCode', 400) + expect(res).to.have.nested.property('result.Code', 1) + }) + + it('returns 200 if key and value is provided', async function () { + const key = 'key' + const value = 'value' + + const res = await http({ + method: 'POST', + url: `/api/v0/dht/put?arg=${key}&arg=${value}` + }, { ipfs }) + + expect(res).to.have.property('statusCode', 200) + expect(ipfs.dht.put.calledWith(Buffer.from(key), Buffer.from(value))).to.be.true() + }) + }) + + describe('/query', () => { + it('only accepts POST', async () => { + const res = await http({ + method: 'GET', + url: '/api/v0/dht/query' + }, { ipfs }) + + expect(res.statusCode).to.equal(405) + }) + + it('returns 400 if no key or value is provided', async () => { + const res = await http({ + method: 'POST', + url: '/api/v0/dht/query' + }, { ipfs }) + + expect(res).to.have.property('statusCode', 400) + expect(res).to.have.nested.property('result.Code', 1) + }) -module.exports = (http) => { - // TODO: unskip when DHT is enabled: https://github.com/ipfs/js-ipfs/pull/1994 - describe.skip('/dht', () => { - let api - - before(() => { - api = http.api._httpApi._apiServers[0] - }) + it('returns 200 if key and value is provided', async function () { + const key = 'key' - describe('/findpeer', () => { - it('only accepts POST', () => { - return testHttpMethod('/api/v0/dht/findpeer') - }) - - it('returns 400 if no peerId is provided', async () => { - const res = await api.inject({ - method: 'POST', - url: '/api/v0/dht/findpeer' - }) - - expect(res.statusCode).to.equal(400) - expect(res.result.Code).to.be.eql(1) - }) - - it('returns 404 if peerId is provided as there is no peers in the routing table', async () => { - const peerId = 'QmQ2zigjQikYnyYUSXZydNXrDRhBut2mubwJBaLXobMt3A' - const res = await api.inject({ - method: 'POST', - url: `/api/v0/dht/findpeer?arg=${peerId}` - }) - - expect(res.statusCode).to.equal(404) - }) - }) - - describe('/findprovs', () => { - it('only accepts POST', async () => { - const res = await api.inject({ - method: 'GET', - url: '/api/v0/dht/findprovs' - }) - - expect(res.statusCode).to.equal(404) - }) - - it('returns 400 if no key is provided', async () => { - const res = await api.inject({ - method: 'POST', - url: '/api/v0/dht/findprovs' - }) - - expect(res.statusCode).to.equal(400) - expect(res.result.Code).to.be.eql(1) - }) - - it('returns 200 if key is provided', async () => { - const key = 'Qmc77hSNykXJ6Jxp1C6RpD8VENV7RK6JD7eAcWpc7nEZx2' - const res = await api.inject({ - method: 'POST', - url: `/api/v0/dht/findprovs?arg=${key}` - }) - - expect(res.statusCode).to.equal(200) - expect(res.result.Type).to.be.eql(4) - }) - }) - - describe('/get', () => { - it('only accepts POST', async () => { - const res = await api.inject({ - method: 'GET', - url: '/api/v0/dht/get' - }) - - expect(res.statusCode).to.equal(404) - }) - - it('returns 400 if no key is provided', async () => { - const res = await api.inject({ - method: 'POST', - url: '/api/v0/dht/get' - }) - - expect(res.statusCode).to.equal(400) - expect(res.result.Code).to.be.eql(1) - }) - - it('returns 200 if key is provided', async () => { - const key = 'key' - const value = 'value' - - let res = await api.inject({ - method: 'POST', - url: `/api/v0/dht/put?arg=${key}&arg=${value}` - }) - - expect(res.statusCode).to.equal(200) - - res = await api.inject({ - method: 'POST', - url: `/api/v0/dht/get?arg=${key}` - }) - - expect(res.statusCode).to.equal(200) - expect(res.result.Type).to.be.eql(5) - expect(res.result.Extra).to.be.eql(value) - }) - }) - - describe('/provide', () => { - it('only accepts POST', async () => { - const res = await api.inject({ - method: 'GET', - url: '/api/v0/dht/provide' - }) - - expect(res.statusCode).to.equal(404) - }) - - it('returns 400 if no key is provided', async () => { - const res = await api.inject({ - method: 'POST', - url: '/api/v0/dht/provide' - }) - - expect(res.statusCode).to.equal(400) - expect(res.result.Code).to.be.eql(1) - }) - - it('returns 500 if key is provided as the file was not added', async () => { - const key = 'Qmc77hSNykXJ6Jxp1C6RpD8VENV7RK6JD7eAcWpc7nEZx2' - const res = await api.inject({ - method: 'POST', - url: `/api/v0/dht/provide?arg=${key}` - }) - - expect(res.statusCode).to.equal(500) // needs file add - }) - }) - - describe('/put', () => { - it('only accepts POST', async () => { - const res = await api.inject({ - method: 'GET', - url: '/api/v0/dht/put' - }) - - expect(res.statusCode).to.equal(404) - }) - - it('returns 400 if no key or value is provided', async () => { - const res = await api.inject({ - method: 'POST', - url: '/api/v0/dht/put' - }) - - expect(res.statusCode).to.equal(400) - expect(res.result.Code).to.be.eql(1) - }) + ipfs.dht.query.withArgs(key).returns([{ + id: 'id' + }]) - it('returns 200 if key and value is provided', async function () { - this.timeout(60 * 1000) - - const key = 'key' - const value = 'value' + const res = await http({ + method: 'POST', + url: `/api/v0/dht/query?arg=${key}` + }, { ipfs }) - const res = await api.inject({ - method: 'POST', - url: `/api/v0/dht/put?arg=${key}&arg=${value}` - }) - - expect(res.statusCode).to.equal(200) - }) + expect(res).to.have.property('statusCode', 200) + expect(JSON.parse(res.result)).to.have.property('ID', 'id') }) }) -} +}) diff --git a/packages/ipfs/test/http-api/inject/dns.js b/packages/ipfs/test/http-api/inject/dns.js index 8d86a3addb..e797954c51 100644 --- a/packages/ipfs/test/http-api/inject/dns.js +++ b/packages/ipfs/test/http-api/inject/dns.js @@ -3,41 +3,79 @@ const { expect } = require('interface-ipfs-core/src/utils/mocha') const testHttpMethod = require('../../utils/test-http-method') +const http = require('../../utils/http') +const sinon = require('sinon') -module.exports = (http) => { - describe('/dns', () => { - let api - - before(() => { - api = http.api._httpApi._apiServers[0] - }) - - it('only accepts POST', () => { - return testHttpMethod('/api/v0/dns?arg=ipfs.io') - }) - - it('resolve ipfs.io DNS', async () => { - const res = await api.inject({ - method: 'POST', - url: '/api/v0/dns?arg=ipfs.io' - }) - - expect(res.result).to.have.property('Path') - }) - - it('resolve ipfs.enstest.eth ENS', async function () { - const res = await api.inject({ - method: 'POST', - url: '/api/v0/dns?arg=ipfs.enstest.eth' - }) - - // TODO: eth.link domains have no SLA yet and are liable to be down... - // Remove skip when reliable! - if (res.statusCode === 500) { - return this.skip() - } - - expect(res.result).to.have.property('Path') - }) +describe('/dns', () => { + let ipfs + + beforeEach(() => { + ipfs = { + dns: sinon.stub() + } + }) + + it('only accepts POST', () => { + return testHttpMethod('/api/v0/dns?arg=ipfs.io') + }) + + it('resolves a domain', async () => { + const domain = 'ipfs.io' + ipfs.dns.withArgs(domain, sinon.match({ + recursive: false, + format: undefined + })).returns('path') + + const res = await http({ + method: 'POST', + url: `/api/v0/dns?arg=${domain}` + }, { ipfs }) + + expect(res).to.have.nested.property('result.Path', 'path') + }) + + it('resolves a domain recursively', async () => { + const domain = 'ipfs.io' + ipfs.dns.withArgs(domain, sinon.match({ + recursive: true, + format: undefined + })).returns('path') + + const res = await http({ + method: 'POST', + url: `/api/v0/dns?arg=${domain}&recursive=true` + }, { ipfs }) + + expect(res).to.have.nested.property('result.Path', 'path') + }) + + it('resolves a domain recursively (short option)', async () => { + const domain = 'ipfs.io' + ipfs.dns.withArgs(domain, sinon.match({ + recursive: true, + format: undefined + })).returns('path') + + const res = await http({ + method: 'POST', + url: `/api/v0/dns?arg=${domain}&r=true` + }, { ipfs }) + + expect(res).to.have.nested.property('result.Path', 'path') + }) + + it('resolves a domain with a format', async () => { + const domain = 'ipfs.io' + ipfs.dns.withArgs(domain, sinon.match({ + recursive: false, + format: 'derp' + })).returns('path') + + const res = await http({ + method: 'POST', + url: `/api/v0/dns?arg=${domain}&format=derp` + }, { ipfs }) + + expect(res).to.have.nested.property('result.Path', 'path') }) -} +}) diff --git a/packages/ipfs/test/http-api/inject/files.js b/packages/ipfs/test/http-api/inject/files.js index 44f53327db..1ff5c61049 100644 --- a/packages/ipfs/test/http-api/inject/files.js +++ b/packages/ipfs/test/http-api/inject/files.js @@ -8,265 +8,362 @@ const FormData = require('form-data') const streamToPromise = require('stream-to-promise') const multibase = require('multibase') const testHttpMethod = require('../../utils/test-http-method') +const http = require('../../utils/http') +const sinon = require('sinon') +const CID = require('cids') +const first = require('it-first') +const toBuffer = require('it-to-buffer') + +describe('/files', () => { + const cid = new CID('QmUBdnXXPyoDFXj3Hj39dNJ5VkN3QFRskXxcGaYFBB8CNR') + let ipfs + + beforeEach(() => { + ipfs = { + add: sinon.stub(), + cat: sinon.stub(), + get: sinon.stub(), + ls: sinon.stub(), + refs: sinon.stub() + } + + ipfs.refs.local = sinon.stub() + }) -module.exports = (http) => { - describe('/files', () => { - let api + async function assertAddArgs (url, fn) { + const content = Buffer.from('TEST\n') + + ipfs.add.callsFake(async function * (source, opts) { + expect(fn(opts)).to.be.true() + + const input = await first(source) + expect(await toBuffer(input.content)).to.deep.equal(content) + + yield { + path: cid.toString(), + cid, + size: content.byteLength, + mode: 0o420, + mtime: { + secs: 100, + nsecs: 0 + } + } + }) - before(() => { - api = http.api._httpApi._apiServers[0] + const form = new FormData() + form.append('data', content) + const headers = form.getHeaders() + + const payload = await streamToPromise(form) + const res = await http({ + method: 'POST', + url, + headers, + payload + }, { ipfs }) + + expect(res).to.have.property('statusCode', 200) + expect(JSON.parse(res.result).Hash).to.equal(cid.toString()) + } + + describe('/add', () => { + it('only accepts POST', () => { + return testHttpMethod('/api/v0/add') }) - describe('/add', () => { - it('only accepts POST', () => { - return testHttpMethod('/api/v0/add') - }) + it('should add buffer bigger than Hapi default max bytes (1024 * 1024)', async () => { + const payload = Buffer.from([ + '', + '------------287032381131322', + 'Content-Disposition: form-data; name="test"; filename="test.txt"', + 'Content-Type: text/plain', + '', + randomBytes(1024 * 1024 * 2).toString('hex'), + '------------287032381131322--' + ].join('\r\n')) + + const res = await http({ + method: 'POST', + url: '/api/v0/add', + headers: { + 'Content-Type': 'multipart/form-data; boundary=----------287032381131322' + }, + payload + }, { ipfs }) + + expect(res).to.have.property('statusCode', 200) + }) - it('should add buffer bigger than Hapi default max bytes (1024 * 1024)', async () => { - const payload = Buffer.from([ - '', - '------------287032381131322', - 'Content-Disposition: form-data; name="test"; filename="test.txt"', - 'Content-Type: text/plain', - '', - randomBytes(1024 * 1024 * 2).toString('hex'), - '------------287032381131322--' - ].join('\r\n')) - - const res = await api.inject({ - method: 'POST', - url: '/api/v0/add', - headers: { - 'Content-Type': 'multipart/form-data; boundary=----------287032381131322' - }, - payload - }) - - expect(res.statusCode).to.not.equal(413) // Payload too large - expect(res.statusCode).to.equal(200) - }) + it('should add data and return a base64 encoded CID', async () => { + const content = Buffer.from('TEST' + Date.now()) + + ipfs.add.returns([{ + path: cid.toString(), + cid, + size: content.byteLength, + mode: 0o420, + mtime: { + secs: 100, + nsecs: 0 + } + }]) + + const form = new FormData() + form.append('data', content) + const headers = form.getHeaders() + + const payload = await streamToPromise(form) + const res = await http({ + method: 'POST', + url: '/api/v0/add?cid-base=base64', + headers, + payload + }, { ipfs }) + + expect(res).to.have.property('statusCode', 200) + expect(multibase.isEncoded(JSON.parse(res.result).Hash)).to.deep.equal('base64') + }) - it('should add data and return a base64 encoded CID', async () => { - const form = new FormData() - form.append('data', Buffer.from('TEST' + Date.now())) - const headers = form.getHeaders() - - const payload = await streamToPromise(form) - const res = await api.inject({ - method: 'POST', - url: '/api/v0/add?cid-base=base64', - headers, - payload - }) - - expect(res.statusCode).to.equal(200) - expect(multibase.isEncoded(JSON.parse(res.result).Hash)).to.deep.equal('base64') + it('should add data without pinning and return a base64 encoded CID', async () => { + const content = Buffer.from('TEST' + Date.now()) + + ipfs.add.callsFake(async function * (source, opts) { + expect(opts).to.have.property('pin', false) + + const input = await first(source) + expect(await toBuffer(input.content)).to.deep.equal(content) + + yield { + path: cid.toString(), + cid, + size: content.byteLength, + mode: 0o420, + mtime: { + secs: 100, + nsecs: 0 + } + } }) - it('should add data without pinning and return a base64 encoded CID', async () => { - const form = new FormData() - form.append('data', Buffer.from('TEST' + Date.now())) - const headers = form.getHeaders() - - const payload = await streamToPromise(form) - const res = await api.inject({ - method: 'POST', - url: '/api/v0/add?cid-base=base64&pin=false', - headers, - payload - }) - - expect(res.statusCode).to.equal(200) - expect(multibase.isEncoded(JSON.parse(res.result).Hash)).to.deep.equal('base64') - }) + const form = new FormData() + form.append('data', content) + const headers = form.getHeaders() - it('should add data using the trickle importer', async () => { - const form = new FormData() - form.append('data', Buffer.from('TEST\n')) - const headers = form.getHeaders() - - const payload = await streamToPromise(form) - const res = await api.inject({ - method: 'POST', - url: '/api/v0/add?trickle=true&pin=false', - headers, - payload - }) - - expect(res.statusCode).to.equal(200) - expect(JSON.parse(res.result).Hash).to.equal('QmRJTAvvv1UNgCXxK9grf6u2pCT2ZQ2wCwsojpC1sTjkp9') - }) + const payload = await streamToPromise(form) + const res = await http({ + method: 'POST', + url: '/api/v0/add?cid-base=base64&pin=false', + headers, + payload + }, { ipfs }) - it('should add data using the balanced importer', async () => { - const form = new FormData() - form.append('data', Buffer.from('TEST\n')) - const headers = form.getHeaders() - - const payload = await streamToPromise(form) - const res = await api.inject({ - method: 'POST', - url: '/api/v0/add?pin=false', - headers, - payload - }) - - expect(res.statusCode).to.equal(200) - expect(JSON.parse(res.result).Hash).to.equal('Qmdudp5XvJr7KrqK6fQ7m2ACStoRxuwfovNHnY6dAAeUis') - }) + expect(res).to.have.property('statusCode', 200) + expect(multibase.isEncoded(JSON.parse(res.result).Hash)).to.deep.equal('base64') }) - describe('/cat', () => { - it('only accepts POST', () => { - return testHttpMethod('/api/v0/cat') - }) + it('should specify the cid version', () => assertAddArgs('/api/v0/add?cid-version=1', (opts) => opts.cidVersion === 1)) - it('returns 400 for request without argument', async () => { - const res = await api.inject({ - method: 'POST', - url: '/api/v0/cat' - }) + it('should specify raw leaves', () => assertAddArgs('/api/v0/add?raw-leaves=true', (opts) => opts.rawLeaves === true)) - expect(res.statusCode).to.equal(400) - expect(res.result.Message).to.be.a('string') - }) + it('should specify only hash', () => assertAddArgs('/api/v0/add?only-hash=true', (opts) => opts.onlyHash === true)) - it('returns 400 for request with invalid argument', async () => { - const res = await api.inject({ - method: 'POST', - url: '/api/v0/cat?arg=invalid' - }) + it('should specify pin', () => assertAddArgs('/api/v0/add?pin=true', (opts) => opts.pin === true)) - expect(res.statusCode).to.equal(400) - expect(res.result.Message).to.be.a('string') - }) + it('should specify wrap with directory', () => assertAddArgs('/api/v0/add?wrap-with-directory=true', (opts) => opts.wrapWithDirectory === true)) - it('should cat a valid hash', async function () { - this.timeout(30 * 1000) - - const data = Buffer.from('TEST' + Date.now()) - const form = new FormData() - form.append('data', data) - const headers = form.getHeaders() - - const payload = await streamToPromise(form) - let res = await api.inject({ - method: 'POST', - url: '/api/v0/add', - headers, - payload - }) - - expect(res.statusCode).to.equal(200) - const cid = JSON.parse(res.result).Hash - - res = await api.inject({ - method: 'POST', - url: '/api/v0/cat?arg=' + cid - }) - - expect(res.statusCode).to.equal(200) - expect(res.rawPayload).to.deep.equal(data) - expect(res.payload).to.equal(data.toString()) - }) + it('should ignore file import concurrency', () => assertAddArgs('/api/v0/add?file-import-concurrency=5', (opts) => opts.fileImportConcurrency === 1)) + + it('should specify block write concurrency', () => assertAddArgs('/api/v0/add?block-write-concurrency=5', (opts) => opts.blockWriteConcurrency === 5)) + + it('should specify shard split threshold', () => assertAddArgs('/api/v0/add?shard-split-threshold=5', (opts) => opts.shardSplitThreshold === 5)) + + it('should specify chunker', () => assertAddArgs('/api/v0/add?chunker=derp', (opts) => opts.chunker === 'derp')) + + it('should add data using the trickle importer', () => assertAddArgs('/api/v0/add?trickle=true', (opts) => opts.trickle === true)) + + it('should specify preload', () => assertAddArgs('/api/v0/add?preload=false', (opts) => opts.preload === false)) + }) + + describe('/cat', () => { + it('only accepts POST', () => { + return testHttpMethod('/api/v0/cat') }) - describe('/get', () => { - it('only accepts POST', () => { - return testHttpMethod('/api/v0/get') - }) + it('returns 400 for request without argument', async () => { + const res = await http({ + method: 'POST', + url: '/api/v0/cat' + }, { ipfs }) + + expect(res).to.have.property('statusCode', 400) + expect(res).to.have.nested.property('result.Message').that.is.a('string') }) - describe('/ls', () => { - it('only accepts POST', () => { - return testHttpMethod('/api/v0/ls') - }) + it('returns 400 for request with invalid argument', async () => { + const res = await http({ + method: 'POST', + url: '/api/v0/cat?arg=invalid' + }, { ipfs }) - it('should list directory contents and return a base64 encoded CIDs', async () => { - const form = new FormData() - form.append('file', Buffer.from('TEST' + Date.now()), { filename: 'data.txt' }) - const headers = form.getHeaders() - - const payload = await streamToPromise(form) - let res = await api.inject({ - method: 'POST', - url: '/api/v0/add?wrap-with-directory=true', - headers, - payload - }) - expect(res.statusCode).to.equal(200) - - const files = res.result.trim().split('\n').map(r => JSON.parse(r)) - const dir = files[files.length - 1] - - res = await api.inject({ - method: 'POST', - url: '/api/v0/ls?cid-base=base64&arg=' + dir.Hash - }) - expect(res.statusCode).to.equal(200) - res.result.Objects[0].Links.forEach(item => { - expect(multibase.isEncoded(item.Hash)).to.deep.equal('base64') - }) - }) + expect(res).to.have.property('statusCode', 400) + expect(res).to.have.nested.property('result.Message').that.is.a('string') }) - describe('/refs', () => { - it('only accepts POST', () => { - return testHttpMethod('/api/v0/refs') - }) + it('should cat a valid hash', async function () { + const data = Buffer.from('TEST' + Date.now()) - it('should list refs', async () => { - const form = new FormData() - form.append('file', Buffer.from('TEST' + Date.now()), { filename: 'data.txt' }) - const headers = form.getHeaders() - - const payload = await streamToPromise(form) - let res = await api.inject({ - method: 'POST', - url: '/api/v0/add?wrap-with-directory=true', - headers, - payload - }) - expect(res.statusCode).to.equal(200) - - const files = res.result.trim().split('\n').map(r => JSON.parse(r)) - const dir = files[files.length - 1] - - res = await api.inject({ - method: 'POST', - url: '/api/v0/refs?format=&arg=' + dir.Hash - }) - expect(res.statusCode).to.equal(200) - expect(JSON.parse(res.result).Ref).to.equal('data.txt') - }) + ipfs.cat.withArgs(cid.toString()).returns([ + data + ]) + + const res = await http({ + method: 'POST', + url: `/api/v0/cat?arg=${cid}` + }, { ipfs }) + + expect(res).to.have.property('statusCode', 200) + expect(res).to.have.deep.property('rawPayload', data) + expect(res).to.have.property('payload', data.toString()) }) - describe('/refs/local', () => { - it('only accepts POST', () => { - return testHttpMethod('/api/v0/refs/local') + it('should cat a valid hash with an offset', async function () { + const data = Buffer.from('TEST' + Date.now()) + + ipfs.cat.withArgs(cid.toString(), sinon.match({ + offset: 10 + })).returns([ + data + ]) + + const res = await http({ + method: 'POST', + url: `/api/v0/cat?arg=${cid}&offset=10` + }, { ipfs }) + + expect(res).to.have.property('statusCode', 200) + expect(res).to.have.deep.property('rawPayload', data) + expect(res).to.have.property('payload', data.toString()) + }) + + it('should cat a valid hash with a length', async function () { + const data = Buffer.from('TEST' + Date.now()) + + ipfs.cat.withArgs(cid.toString(), sinon.match({ + length: 10 + })).returns([ + data + ]) + + const res = await http({ + method: 'POST', + url: `/api/v0/cat?arg=${cid}&length=10` + }, { ipfs }) + + expect(res).to.have.property('statusCode', 200) + expect(res).to.have.deep.property('rawPayload', data) + expect(res).to.have.property('payload', data.toString()) + }) + }) + + describe('/get', () => { + it('only accepts POST', () => { + return testHttpMethod('/api/v0/get') + }) + }) + + describe('/ls', () => { + it('only accepts POST', () => { + return testHttpMethod('/api/v0/ls') + }) + + it('should list directory contents', async () => { + ipfs.ls.withArgs(cid.toString(), sinon.match({ + recursive: false + })).returns([{ + name: 'link', + cid, + size: 10, + type: 'file', + depth: 1, + mode: 0o420 + }]) + + const res = await http({ + method: 'POST', + url: `/api/v0/ls?arg=${cid}` + }, { ipfs }) + + expect(res).to.have.property('statusCode', 200) + expect(res).to.have.deep.nested.property('result.Objects[0]', { + Hash: cid.toString(), + Links: [{ + Depth: 1, + Hash: cid.toString(), + Mode: '0420', + Name: 'link', + Size: 10, + Type: 2 + }] }) + }) - it('should list local refs', async () => { - const form = new FormData() - form.append('file', Buffer.from('TEST' + Date.now()), { filename: 'data.txt' }) - const headers = form.getHeaders() - - const payload = await streamToPromise(form) - let res = await api.inject({ - method: 'POST', - url: '/api/v0/add?wrap-with-directory=true', - headers, - payload - }) - expect(res.statusCode).to.equal(200) - - res = await api.inject({ - method: 'POST', - url: '/api/v0/refs/local' - }) - expect(res.statusCode).to.equal(200) - const refs = res.result.trim().split('\n').map(JSON.parse).map(r => r.Ref) - expect(refs.length).to.be.gt(0) + // TODO: unskip after switch to v1 CIDs by default + it.skip('should return base64 encoded CIDs', async () => { + ipfs.ls.withArgs(cid.toString(), sinon.match({ + recursive: false + })).returns([]) + + const res = await http({ + method: 'POST', + url: `/api/v0/ls?cid-base=base64&arg=${cid}` + }, { ipfs }) + + expect(res).to.have.property('statusCode', 200) + expect(res).to.have.deep.nested.property('result.Objects[0]', { + Hash: cid.toV1().toString('base64'), + Links: [] }) }) }) -} + + describe('/refs', () => { + it('only accepts POST', () => { + return testHttpMethod('/api/v0/refs') + }) + + it('should list refs', async () => { + ipfs.refs.withArgs(cid.toString()).returns([{ + ref: cid.toString() + }]) + + const res = await http({ + method: 'POST', + url: `/api/v0/refs?format=&arg=${cid}` + }, { ipfs }) + + expect(res).to.have.property('statusCode', 200) + expect(JSON.parse(res.result)).to.have.property('Ref', cid.toString()) + }) + }) + + describe('/refs/local', () => { + it('only accepts POST', () => { + return testHttpMethod('/api/v0/refs/local') + }) + + it('should list local refs', async () => { + ipfs.refs.local.returns([{ + ref: cid.toString() + }]) + + const res = await http({ + method: 'POST', + url: '/api/v0/refs/local' + }, { ipfs }) + + expect(res).to.have.property('statusCode', 200) + expect(JSON.parse(res.result)).to.have.property('Ref', cid.toString()) + }) + }) +}) diff --git a/packages/ipfs/test/http-api/inject/id.js b/packages/ipfs/test/http-api/inject/id.js index 6105a2c766..2fe87e0dab 100644 --- a/packages/ipfs/test/http-api/inject/id.js +++ b/packages/ipfs/test/http-api/inject/id.js @@ -3,39 +3,40 @@ const { expect } = require('interface-ipfs-core/src/utils/mocha') const testHttpMethod = require('../../utils/test-http-method') +const http = require('../../utils/http') +const sinon = require('sinon') -module.exports = (http) => { - describe('/id', () => { - let api +describe('/id', () => { + let ipfs - before(() => { - api = http.api._httpApi._apiServers[0] - }) + beforeEach(() => { + ipfs = { + id: sinon.stub() + } + }) + + it('only accepts POST', () => { + return testHttpMethod('/api/v0/id') + }) - it('only accepts POST', () => { - return testHttpMethod('/api/v0/id') + it('get the id', async () => { + ipfs.id.returns({ + id: 'id', + publicKey: 'publicKey', + addresses: 'addresses', + agentVersion: 'agentVersion', + protocolVersion: 'protocolVersion' }) - it('get the id', async () => { - const res = await api.inject({ - method: 'POST', - url: '/api/v0/id' - }) + const res = await http({ + method: 'POST', + url: '/api/v0/id' + }, { ipfs }) - expect(res.result.ID).to.equal(idResult.ID) - expect(res.result.PublicKey).to.equal(idResult.PublicKey) - const agentComponents = res.result.AgentVersion.split('/') - expect(agentComponents).lengthOf.above(1) - expect(agentComponents[0]).to.equal(idResult.AgentVersion) - expect(res.result.ProtocolVersion).to.equal(idResult.ProtocolVersion) - }) + expect(res).to.have.nested.property('result.ID', 'id') + expect(res).to.have.nested.property('result.PublicKey', 'publicKey') + expect(res).to.have.nested.property('result.Addresses', 'addresses') + expect(res).to.have.nested.property('result.AgentVersion', 'agentVersion') + expect(res).to.have.nested.property('result.ProtocolVersion', 'protocolVersion') }) -} - -const idResult = { - ID: 'QmTuh8pVDCz5kbShrK8MJsJgTycCcGwZm8hQd8SxdbYmby', - PublicKey: 'CAASpgIwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQCiyLgGRpuGiorm6FzvBbrTU60e6iPMmwXL9mXyGitepQyeN7XF8e6cooFeJI/NIyvbmpa7rHCDzTWP+6ebIMOXjUjQDAgaYdHywKbAXi2cgh96yuTN+cfPJ0IVA1/4Xsn/mnaMmSNDxqnK3fExEDxZizL9iI7KQCGOHociwjNj2cqaz+4ldTQ6QBbqa8nBMbulUNtSzwihQHTHNVwhuYFGPXIIK8UhM1VR20HcCbX+TZ9RpBWLIGZgjJl2ClW7wLW1OAb55I/9CK6AmfOriVYSBxZSFi2jiPCGQmuzfiqEke6/hSZtxe8DRo8ELOQ9K2P3L27H2az2atis2FoqVY2LAgMBAAE=', - Addresses: ['/ip4/0.0.0.0/tcp/0'], - AgentVersion: 'js-ipfs', - ProtocolVersion: '9000' -} +}) diff --git a/packages/ipfs/test/http-api/inject/key.js b/packages/ipfs/test/http-api/inject/key.js new file mode 100644 index 0000000000..16f6e19dc2 --- /dev/null +++ b/packages/ipfs/test/http-api/inject/key.js @@ -0,0 +1,176 @@ +/* eslint max-nested-callbacks: ["error", 8] */ +/* eslint-env mocha */ +'use strict' + +const { expect } = require('interface-ipfs-core/src/utils/mocha') +const testHttpMethod = require('../../utils/test-http-method') +const http = require('../../utils/http') +const sinon = require('sinon') + +describe('/key', function () { + let ipfs + + beforeEach(() => { + ipfs = { + key: { + list: sinon.stub(), + rm: sinon.stub(), + rename: sinon.stub(), + gen: sinon.stub(), + export: sinon.stub(), + import: sinon.stub() + } + } + }) + + describe('/list', () => { + it('only accepts POST', () => { + return testHttpMethod('/api/v0/key/list') + }) + + it('should list keys', async () => { + ipfs.key.list.returns([{ + name: 'name', + id: 'id' + }]) + + const res = await http({ + method: 'POST', + url: '/api/v0/key/list' + }, { ipfs }) + + expect(res).to.have.property('statusCode', 200) + expect(res).to.have.nested.property('result.Keys[0].Name', 'name') + expect(res).to.have.nested.property('result.Keys[0].Id', 'id') + }) + }) + + describe('/gen', () => { + it('only accepts POST', () => { + return testHttpMethod('/api/v0/key/gen') + }) + + it('should generate a key', async () => { + const name = 'name' + const type = 'type' + const size = 10 + + ipfs.key.gen.withArgs(name, sinon.match({ + type, + size + })).returns({ + name: 'name', + id: 'id' + }) + + const res = await http({ + method: 'POST', + url: `/api/v0/key/gen?arg=${name}&type=${type}&size=${size}` + }, { ipfs }) + + expect(res).to.have.property('statusCode', 200) + expect(res).to.have.nested.property('result.Name', name) + expect(res).to.have.nested.property('result.Id', 'id') + }) + }) + + describe('/rm', () => { + it('only accepts POST', () => { + return testHttpMethod('/api/v0/key/rm') + }) + + it('should remove a key', async () => { + const name = 'name' + + ipfs.key.rm.withArgs(name).returns({ + name: 'name', + id: 'id' + }) + + const res = await http({ + method: 'POST', + url: `/api/v0/key/rm?arg=${name}` + }, { ipfs }) + + expect(res).to.have.property('statusCode', 200) + expect(res).to.have.nested.property('result.Keys[0].Name', 'name') + expect(res).to.have.nested.property('result.Keys[0].Id', 'id') + }) + }) + + describe('/rename', () => { + it('only accepts POST', () => { + return testHttpMethod('/api/v0/key/rename') + }) + + it('should rename a key', async () => { + const oldName = 'oldName' + const newName = 'newName' + + ipfs.key.rename.withArgs(oldName, newName).returns({ + was: oldName, + now: newName, + id: 'id', + overwrite: 'overwrite' + }) + + const res = await http({ + method: 'POST', + url: `/api/v0/key/rename?arg=${oldName}&arg=${newName}` + }, { ipfs }) + + expect(res).to.have.property('statusCode', 200) + expect(res).to.have.nested.property('result.Was', oldName) + expect(res).to.have.nested.property('result.Now', newName) + expect(res).to.have.nested.property('result.Id', 'id') + expect(res).to.have.nested.property('result.Overwrite', 'overwrite') + }) + }) + + describe('/export', () => { + it('only accepts POST', () => { + return testHttpMethod('/api/v0/key/export') + }) + + it('should export a key', async () => { + const name = 'name' + const password = 'password' + + ipfs.key.export.withArgs(name, password).returns('pem') + + const res = await http({ + method: 'POST', + url: `/api/v0/key/export?arg=${name}&password=${password}` + }, { ipfs }) + + expect(res).to.have.property('statusCode', 200) + expect(res).to.have.property('result', 'pem') + }) + }) + + describe('/import', () => { + it('only accepts POST', () => { + return testHttpMethod('/api/v0/key/import') + }) + + it('should import a key', async () => { + const name = 'name' + const pem = 'pem' + const password = 'password' + + ipfs.key.import.withArgs(name, pem, password).returns({ + name, + id: 'id' + }) + + const res = await http({ + method: 'POST', + url: `/api/v0/key/import?arg=${name}&pem=${pem}&password=${password}` + }, { ipfs }) + + expect(res).to.have.property('statusCode', 200) + expect(res).to.have.nested.property('result.Name', name) + expect(res).to.have.nested.property('result.Id', 'id') + }) + }) +}) diff --git a/packages/ipfs/test/http-api/inject/name.js b/packages/ipfs/test/http-api/inject/name.js index 0855e39a11..e3558cdf9a 100644 --- a/packages/ipfs/test/http-api/inject/name.js +++ b/packages/ipfs/test/http-api/inject/name.js @@ -4,86 +4,148 @@ const CID = require('cids') const { expect } = require('interface-ipfs-core/src/utils/mocha') -const checkAll = (bits) => string => bits.every(bit => string.includes(bit)) const testHttpMethod = require('../../utils/test-http-method') +const http = require('../../utils/http') +const sinon = require('sinon') + +describe('/name', function () { + const cid = new CID('QmbndGRXYRyfU41TUvc52gMrwq87JJg18QsDPcCeaMcM61') + let ipfs + + beforeEach(() => { + ipfs = { + name: { + resolve: sinon.stub(), + publish: sinon.stub(), + pubsub: { + state: sinon.stub(), + subs: sinon.stub(), + cancel: sinon.stub() + } + } + } + }) + + describe('/publish', () => { + it('only accepts POST', () => { + return testHttpMethod(`/api/v0/name/publish?arg=${cid}&resolve=false`) + }) + + it('should publish a record', async () => { + const resolve = true + const lifetime = '24h' + const ttl = 'ttl' + const key = 'key' + const allowOffline = true + const name = 'name' + + ipfs.name.publish.withArgs(cid.toString(), sinon.match({ + resolve, + lifetime, + ttl, + key, + allowOffline + })).returns({ + name, + value: cid.toString() + }) + + const res = await http({ + method: 'POST', + url: `/api/v0/name/publish?arg=${cid}&resolve=${resolve}&lifetime=${lifetime}&ttl=${ttl}&key=${key}&allow-offline=${allowOffline}` + }, { ipfs }) -module.exports = (http) => { - describe('/name', function () { - const cid = 'QmbndGRXYRyfU41TUvc52gMrwq87JJg18QsDPcCeaMcM61' - let api + expect(res).to.have.property('statusCode', 200) + expect(res).to.have.nested.property('result.Name', name) + expect(res).to.have.nested.property('result.Value', cid.toString()) + }) + }) - before(() => { - api = http.api._httpApi._apiServers[0] + describe('/resolve', () => { + it('only accepts POST', () => { + return testHttpMethod('/api/v0/name/resolve') }) - describe('/publish', () => { + it('should resolve a record', async () => { + const nocache = true + const recursive = true + + ipfs.name.resolve.withArgs(cid.toString(), sinon.match({ + nocache, + recursive + })).returns([ + cid.toString() + ]) + + const res = await http({ + method: 'POST', + url: `/api/v0/name/resolve?arg=${cid}&nocache=${nocache}&recursive=${recursive}` + }, { ipfs }) + + expect(res).to.exist() + expect(res).to.have.nested.property('result.Path', cid.toString()) + }) + }) + + describe('/pubsub', () => { + describe('/pubsub/state', () => { it('only accepts POST', () => { - return testHttpMethod(`/api/v0/name/publish?arg=${cid}&resolve=false`) + return testHttpMethod('/api/v0/name/pubsub/state') }) - it('should publish a record', async function () { - this.timeout(80 * 1000) + it('should return enabled state', async () => { + ipfs.name.pubsub.state.returns({ + enabled: true + }) - const res = await api.inject({ + const res = await http({ method: 'POST', - url: `/api/v0/name/publish?arg=${cid}&resolve=false` - }) + url: '/api/v0/name/pubsub/state' + }, { ipfs }) expect(res).to.exist() - expect(res.result.Value).to.equal(`/ipfs/${cid}`) + expect(res).to.have.nested.property('result.Enabled', true) }) }) - describe('/resolve', () => { + describe('/pubsub/subs', () => { it('only accepts POST', () => { - return testHttpMethod('/api/v0/name/resolve') + return testHttpMethod('/api/v0/name/pubsub/subs') }) - it('should publish and resolve a record', async function () { - this.timeout(160 * 1000) + it('should return subscriptions', async () => { + ipfs.name.pubsub.subs.returns('value') - let res = await api.inject({ + const res = await http({ method: 'POST', - url: `/api/v0/name/publish?arg=${cid}&resolve=false` - }) - - expect(res).to.exist() - expect(res.result.Value).to.equal(`/ipfs/${cid}`) - - res = await api.inject({ - method: 'POST', - url: '/api/v0/name/resolve' - }) + url: '/api/v0/name/pubsub/subs' + }, { ipfs }) expect(res).to.exist() - expect(res.result.Path).to.satisfy(checkAll([`/ipfs/${cid}`])) + expect(res).to.have.nested.property('result.Strings', 'value') }) + }) - it('should publish and resolve a record with explicit CIDv1 in Base32', async function () { - this.timeout(160 * 1000) + describe('/pubsub/cancel', () => { + it('only accepts POST', () => { + return testHttpMethod('/api/v0/name/pubsub/cancel') + }) - // ensure PeerID is represented as CIDv1 in Base32 - const { id } = await http.api._ipfs.id() - let cidv1 = new CID(id) - if (cidv1.version === 0) cidv1 = cidv1.toV1() // future-proofing - const peerIdAsCidv1b32 = cidv1.toString('base32') + it('should cancel subscription', async () => { + const name = 'name' - let res = await api.inject({ - method: 'POST', - url: `/api/v0/name/publish?arg=${cid}&resolve=false` + ipfs.name.pubsub.cancel.withArgs(name).returns({ + canceled: true }) - expect(res).to.exist() - expect(res.result.Value).to.equal(`/ipfs/${cid}`) - - res = await api.inject({ + const res = await http({ method: 'POST', - url: `/api/v0/name/resolve?arg=${peerIdAsCidv1b32}` - }) + url: `/api/v0/name/pubsub/cancel?arg=${name}` + }, { ipfs }) expect(res).to.exist() - expect(res.result.Path).to.satisfy(checkAll([`/ipfs/${cid}`])) + expect(res).to.have.nested.property('result.Canceled', true) }) }) }) -} +}) diff --git a/packages/ipfs/test/http-api/inject/object.js b/packages/ipfs/test/http-api/inject/object.js index da400ae0da..194668b202 100644 --- a/packages/ipfs/test/http-api/inject/object.js +++ b/packages/ipfs/test/http-api/inject/object.js @@ -8,931 +8,862 @@ const FormData = require('form-data') const streamToPromise = require('stream-to-promise') const multibase = require('multibase') const testHttpMethod = require('../../utils/test-http-method') +const http = require('../../utils/http') +const sinon = require('sinon') +const CID = require('cids') +const UnixFS = require('ipfs-unixfs') +const { + DAGNode, + DAGLink +} = require('ipld-dag-pb') + +describe('/object', () => { + const cid = new CID('QmdfTbBqBPQ7VNxZEYEj14VmRuZBkqFbiwReogJgS1zR1n') + const cid2 = new CID('QmdfTbBqBPQ7VNxZEYEj14VmRuZBkqFbiwReogJgS1zR1a') + const unixfs = new UnixFS({ + type: 'file' + }) + const fileNode = new DAGNode(unixfs.marshal(), [ + new DAGLink('', 5, cid) + ]) + const emptyDirectoryNode = new DAGNode(new UnixFS({ + type: 'directory' + }).marshal()) + let ipfs + + beforeEach(() => { + ipfs = { + object: { + new: sinon.stub(), + get: sinon.stub(), + put: sinon.stub(), + stat: sinon.stub(), + data: sinon.stub(), + links: sinon.stub(), + patch: { + appendData: sinon.stub(), + setData: sinon.stub(), + addLink: sinon.stub(), + rmLink: sinon.stub() + } + } + } + }) -module.exports = (http) => { - describe('/object', () => { - let api - - before('api', () => { - api = http.api._httpApi._apiServers[0] + describe('/new', () => { + it('only accepts POST', () => { + return testHttpMethod('/api/v0/object/new') }) - describe('/new', () => { - it('only accepts POST', () => { - return testHttpMethod('/api/v0/object/new') - }) - - it('returns value', async () => { - const res = await api.inject({ - method: 'POST', - url: '/api/v0/object/new' - }) - - expect(res.statusCode).to.equal(200) - expect(res.result.Hash) - .to.equal('QmdfTbBqBPQ7VNxZEYEj14VmRuZBkqFbiwReogJgS1zR1n') - expect(res.result.Links).to.be.eql([]) - }) - - // TODO: unskip after switch to v1 CIDs by default - it.skip('should create a new object and return a base64 encoded CID', async () => { - const res = await api.inject({ - method: 'POST', - url: '/api/v0/object/new?cid-base=base64' - }) + it('returns value', async () => { + ipfs.object.new.withArgs(undefined).returns(cid) + ipfs.object.get.withArgs(cid).returns(emptyDirectoryNode) - expect(res.statusCode).to.equal(200) - expect(multibase.isEncoded(res.result.Hash)).to.deep.equal('base64') - }) - - it('should not create a new object for invalid cid-base option', async () => { - const res = await api.inject({ - method: 'POST', - url: '/api/v0/object/new?cid-base=invalid' - }) + const res = await http({ + method: 'POST', + url: '/api/v0/object/new' + }, { ipfs }) - expect(res.statusCode).to.equal(400) - expect(res.result.Message).to.include('Invalid request query input') - }) + expect(res).to.have.property('statusCode', 200) + expect(res).to.have.nested.property('result.Hash', cid.toString()) + expect(res).to.have.nested.property('result.Links').that.is.empty() }) - describe('/get', () => { - it('only accepts POST', () => { - return testHttpMethod('/api/v0/object/get') - }) + it('should create an object with the passed template', async () => { + const template = 'template' - it('returns 400 for request without argument', async () => { - const res = await api.inject({ - method: 'POST', - url: '/api/v0/object/get' - }) + ipfs.object.new.withArgs(template).returns(cid) + ipfs.object.get.withArgs(cid).returns(emptyDirectoryNode) - expect(res.statusCode).to.equal(400) - expect(res.result.Message).to.be.a('string') - }) + const res = await http({ + method: 'POST', + url: `/api/v0/object/new?arg=${template}` + }, { ipfs }) - it('returns 400 for request with invalid argument', async () => { - const res = await api.inject({ - method: 'POST', - url: '/api/v0/object/get?arg=invalid' - }) - - expect(res.statusCode).to.equal(400) - expect(res.result.Code).to.equal(1) - expect(res.result.Message).to.be.a('string') - }) + expect(res).to.have.property('statusCode', 200) + expect(res).to.have.nested.property('result.Hash', cid.toString()) + expect(res).to.have.nested.property('result.Links').that.is.empty() + }) - it('returns value', async () => { - const res = await api.inject({ - method: 'POST', - url: '/api/v0/object/get?arg=QmdfTbBqBPQ7VNxZEYEj14VmRuZBkqFbiwReogJgS1zR1n' - }) + // TODO: unskip after switch to v1 CIDs by default + it.skip('should create a new object and return a base64 encoded CID', async () => { + ipfs.object.new.withArgs(undefined).returns(cid) + ipfs.object.get.withArgs(cid).returns(emptyDirectoryNode) - expect(res.statusCode).to.equal(200) - expect(res.result.Links).to.eql([]) - expect(res.result.Data).to.be.empty() - }) + const res = await http({ + method: 'POST', + url: '/api/v0/object/new?cid-base=base64' + }, { ipfs }) - // TODO: unskip after switch to v1 CIDs by default - it.skip('should get object and return a base64 encoded CID', async () => { - let res = await api.inject({ - method: 'POST', - url: '/api/v0/object/new' - }) - - expect(res.statusCode).to.equal(200) + expect(res).to.have.property('statusCode', 200) + expect(multibase.isEncoded(res.result.Hash)).to.deep.equal('base64') + }) - res = await api.inject({ - method: 'POST', - url: '/api/v0/object/get?cid-base=base64&arg=' + res.result.Hash - }) + it('should not create a new object for invalid cid-base option', async () => { + const res = await http({ + method: 'POST', + url: '/api/v0/object/new?cid-base=invalid' + }, { ipfs }) - expect(res.statusCode).to.equal(200) - expect(multibase.isEncoded(res.result.Hash)).to.deep.equal('base64') - }) + expect(res).to.have.property('statusCode', 400) + expect(res).to.have.nested.property('result.Message').that.includes('Invalid request query input') + }) + }) - it('should not get an object for invalid cid-base option', async () => { - let res = await api.inject({ - method: 'POST', - url: '/api/v0/object/new' - }) - expect(res.statusCode).to.equal(200) + describe('/get', () => { + it('only accepts POST', () => { + return testHttpMethod('/api/v0/object/get') + }) - res = await api.inject({ - method: 'POST', - url: '/api/v0/object/get?cid-base=invalid&arg=' + res.result.Hash - }) + it('returns 400 for request without argument', async () => { + const res = await http({ + method: 'POST', + url: '/api/v0/object/get' + }, { ipfs }) - expect(res.statusCode).to.equal(400) - expect(res.result.Message).to.include('Invalid request query input') - }) + expect(res).to.have.property('statusCode', 400) + expect(res).to.have.nested.property('result.Message').that.is.a('string') }) - describe('/put', () => { - it('only accepts POST', () => { - return testHttpMethod('/api/v0/object/put') - }) + it('returns 400 for request with invalid argument', async () => { + const res = await http({ + method: 'POST', + url: '/api/v0/object/get?arg=invalid' + }, { ipfs }) - it('returns 400 if no node is provided', async () => { - const form = new FormData() - const headers = form.getHeaders() + expect(res).to.have.property('statusCode', 400) + expect(res).to.have.nested.property('result.Code', 1) + expect(res).to.have.nested.property('result.Message').that.is.a('string') + }) - const payload = await streamToPromise(form) - const res = await api.inject({ - method: 'POST', - url: '/api/v0/object/put', - headers, - payload - }) + it('returns value', async () => { + ipfs.object.get.withArgs(cid).returns(emptyDirectoryNode) - expect(res.statusCode).to.equal(400) - }) + const res = await http({ + method: 'POST', + url: `/api/v0/object/get?arg=${cid}` + }, { ipfs }) - it('returns 400 if the node is invalid', async () => { - const form = new FormData() - const filePath = 'test/fixtures/test-data/badnode.json' - form.append('file', fs.createReadStream(filePath)) - const headers = form.getHeaders() - - const payload = await streamToPromise(form) - const res = await api.inject({ - method: 'POST', - url: '/api/v0/object/put', - headers, - payload - }) - - expect(res.statusCode).to.equal(400) - }) + expect(res).to.have.property('statusCode', 200) + expect(res).to.have.nested.property('result.Links').that.is.empty() + expect(res).to.have.nested.property('result.Data', emptyDirectoryNode.Data.toString()) + }) - it('updates value', async () => { - const form = new FormData() - const filePath = 'test/fixtures/test-data/node.json' - form.append('data', fs.createReadStream(filePath)) - const headers = form.getHeaders() - - const expectedResult = { - Data: Buffer.from('another'), - Hash: 'QmZZmY4KCu9r3e7M2Pcn46Fc5qbn6NpzaAGaYb22kbfTqm', - Links: [{ - Name: 'some link', - Hash: 'QmXg9Pp2ytZ14xgmQjYEiHjVjMFXzCVVEcRTWJBmLgR39V', - Size: 8 - }], - Size: 68 - } + // TODO: unskip after switch to v1 CIDs by default + it.skip('should get object and return a base64 encoded CID', async () => { + ipfs.object.get.withArgs(cid).returns(emptyDirectoryNode) - const payload = await streamToPromise(form) - const res = await api.inject({ - method: 'POST', - url: '/api/v0/object/put', - headers, - payload - }) + const res = await http({ + method: 'POST', + url: `/api/v0/object/get?cid-base=base64&arg=${cid}` + }, { ipfs }) - expect(res.statusCode).to.equal(200) - expect(res.result).to.eql(expectedResult) - }) + expect(res).to.have.property('statusCode', 200) + expect(multibase.isEncoded(res.result.Hash)).to.deep.equal('base64') + }) - // TODO: unskip after switch to v1 CIDs by default - it.skip('should put data and return a base64 encoded CID', async () => { - const form = new FormData() - form.append('file', JSON.stringify({ Data: 'TEST' + Date.now(), Links: [] }), { filename: 'node.json' }) - const headers = form.getHeaders() - - const payload = await streamToPromise(form) - const res = await api.inject({ - method: 'POST', - url: '/api/v0/object/put?cid-base=base64', - headers, - payload - }) - - expect(res.statusCode).to.equal(200) - expect(multibase.isEncoded(res.result.Hash)).to.deep.equal('base64') - }) + it('should not get an object for invalid cid-base option', async () => { + const res = await http({ + method: 'POST', + url: `/api/v0/object/get?cid-base=invalid&arg=${cid}` + }, { ipfs }) - it('should not put data for invalid cid-base option', async () => { - const form = new FormData() - form.append('file', JSON.stringify({ Data: 'TEST' + Date.now(), Links: [] }), { filename: 'node.json' }) - const headers = form.getHeaders() - - const payload = await streamToPromise(form) - const res = await api.inject({ - method: 'POST', - url: '/api/v0/object/put?cid-base=invalid', - headers, - payload - }) - - expect(res.statusCode).to.equal(400) - expect(res.result.Message).to.include('Invalid request query input') - }) + expect(res).to.have.property('statusCode', 400) + expect(res).to.have.nested.property('result.Message').that.includes('Invalid request query input') }) + }) - describe('/stat', () => { - it('only accepts POST', () => { - return testHttpMethod('/api/v0/object/stat') - }) + describe('/put', () => { + it('only accepts POST', () => { + return testHttpMethod('/api/v0/object/put') + }) - it('returns 400 for request without argument', async () => { - const res = await api.inject({ - method: 'POST', - url: '/api/v0/object/stat' - }) + it('returns 400 if no node is provided', async () => { + const form = new FormData() + const headers = form.getHeaders() - expect(res.statusCode).to.equal(400) - expect(res.result.Message).to.be.a('string') - }) + const payload = await streamToPromise(form) + const res = await http({ + method: 'POST', + url: '/api/v0/object/put', + headers, + payload + }, { ipfs }) - it('returns 400 for request with invalid argument', async () => { - const res = await api.inject({ - method: 'POST', - url: '/api/v0/object/stat?arg=invalid' - }) + expect(res).to.have.property('statusCode', 400) + }) - expect(res.statusCode).to.equal(400) - expect(res.result.Code).to.equal(1) - expect(res.result.Message).to.be.a('string') - }) + it('returns 400 if the node is invalid', async () => { + const form = new FormData() + const filePath = 'test/fixtures/test-data/badnode.json' + form.append('file', fs.createReadStream(filePath)) + const headers = form.getHeaders() + + const payload = await streamToPromise(form) + const res = await http({ + method: 'POST', + url: '/api/v0/object/put', + headers, + payload + }, { ipfs }) + + expect(res).to.have.property('statusCode', 400) + }) - it('returns value', async () => { - const res = await api.inject({ - method: 'POST', - url: '/api/v0/object/stat?arg=QmZZmY4KCu9r3e7M2Pcn46Fc5qbn6NpzaAGaYb22kbfTqm' - }) - - 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(60 - 7) - expect(res.result.DataSize).to.equal(7) - expect(res.result.CumulativeSize).to.equal(60 + 8) - }) + it('updates value', async () => { + ipfs.object.put.returns(cid) + + const form = new FormData() + const filePath = 'test/fixtures/test-data/node.json' + form.append('data', fs.createReadStream(filePath)) + const headers = form.getHeaders() + + const expectedResult = { + Data: Buffer.from('another'), + Hash: cid.toString(), + Links: [{ + Name: 'some link', + Hash: 'QmXg9Pp2ytZ14xgmQjYEiHjVjMFXzCVVEcRTWJBmLgR39V', + Size: 8 + }], + Size: 68 + } + + const payload = await streamToPromise(form) + const res = await http({ + method: 'POST', + url: '/api/v0/object/put', + headers, + payload + }, { ipfs }) + + expect(res).to.have.property('statusCode', 200) + expect(res).to.have.deep.property('result', expectedResult) + }) - // TODO: unskip after switch to v1 CIDs by default - it.skip('should stat object and return a base64 encoded CID', async () => { - let res = await api.inject({ - method: 'POST', - url: '/api/v0/object/new' - }) + // TODO: unskip after switch to v1 CIDs by default + it.skip('should put data and return a base64 encoded CID', async () => { + const form = new FormData() + form.append('file', JSON.stringify({ Data: 'TEST' + Date.now(), Links: [] }), { filename: 'node.json' }) + const headers = form.getHeaders() + + const payload = await streamToPromise(form) + const res = await http({ + method: 'POST', + url: '/api/v0/object/put?cid-base=base64', + headers, + payload + }, { ipfs }) + + expect(res).to.have.property('statusCode', 200) + expect(multibase.isEncoded(res.result.Hash)).to.deep.equal('base64') + }) - expect(res.statusCode).to.equal(200) + it('should not put data for invalid cid-base option', async () => { + const form = new FormData() + form.append('file', JSON.stringify({ Data: 'TEST' + Date.now(), Links: [] }), { filename: 'node.json' }) + const headers = form.getHeaders() + + const payload = await streamToPromise(form) + const res = await http({ + method: 'POST', + url: '/api/v0/object/put?cid-base=invalid', + headers, + payload + }, { ipfs }) + + expect(res).to.have.property('statusCode', 400) + expect(res).to.have.nested.property('result.Message').that.includes('Invalid request query input') + }) + }) - res = await api.inject({ - method: 'POST', - url: '/api/v0/object/stat?cid-base=base64&arg=' + res.result.Hash - }) + describe('/stat', () => { + it('only accepts POST', () => { + return testHttpMethod('/api/v0/object/stat') + }) - expect(res.statusCode).to.equal(200) - expect(multibase.isEncoded(res.result.Hash)).to.deep.equal('base64') - }) + it('returns 400 for request without argument', async () => { + const res = await http({ + method: 'POST', + url: '/api/v0/object/stat' + }, { ipfs }) - it('should not stat object for invalid cid-base option', async () => { - let res = await api.inject({ - method: 'POST', - url: '/api/v0/object/new' - }) + expect(res).to.have.property('statusCode', 400) + expect(res).to.have.nested.property('result.Message').that.is.a('string') + }) - expect(res.statusCode).to.equal(200) + it('returns 400 for request with invalid argument', async () => { + const res = await http({ + method: 'POST', + url: '/api/v0/object/stat?arg=invalid' + }, { ipfs }) - res = await api.inject({ - method: 'POST', - url: '/api/v0/object/stat?cid-base=invalid&arg=' + res.result.Hash - }) + expect(res).to.have.property('statusCode', 400) + expect(res).to.have.nested.property('result.Code', 1) + expect(res).to.have.nested.property('result.Message').that.is.a('string') + }) - expect(res.statusCode).to.equal(400) - expect(res.result.Message).to.include('Invalid request query input') - }) + it('returns value', async () => { + ipfs.object.stat.withArgs(cid).returns({ + Hash: cid.toString(), + NumLinks: 'NumLinks', + BlockSize: 'BlockSize', + LinksSize: 'LinksSize', + DataSize: 'DataSize', + CumulativeSize: 'CumulativeSize' + }) + + const res = await http({ + method: 'POST', + url: `/api/v0/object/stat?arg=${cid}` + }, { ipfs }) + + expect(res).to.have.property('statusCode', 200) + expect(res).to.have.nested.property('result.Hash', cid.toString()) + expect(res).to.have.nested.property('result.NumLinks', 'NumLinks') + expect(res).to.have.nested.property('result.BlockSize', 'BlockSize') + expect(res).to.have.nested.property('result.LinksSize', 'LinksSize') + expect(res).to.have.nested.property('result.DataSize', 'DataSize') + expect(res).to.have.nested.property('result.CumulativeSize', 'CumulativeSize') }) - describe('/data', () => { - it('only accepts POST', () => { - return testHttpMethod('/api/v0/object/data') - }) + // TODO: unskip after switch to v1 CIDs by default + it.skip('should stat object and return a base64 encoded CID', async () => { + let res = await http({ + method: 'POST', + url: '/api/v0/object/new' + }, { ipfs }) - it('returns 400 for request without argument', async () => { - const res = await api.inject({ - method: 'POST', - url: '/api/v0/object/data' - }) + expect(res).to.have.property('statusCode', 200) - expect(res.statusCode).to.equal(400) - expect(res.result.Message).to.be.a('string') - }) + res = await http({ + method: 'POST', + url: '/api/v0/object/stat?cid-base=base64&arg=' + res.result.Hash + }, { ipfs }) - it('returns 400 for request with invalid argument', async () => { - const res = await api.inject({ - method: 'POST', - url: '/api/v0/object/data?arg=invalid' - }) + expect(res).to.have.property('statusCode', 200) + expect(multibase.isEncoded(res.result.Hash)).to.deep.equal('base64') + }) - expect(res.statusCode).to.equal(400) - expect(res.result.Code).to.equal(1) - expect(res.result.Message).to.be.a('string') - }) + it('should not stat object for invalid cid-base option', async () => { + const res = await http({ + method: 'POST', + url: `/api/v0/object/stat?cid-base=invalid&arg=${cid}` + }, { ipfs }) - it('returns value', async () => { - const res = await api.inject({ - method: 'POST', - url: '/api/v0/object/data?arg=QmZZmY4KCu9r3e7M2Pcn46Fc5qbn6NpzaAGaYb22kbfTqm' - }) + expect(res).to.have.property('statusCode', 400) + expect(res).to.have.nested.property('result.Message').that.includes('Invalid request query input') + }) + }) - expect(res.statusCode).to.equal(200) - expect(res.result).to.equal('another') - }) + describe('/data', () => { + it('only accepts POST', () => { + return testHttpMethod('/api/v0/object/data') }) - describe('/links', () => { - it('only accepts POST', () => { - return testHttpMethod('/api/v0/object/links') - }) + it('returns 400 for request without argument', async () => { + const res = await http({ + method: 'POST', + url: '/api/v0/object/data' + }, { ipfs }) - it('returns 400 for request without argument', async () => { - const res = await api.inject({ - method: 'POST', - url: '/api/v0/object/links' - }) + expect(res).to.have.property('statusCode', 400) + expect(res).to.have.nested.property('result.Message').that.is.a('string') + }) - expect(res.statusCode).to.equal(400) - expect(res.result.Message).to.be.a('string') - }) + it('returns 400 for request with invalid argument', async () => { + const res = await http({ + method: 'POST', + url: '/api/v0/object/data?arg=invalid' + }, { ipfs }) - it('returns 400 for request with invalid argument', async () => { - const res = await api.inject({ - method: 'POST', - url: '/api/v0/object/links?arg=invalid' - }) + expect(res).to.have.property('statusCode', 400) + expect(res).to.have.nested.property('result.Code', 1) + expect(res).to.have.nested.property('result.Message').that.is.a('string') + }) - expect(res.statusCode).to.equal(400) - expect(res.result.Code).to.equal(1) - expect(res.result.Message).to.be.a('string') - }) + it('returns value', async () => { + ipfs.object.data.withArgs(cid).returns(emptyDirectoryNode.Data) - it('returns value', async () => { - const expectedResult = { - Hash: 'QmZZmY4KCu9r3e7M2Pcn46Fc5qbn6NpzaAGaYb22kbfTqm', - Links: [ - { Name: 'some link', Hash: 'QmXg9Pp2ytZ14xgmQjYEiHjVjMFXzCVVEcRTWJBmLgR39V', Size: 8 } - ] - } + const res = await http({ + method: 'POST', + url: `/api/v0/object/data?arg=${cid}` + }, { ipfs }) - const res = await api.inject({ - method: 'POST', - url: '/api/v0/object/links?arg=QmZZmY4KCu9r3e7M2Pcn46Fc5qbn6NpzaAGaYb22kbfTqm' - }) + expect(res).to.have.property('statusCode', 200) + expect(res).to.have.property('result', emptyDirectoryNode.Data.toString()) + }) + }) - expect(res.statusCode).to.equal(200) - expect(res.result).to.deep.equal(expectedResult) - }) + describe('/links', () => { + it('only accepts POST', () => { + return testHttpMethod('/api/v0/object/links') + }) - // TODO: unskip after switch to v1 CIDs by default - it.skip('should list object links and return a base64 encoded CID', async () => { - let res = await api.inject({ - method: 'POST', - url: '/api/v0/object/new' - }) - - expect(res.statusCode).to.equal(200) - const linkHash = res.result.Hash - - const form = new FormData() - form.append('file', JSON.stringify({ - Data: 'TEST' + Date.now(), - Links: [{ Name: 'emptyNode', Hash: linkHash, Size: 8 }] - }), { filename: 'node.json' }) - const headers = form.getHeaders() - - const payload = await streamToPromise(form) - res = await api.inject({ - method: 'POST', - url: '/api/v0/object/put', - headers, - payload - }) - - expect(res.statusCode).to.equal(200) - const hash = res.result.Hash - - res = await api.inject({ - method: 'POST', - url: '/api/v0/object/links?cid-base=base64&arg=' + hash - }) - - expect(res.statusCode).to.equal(200) - expect(multibase.isEncoded(res.result.Hash)).to.deep.equal('base64') - expect(res.result.Links).to.have.length(1) - expect(multibase.isEncoded(res.result.Links[0].Hash)).to.deep.equal('base64') - }) + it('returns 400 for request without argument', async () => { + const res = await http({ + method: 'POST', + url: '/api/v0/object/links' + }, { ipfs }) - it('should not list object links for invalid cid-base option', async () => { - let res = await api.inject({ - method: 'POST', - url: '/api/v0/object/new' - }) - - expect(res.statusCode).to.equal(200) - const linkHash = res.result.Hash - - const form = new FormData() - form.append('file', JSON.stringify({ - Data: 'TEST' + Date.now(), - Links: [{ Name: 'emptyNode', Hash: linkHash, Size: 8 }] - }), { filename: 'node.json' }) - const headers = form.getHeaders() - - const payload = await streamToPromise(form) - res = await api.inject({ - method: 'POST', - url: '/api/v0/object/put', - headers, - payload - }) - - expect(res.statusCode).to.equal(200) - const hash = res.result.Hash - - res = await api.inject({ - method: 'POST', - url: '/api/v0/object/links?cid-base=invalid&arg=' + hash - }) - - expect(res.statusCode).to.equal(400) - expect(res.result.Message).to.include('Invalid request query input') - }) + expect(res).to.have.property('statusCode', 400) + expect(res).to.have.nested.property('result.Message').that.is.a('string') }) - describe('/patch/append-data', () => { - it('only accepts POST', () => { - return testHttpMethod('/api/v0/object/patch/append-data') - }) + it('returns 400 for request with invalid argument', async () => { + const res = await http({ + method: 'POST', + url: '/api/v0/object/links?arg=invalid' + }, { ipfs }) - it('returns 400 for request without key', async () => { - const res = await api.inject({ - method: 'POST', - url: '/api/v0/object/patch/append-data' - }) - - expect(res.statusCode).to.equal(400) - expect(res.result.Message).to.be.a('string') - }) + expect(res).to.have.property('statusCode', 400) + expect(res).to.have.nested.property('result.Code', 1) + expect(res).to.have.nested.property('result.Message').that.is.a('string') + }) - it('returns 400 if no data is provided', async () => { - const form = new FormData() - const headers = form.getHeaders() + it('returns value', async () => { + ipfs.object.links.withArgs(cid).returns(fileNode.Links) + + const expectedResult = { + Hash: cid.toString(), + Links: [{ + Name: '', + Hash: cid.toString(), + Size: 5 + }] + } + + const res = await http({ + method: 'POST', + url: `/api/v0/object/links?arg=${cid}` + }, { ipfs }) + + expect(res).to.have.property('statusCode', 200) + expect(res).to.have.deep.property('result', expectedResult) + }) - const payload = await streamToPromise(form) - const res = await api.inject({ - method: 'POST', - url: '/api/v0/object/patch/append-data?arg=QmVLUHkjGg3duGb5w3dnwK5w2P9QWuJmtVNuDPLc9ZDjzk', - headers, - payload - }) + // TODO: unskip after switch to v1 CIDs by default + it.skip('should list object links and return a base64 encoded CID', async () => { + const res = await http({ + method: 'POST', + url: `/api/v0/object/links?cid-base=base64&arg=${cid}` + }, { ipfs }) + + expect(res).to.have.property('statusCode', 200) + expect(multibase.isEncoded(res.result.Hash)).to.deep.equal('base64') + expect(res).to.have.nested.property('result.Links').that.is.empty() + expect(multibase.isEncoded(res.result.Links[0].Hash)).to.deep.equal('base64') + }) - expect(res.statusCode).to.equal(400) - }) + it('should not list object links for invalid cid-base option', async () => { + const res = await http({ + method: 'POST', + url: `/api/v0/object/links?cid-base=invalid&arg=${cid}` + }, { ipfs }) - it('returns 400 for request with invalid key', async () => { - const form = new FormData() - const filePath = 'test/fixtures/test-data/badconfig' - form.append('file', fs.createReadStream(filePath)) - const headers = form.getHeaders() - - const payload = await streamToPromise(form) - const res = await api.inject({ - method: 'POST', - url: '/api/v0/object/patch/append-data?arg=invalid', - headers, - payload - }) - - expect(res.statusCode).to.equal(400) - }) + expect(res).to.have.property('statusCode', 400) + expect(res).to.have.nested.property('result.Message').that.includes('Invalid request query input') + }) + }) - it('updates value', async () => { - const form = new FormData() - const filePath = 'test/fixtures/test-data/badconfig' - form.append('data', fs.createReadStream(filePath)) - const headers = form.getHeaders() - const expectedResult = { - Data: fs.readFileSync(filePath), - Hash: 'QmfY37rjbPCZRnhvvJuQ46htW3VCAWziVB991P79h6WSv6', - Links: [], - Size: 19 - } + describe('/patch/append-data', () => { + it('only accepts POST', () => { + return testHttpMethod('/api/v0/object/patch/append-data') + }) - const payload = await streamToPromise(form) - const res = await api.inject({ - method: 'POST', - url: '/api/v0/object/patch/append-data?arg=QmdfTbBqBPQ7VNxZEYEj14VmRuZBkqFbiwReogJgS1zR1n', - headers, - payload - }) + it('returns 400 for request without key', async () => { + const res = await http({ + method: 'POST', + url: '/api/v0/object/patch/append-data' + }, { ipfs }) - expect(res.statusCode).to.equal(200) - expect(res.result).to.deep.equal(expectedResult) - }) + expect(res).to.have.property('statusCode', 400) + expect(res).to.have.nested.property('result.Message').that.is.a('string') + }) - // TODO: unskip after switch to v1 CIDs by default - it.skip('should append data to object and return a base64 encoded CID', async () => { - let res = await api.inject({ - method: 'POST', - url: '/api/v0/object/new' - }) - - expect(res.statusCode).to.equal(200) - - const form = new FormData() - form.append('data', Buffer.from('TEST' + Date.now())) - const headers = form.getHeaders() - - const payload = await streamToPromise(form) - res = await api.inject({ - method: 'POST', - url: '/api/v0/object/patch/append-data?cid-base=base64&arg=' + res.result.Hash, - headers, - payload - }) - - expect(res.statusCode).to.equal(200) - expect(multibase.isEncoded(res.result.Hash)).to.deep.equal('base64') - }) + it('returns 400 if no data is provided', async () => { + const form = new FormData() + const headers = form.getHeaders() - it('should not append data to object for invalid cid-base option', async () => { - let res = await api.inject({ - method: 'POST', - url: '/api/v0/object/new' - }) + const payload = await streamToPromise(form) + const res = await http({ + method: 'POST', + url: '/api/v0/object/patch/append-data?arg=QmVLUHkjGg3duGb5w3dnwK5w2P9QWuJmtVNuDPLc9ZDjzk', + headers, + payload + }, { ipfs }) - expect(res.statusCode).to.equal(200) + expect(res).to.have.property('statusCode', 400) + }) - const form = new FormData() - form.append('data', Buffer.from('TEST' + Date.now())) - const headers = form.getHeaders() + it('returns 400 for request with invalid key', async () => { + const form = new FormData() + const filePath = 'test/fixtures/test-data/badconfig' + form.append('file', fs.createReadStream(filePath)) + const headers = form.getHeaders() + + const payload = await streamToPromise(form) + const res = await http({ + method: 'POST', + url: '/api/v0/object/patch/append-data?arg=invalid', + headers, + payload + }, { ipfs }) + + expect(res).to.have.property('statusCode', 400) + }) - const payload = await streamToPromise(form) - res = await api.inject({ - method: 'POST', - url: '/api/v0/object/patch/append-data?cid-base=invalid&arg=' + res.result.Hash, - headers, - payload - }) + it('updates value', async () => { + const data = Buffer.from('TEST' + Date.now()) + + ipfs.object.patch.appendData.withArgs(cid, data).returns(cid) + ipfs.object.get.withArgs(cid).returns(emptyDirectoryNode) + + const form = new FormData() + form.append('data', data) + const headers = form.getHeaders() + const expectedResult = { + Data: emptyDirectoryNode.Data, + Hash: cid.toString(), + Links: [], + Size: 4 + } + + const payload = await streamToPromise(form) + const res = await http({ + method: 'POST', + url: `/api/v0/object/patch/append-data?arg=${cid}`, + headers, + payload + }, { ipfs }) + + expect(res).to.have.property('statusCode', 200) + expect(res.result).to.deep.equal(expectedResult) + }) - expect(res.statusCode).to.equal(400) - expect(res.result.Message).to.include('Invalid request query input') - }) + // TODO: unskip after switch to v1 CIDs by default + it.skip('should append data to object and return a base64 encoded CID', async () => { + const form = new FormData() + form.append('data', Buffer.from('TEST' + Date.now())) + const headers = form.getHeaders() + + const payload = await streamToPromise(form) + const res = await http({ + method: 'POST', + url: `/api/v0/object/patch/append-data?cid-base=base64&arg=${cid}`, + headers, + payload + }, { ipfs }) + + expect(res).to.have.property('statusCode', 200) + expect(multibase.isEncoded(res.result.Hash)).to.deep.equal('base64') }) - describe('/patch/set-data', () => { - it('only accepts POST', () => { - return testHttpMethod('/api/v0/object/patch/set-data') - }) + it('should not append data to object for invalid cid-base option', async () => { + const form = new FormData() + form.append('data', Buffer.from('TEST' + Date.now())) + const headers = form.getHeaders() + + const payload = await streamToPromise(form) + const res = await http({ + method: 'POST', + url: `/api/v0/object/patch/append-data?cid-base=invalid&arg=${cid}`, + headers, + payload + }, { ipfs }) + + expect(res).to.have.property('statusCode', 400) + expect(res).to.have.nested.property('result.Message').that.includes('Invalid request query input') + }) + }) - it('returns 400 for request without key', async () => { - const res = await api.inject({ - method: 'POST', - url: '/api/v0/object/patch/set-data' - }) + describe('/patch/set-data', () => { + it('only accepts POST', () => { + return testHttpMethod('/api/v0/object/patch/set-data') + }) - expect(res.statusCode).to.equal(400) - expect(res.result.Message).to.be.a('string') - }) + it('returns 400 for request without key', async () => { + const res = await http({ + method: 'POST', + url: '/api/v0/object/patch/set-data' + }, { ipfs }) - it('returns 400 if no data is provided', async () => { - const form = new FormData() - const headers = form.getHeaders() + expect(res).to.have.property('statusCode', 400) + expect(res).to.have.nested.property('result.Message').that.is.a('string') + }) - const payload = await streamToPromise(form) - const res = await api.inject({ - method: 'POST', - url: '/api/v0/object/patch/set-data?arg=QmVLUHkjGg3duGb5w3dnwK5w2P9QWuJmtVNuDPLc9ZDjzk', - headers, - payload - }) + it('returns 400 if no data is provided', async () => { + const form = new FormData() + const headers = form.getHeaders() - expect(res.statusCode).to.equal(400) - }) + const payload = await streamToPromise(form) + const res = await http({ + method: 'POST', + url: '/api/v0/object/patch/set-data?arg=QmVLUHkjGg3duGb5w3dnwK5w2P9QWuJmtVNuDPLc9ZDjzk', + headers, + payload + }, { ipfs }) - it('returns 400 for request with invalid key', async () => { - const form = new FormData() - const filePath = 'test/fixtures/test-data/badconfig' - form.append('file', fs.createReadStream(filePath)) - const headers = form.getHeaders() - - const payload = await streamToPromise(form) - const res = await api.inject({ - method: 'POST', - url: '/api/v0/object/patch/set-data?arg=invalid', - headers, - payload - }) - - expect(res.statusCode).to.equal(400) - }) + expect(res).to.have.property('statusCode', 400) + }) - it('updates value', async () => { - const form = new FormData() - const filePath = 'test/fixtures/test-data/badconfig' - form.append('data', fs.createReadStream(filePath)) - const headers = form.getHeaders() - const expectedResult = { - Hash: 'QmfY37rjbPCZRnhvvJuQ46htW3VCAWziVB991P79h6WSv6', - Links: [] - } + it('returns 400 for request with invalid key', async () => { + const form = new FormData() + const filePath = 'test/fixtures/test-data/badconfig' + form.append('file', fs.createReadStream(filePath)) + const headers = form.getHeaders() + + const payload = await streamToPromise(form) + const res = await http({ + method: 'POST', + url: '/api/v0/object/patch/set-data?arg=invalid', + headers, + payload + }, { ipfs }) + + expect(res).to.have.property('statusCode', 400) + }) - const payload = await streamToPromise(form) - const res = await api.inject({ - method: 'POST', - url: '/api/v0/object/patch/set-data?arg=QmfY37rjbPCZRnhvvJuQ46htW3VCAWziVB991P79h6WSv6', - headers, - payload - }) + it('updates value', async () => { + const data = Buffer.from('TEST' + Date.now()) + + ipfs.object.patch.setData.withArgs(cid, data).returns(cid) + ipfs.object.get.withArgs(cid).returns(emptyDirectoryNode) + + const form = new FormData() + form.append('data', data) + const headers = form.getHeaders() + const expectedResult = { + Hash: cid.toString(), + Links: [] + } + + const payload = await streamToPromise(form) + const res = await http({ + method: 'POST', + url: `/api/v0/object/patch/set-data?arg=${cid}`, + headers, + payload + }, { ipfs }) + + expect(res).to.have.property('statusCode', 200) + expect(res.result).to.deep.equal(expectedResult) + }) - expect(res.statusCode).to.equal(200) - expect(res.result).to.deep.equal(expectedResult) - }) + // TODO: unskip after switch to v1 CIDs by default + it.skip('should set data for object and return a base64 encoded CID', async () => { + const form = new FormData() + form.append('data', Buffer.from('TEST' + Date.now())) + const headers = form.getHeaders() + + const payload = await streamToPromise(form) + const res = await http({ + method: 'POST', + url: `/api/v0/object/patch/set-data?cid-base=base64&arg=${cid}`, + headers, + payload + }, { ipfs }) + + expect(res).to.have.property('statusCode', 200) + expect(multibase.isEncoded(res.result.Hash)).to.deep.equal('base64') + }) - // TODO: unskip after switch to v1 CIDs by default - it.skip('should set data for object and return a base64 encoded CID', async () => { - let res = await api.inject({ - method: 'POST', - url: '/api/v0/object/new' - }) - expect(res.statusCode).to.equal(200) - - const form = new FormData() - form.append('data', Buffer.from('TEST' + Date.now())) - const headers = form.getHeaders() - - const payload = await streamToPromise(form) - res = await api.inject({ - method: 'POST', - url: '/api/v0/object/patch/set-data?cid-base=base64&arg=' + res.result.Hash, - headers, - payload - }) - - expect(res.statusCode).to.equal(200) - expect(multibase.isEncoded(res.result.Hash)).to.deep.equal('base64') - }) + it('should not set data for object for invalid cid-base option', async () => { + const form = new FormData() + form.append('data', Buffer.from('TEST' + Date.now())) + const headers = form.getHeaders() + + const payload = await streamToPromise(form) + const res = await http({ + method: 'POST', + url: `/api/v0/object/patch/set-data?cid-base=invalid&arg=${cid}`, + headers, + payload + }, { ipfs }) + + expect(res).to.have.property('statusCode', 400) + expect(res).to.have.nested.property('result.Message').that.includes('Invalid request query input') + }) + }) - it('should not set data for object for invalid cid-base option', async () => { - let res = await api.inject({ - method: 'POST', - url: '/api/v0/object/new' - }) + describe('/patch/add-link', () => { + it('only accepts POST', () => { + return testHttpMethod('/api/v0/object/patch/add-link') + }) - expect(res.statusCode).to.equal(200) + it('returns 400 for request without arguments', async () => { + const res = await http({ + method: 'POST', + url: '/api/v0/object/patch/add-link' + }, { ipfs }) - const form = new FormData() - form.append('data', Buffer.from('TEST' + Date.now())) - const headers = form.getHeaders() + expect(res).to.have.property('statusCode', 400) + expect(res).to.have.nested.property('result.Message').that.is.a('string') + }) - const payload = await streamToPromise(form) - res = await api.inject({ - method: 'POST', - url: '/api/v0/object/patch/set-data?cid-base=invalid&arg=' + res.result.Hash, - headers, - payload - }) + it('returns 400 for request with only one invalid argument', async () => { + const res = await http({ + method: 'POST', + url: '/api/v0/object/patch/add-link?arg=invalid' + }, { ipfs }) - expect(res.statusCode).to.equal(400) - expect(res.result.Message).to.include('Invalid request query input') - }) + expect(res).to.have.property('statusCode', 400) + expect(res).to.have.nested.property('result.Message').that.is.a('string') }) - describe('/patch/add-link', () => { - it('only accepts POST', () => { - return testHttpMethod('/api/v0/object/patch/add-link') - }) - - it('returns 400 for request without arguments', async () => { - const res = await api.inject({ - method: 'POST', - url: '/api/v0/object/patch/add-link' - }) + it('returns 400 for request with invalid first argument', async () => { + const res = await http({ + method: 'POST', + url: '/api/v0/object/patch/add-link?arg=&arg=foo&arg=QmTz3oc4gdpRMKP2sdGUPZTAGRngqjsi99BPoztyP53JMM' + }, { ipfs }) - expect(res.statusCode).to.equal(400) - expect(res.result.Message).to.be.a('string') - }) + expect(res).to.have.property('statusCode', 400) + expect(res).to.have.nested.property('result.Code', 1) + expect(res).to.have.nested.property('result.Message').that.is.a('string') + }) - it('returns 400 for request with only one invalid argument', async () => { - const res = await api.inject({ - method: 'POST', - url: '/api/v0/object/patch/add-link?arg=invalid' - }) + it('returns 400 for request with empty second argument', async () => { + const res = await http({ + method: 'POST', + url: '/api/v0/object/patch/add-link?arg=QmUNLLsPACCz1vLxQVkXqqLX5R1X345qqfHbsf67hvA3Nn&arg=&arg=QmTz3oc4gdpRMKP2sdGUPZTAGRngqjsi99BPoztyP53JMM' + }, { ipfs }) - expect(res.statusCode).to.equal(400) - expect(res.result.Message).to.be.a('string') - }) + expect(res).to.have.property('statusCode', 400) + expect(res).to.have.nested.property('result.Code', 1) + expect(res).to.have.nested.property('result.Message').that.is.a('string') + }) - it('returns 400 for request with invalid first argument', async () => { - const res = await api.inject({ - method: 'POST', - url: '/api/v0/object/patch/add-link?arg=&arg=foo&arg=QmTz3oc4gdpRMKP2sdGUPZTAGRngqjsi99BPoztyP53JMM' - }) + it('returns value', async () => { + const name = 'name' - expect(res.statusCode).to.equal(400) - expect(res.result.Code).to.equal(1) - expect(res.result.Message).to.be.a('string') - }) + ipfs.object.patch.addLink.withArgs(cid, sinon.match({ + Name: name, + Hash: cid2 + })).returns(cid) + ipfs.object.get.withArgs(cid).returns(fileNode) + ipfs.object.get.withArgs(cid2).returns(fileNode) - it('returns 400 for request with empty second argument', async () => { - const res = await api.inject({ - method: 'POST', - url: '/api/v0/object/patch/add-link?arg=QmUNLLsPACCz1vLxQVkXqqLX5R1X345qqfHbsf67hvA3Nn&arg=&arg=QmTz3oc4gdpRMKP2sdGUPZTAGRngqjsi99BPoztyP53JMM' - }) + const res = await http({ + method: 'POST', + url: `/api/v0/object/patch/add-link?arg=${cid}&arg=${name}&arg=${cid2}` + }, { ipfs }) - expect(res.statusCode).to.equal(400) - expect(res.result.Code).to.equal(1) - expect(res.result.Message).to.be.a('string') + expect(res).to.have.property('statusCode', 200) + expect(res).to.have.nested.property('result.Hash', cid.toString()) + expect(res).to.have.deep.nested.property('result.Links[0]', { + Name: '', + Hash: cid.toString(), + Size: 5 }) + }) - it('returns value', async () => { - const res = await api.inject({ - method: 'POST', - url: '/api/v0/object/patch/add-link?arg=QmdfTbBqBPQ7VNxZEYEj14VmRuZBkqFbiwReogJgS1zR1n&arg=foo&arg=QmUNLLsPACCz1vLxQVkXqqLX5R1X345qqfHbsf67hvA3Nn' - }) - - expect(res.statusCode).to.equal(200) - expect(res.result.Hash).to.equal('QmdVHE8fUD6FLNLugtNxqDFyhaCgdob372hs6BYEe75VAK') - expect(res.result.Links[0]).to.deep.equal({ - Name: 'foo', - Hash: 'QmUNLLsPACCz1vLxQVkXqqLX5R1X345qqfHbsf67hvA3Nn', - Size: 4 - }) - }) + // TODO: unskip after switch to v1 CIDs by default + it.skip('should add a link to an object and return a base64 encoded CID', async () => { + const res = await http({ + method: 'POST', + url: `/api/v0/object/patch/add-link?cid-base=base64&arg=${cid}&arg=test&arg=${cid2}` + }, { ipfs }) - // TODO: unskip after switch to v1 CIDs by default - it.skip('should add a link to an object and return a base64 encoded CID', async () => { - let res = await api.inject({ - method: 'POST', - url: '/api/v0/object/new' - }) + expect(res).to.have.property('statusCode', 200) + expect(multibase.isEncoded(res.result.Hash)).to.deep.equal('base64') + }) - expect(res.statusCode).to.equal(200) + it('should not add a link to an object for invalid cid-base option', async () => { + const res = await http({ + method: 'POST', + url: `/api/v0/object/patch/add-link?cid-base=invalid&arg=${cid}&arg=test&arg=${cid2}` + }, { ipfs }) - res = await api.inject({ - method: 'POST', - url: `/api/v0/object/patch/add-link?cid-base=base64&arg=${res.result.Hash}&arg=test&arg=${res.result.Hash}` - }) + expect(res).to.have.property('statusCode', 400) + expect(res).to.have.nested.property('result.Message').that.includes('Invalid request query input') + }) + }) - expect(res.statusCode).to.equal(200) - expect(multibase.isEncoded(res.result.Hash)).to.deep.equal('base64') - }) + describe('/patch/rm-link', () => { + it('only accepts POST', () => { + return testHttpMethod('/api/v0/object/patch/rm-link') + }) - it('should not add a link to an object for invalid cid-base option', async () => { - let res = await api.inject({ - method: 'POST', - url: '/api/v0/object/new' - }) + it('returns 400 for request without arguments', async () => { + const res = await http({ + method: 'POST', + url: '/api/v0/object/patch/rm-link' + }, { ipfs }) - expect(res.statusCode).to.equal(200) + expect(res).to.have.property('statusCode', 400) + expect(res).to.have.nested.property('result.Message').that.is.a('string') + }) - res = await api.inject({ - method: 'POST', - url: `/api/v0/object/patch/add-link?cid-base=invalid&arg=${res.result.Hash}&arg=test&arg=${res.result.Hash}` - }) + it('returns 400 for request with only one invalid argument', async () => { + const res = await http({ + method: 'POST', + url: '/api/v0/object/patch/rm-link?arg=invalid' + }, { ipfs }) - expect(res.statusCode).to.equal(400) - expect(res.result.Message).to.include('Invalid request query input') - }) + expect(res).to.have.property('statusCode', 400) + expect(res).to.have.nested.property('result.Message').that.is.a('string') }) - describe('/patch/rm-link', () => { - it('only accepts POST', () => { - return testHttpMethod('/api/v0/object/patch/rm-link') - }) + it('returns 400 for request with invalid first argument', async () => { + const res = await http({ + method: 'POST', + url: '/api/v0/object/patch/rm-link?arg=invalid&arg=foo' + }, { ipfs }) - it('returns 400 for request without arguments', async () => { - const res = await api.inject({ - method: 'POST', - url: '/api/v0/object/patch/rm-link' - }) + expect(res).to.have.property('statusCode', 400) + expect(res).to.have.nested.property('result.Code', 1) + expect(res).to.have.nested.property('result.Message').that.is.a('string') + }) - expect(res.statusCode).to.equal(400) - expect(res.result.Message).to.be.a('string') - }) + it('returns 400 for request with invalid second argument', async () => { + const res = await http({ + method: 'POST', + url: '/api/v0/object/patch/rm-link?arg=QmZKetgwm4o3LhNaoLSHv32wBhTwj9FBwAdSchDMKyFQEx&arg=' + }, { ipfs }) - it('returns 400 for request with only one invalid argument', async () => { - const res = await api.inject({ - method: 'POST', - url: '/api/v0/object/patch/rm-link?arg=invalid' - }) + expect(res).to.have.property('statusCode', 400) + expect(res).to.have.nested.property('result.Code', 1) + expect(res).to.have.nested.property('result.Message').that.is.a('string') + }) - expect(res.statusCode).to.equal(400) - expect(res.result.Message).to.be.a('string') - }) + it('returns value', async () => { + const name = 'name' - it('returns 400 for request with invalid first argument', async () => { - const res = await api.inject({ - method: 'POST', - url: '/api/v0/object/patch/rm-link?arg=invalid&arg=foo' - }) + ipfs.object.patch.rmLink.withArgs(cid, sinon.match({ + name + })).returns(cid2) + ipfs.object.get.withArgs(cid2).returns(emptyDirectoryNode) - expect(res.statusCode).to.equal(400) - expect(res.result.Code).to.equal(1) - expect(res.result.Message).to.be.a('string') - }) + const res = await http({ + method: 'POST', + url: `/api/v0/object/patch/rm-link?arg=${cid}&arg=${name}` + }, { ipfs }) - it('returns 400 for request with invalid second argument', async () => { - const res = await api.inject({ - method: 'POST', - url: '/api/v0/object/patch/rm-link?arg=QmZKetgwm4o3LhNaoLSHv32wBhTwj9FBwAdSchDMKyFQEx&arg=' - }) + expect(res).to.have.property('statusCode', 200) + expect(res).to.have.nested.property('result.Hash', cid2.toString()) + }) - expect(res.statusCode).to.equal(400) - expect(res.result.Code).to.equal(1) - expect(res.result.Message).to.be.a('string') - }) + // TODO: unskip after switch to v1 CIDs by default + it.skip('should remove a link from an object and return a base64 encoded CID', async () => { + const name = 'name' - it('returns value', async () => { - const res = await api.inject({ - method: 'POST', - url: '/api/v0/object/patch/rm-link?arg=QmdVHE8fUD6FLNLugtNxqDFyhaCgdob372hs6BYEe75VAK&arg=foo' - }) + const res = await http({ + method: 'POST', + url: `/api/v0/object/patch/rm-link?cid-base=base64&arg=${cid}&arg=${name}` + }, { ipfs }) - expect(res.statusCode).to.equal(200) - expect(res.result.Hash).to.equal('QmdfTbBqBPQ7VNxZEYEj14VmRuZBkqFbiwReogJgS1zR1n') - }) + expect(res).to.have.property('statusCode', 200) + expect(multibase.isEncoded(res.result.Hash)).to.deep.equal('base64') + }) - // TODO: unskip after switch to v1 CIDs by default - it.skip('should remove a link from an object and return a base64 encoded CID', async () => { - const linkName = 'TEST' + Date.now() - - let res = await api.inject({ - method: 'POST', - url: '/api/v0/object/new' - }) - - expect(res.statusCode).to.equal(200) - const linkHash = res.result.Hash - - const form = new FormData() - form.append('file', JSON.stringify({ - Data: 'TEST' + Date.now(), - Links: [{ Name: linkName, Hash: linkHash, Size: 8 }] - }), { filename: 'node.json' }) - const headers = form.getHeaders() - - const payload = await streamToPromise(form) - res = await api.inject({ - method: 'POST', - url: '/api/v0/object/put', - headers, - payload - }) - - expect(res.statusCode).to.equal(200) - const hash = res.result.Hash - - res = await api.inject({ - method: 'POST', - url: `/api/v0/object/patch/rm-link?cid-base=base64&arg=${hash}&arg=${linkName}` - }) - - expect(res.statusCode).to.equal(200) - expect(multibase.isEncoded(res.result.Hash)).to.deep.equal('base64') - }) + it('should not remove a link from an object for invalid cid-base option', async () => { + const res = await http({ + method: 'POST', + url: `/api/v0/object/patch/rm-link?cid-base=invalid&arg=${cid}&arg=derp` + }, { ipfs }) - it('should not remove a link from an object for invalid cid-base option', async () => { - const linkName = 'TEST' + Date.now() - - let res = await api.inject({ - method: 'POST', - url: '/api/v0/object/new' - }) - - expect(res.statusCode).to.equal(200) - const linkHash = res.result.Hash - - const form = new FormData() - form.append('file', JSON.stringify({ - Data: 'TEST' + Date.now(), - Links: [{ Name: linkName, Hash: linkHash, Size: 8 }] - }), { filename: 'node.json' }) - const headers = form.getHeaders() - - const payload = await streamToPromise(form) - res = await api.inject({ - method: 'POST', - url: '/api/v0/object/put', - headers, - payload - }) - - expect(res.statusCode).to.equal(200) - const hash = res.result.Hash - - res = await api.inject({ - method: 'POST', - url: `/api/v0/object/patch/rm-link?cid-base=invalid&arg=${hash}&arg=${linkName}` - }) - - expect(res.statusCode).to.equal(400) - expect(res.result.Message).to.include('Invalid request query input') - }) + expect(res).to.have.property('statusCode', 400) + expect(res).to.have.nested.property('result.Message').that.includes('Invalid request query input') }) }) -} +}) diff --git a/packages/ipfs/test/http-api/inject/pin.js b/packages/ipfs/test/http-api/inject/pin.js index 37b4a2f63b..12cd5458be 100644 --- a/packages/ipfs/test/http-api/inject/pin.js +++ b/packages/ipfs/test/http-api/inject/pin.js @@ -3,333 +3,300 @@ 'use strict' const { expect } = require('interface-ipfs-core/src/utils/mocha') -const FormData = require('form-data') -const streamToPromise = require('stream-to-promise') const multibase = require('multibase') const testHttpMethod = require('../../utils/test-http-method') +const http = require('../../utils/http') +const sinon = require('sinon') +const CID = require('cids') +const allNdjson = require('../../utils/all-ndjson') + +describe('/pin', () => { + const cid = new CID('QmfGBRT6BbWJd7yUc2uYdaUZJBbnEFvTqehPFoSMQ6wgdr') + const cid2 = new CID('QmZTR5bcpQD7cFgTorqxZDYaew1Wqgfbd2ud9QqGPAkK2V') + let ipfs + + beforeEach(() => { + ipfs = { + pin: { + ls: sinon.stub(), + add: sinon.stub(), + rm: sinon.stub() + } + } + }) -// We use existing pin structure in the go-ipfs-repo fixture -// so that we don't have to stream a bunch of object/put operations -// This is suitable because these tests target the functionality -// of the /pin endpoints and don't delve into the pin core -// -// fixture's pins: -// - root1 -// - c1 -// - c2 -// - c3 -// - c4 -// - c5 -// - c6 - -const pins = { - root1: 'QmfGBRT6BbWJd7yUc2uYdaUZJBbnEFvTqehPFoSMQ6wgdr', - c1: 'QmZTR5bcpQD7cFgTorqxZDYaew1Wqgfbd2ud9QqGPAkK2V', - c2: 'QmYCvbfNbCwFR45HiNP45rwJgvatpiW38D961L5qAhUM5Y', - c3: 'QmY5heUM5qgRubMDD1og9fhCPA6QdkMp3QCwd4s7gJsyE7', - c4: 'QmQN88TEidd3RY2u3dpib49fERTDfKtDpvxnvczATNsfKT', - c5: 'QmPZ9gcCEpqKTo6aq61g2nXGUhM4iCL3ewB6LDXZCtioEB', - c6: 'QmciSU8hfpAXKjvK5YLUSwApomGSWN5gFbP4EpDAEzu2Te' -} - -module.exports = (http) => { - describe('/pin', function () { - this.timeout(20 * 1000) - let api - - before(() => { - api = http.api._httpApi._apiServers[0] + describe('/rm', () => { + it('only accepts POST', () => { + return testHttpMethod(`/api/v0/pin/rm?arg=${cid}`) }) - describe('/rm', () => { - it('only accepts POST', () => { - return testHttpMethod(`/api/v0/pin/rm?arg=${pins.root1}`) - }) + it('fails on invalid args', async () => { + const res = await http({ + method: 'POST', + url: '/api/v0/pin/rm?arg=invalid' + }, { ipfs }) - it('fails on invalid args', async () => { - const res = await api.inject({ - method: 'POST', - url: '/api/v0/pin/rm?arg=invalid' - }) + expect(res).to.have.property('statusCode', 400) + expect(res).to.have.nested.property('result.Message').that.matches(/invalid ipfs ref path/) + }) - expect(res.statusCode).to.equal(400) - expect(res.result.Message).to.match(/invalid ipfs ref path/) - }) + it('unpins recursive pins', async () => { + ipfs.pin.rm.withArgs([cid.toString()], sinon.match({ + recursive: true + })).returns([{ + cid + }]) - it('unpins recursive pins', async () => { - const res = await api.inject({ - method: 'POST', - url: `/api/v0/pin/rm?arg=${pins.root1}` - }) + const res = await http({ + method: 'POST', + url: `/api/v0/pin/rm?arg=${cid}` + }, { ipfs }) - expect(res.statusCode).to.equal(200) - expect(res.result.Pins).to.deep.eql([pins.root1]) - }) + expect(res).to.have.property('statusCode', 200) + expect(res).to.have.deep.nested.property('result.Pins', [cid.toString()]) + }) - it('unpins direct pins', async () => { - let res = await api.inject({ - method: 'POST', - url: `/api/v0/pin/add?arg=${pins.root1}&recursive=false` - }) + it('unpins direct pins', async () => { + ipfs.pin.rm.withArgs([cid.toString()], sinon.match({ + recursive: false + })).returns([{ + cid + }]) - expect(res.statusCode).to.equal(200) + const res = await http({ + method: 'POST', + url: `/api/v0/pin/rm?arg=${cid}&recursive=false` + }, { ipfs }) - res = await api.inject({ - method: 'POST', - url: `/api/v0/pin/rm?arg=${pins.root1}&recursive=false` - }) + expect(res).to.have.property('statusCode', 200) + expect(res).to.have.deep.nested.property('result.Pins', [cid.toString()]) + }) - expect(res.statusCode).to.equal(200) - expect(res.result.Pins).to.deep.eql([pins.root1]) + it('should remove pin and return base64 encoded CID', async () => { + ipfs.pin.rm.withArgs([cid.toString()], sinon.match({ + recursive: true + })).returns([{ + cid + }]) + + const res = await http({ + method: 'POST', + url: `/api/v0/pin/rm?arg=${cid}&cid-base=base64` + }, { ipfs }) + + expect(res).to.have.property('statusCode', 200) + res.result.Pins.forEach(cid => { + expect(multibase.isEncoded(cid)).to.deep.equal('base64') }) + }) - it('should remove pin and return base64 encoded CID', async () => { - const form = new FormData() - form.append('data', Buffer.from('TEST' + Date.now())) - const headers = form.getHeaders() - - const payload = await streamToPromise(form) - let res = await api.inject({ - method: 'POST', - url: '/api/v0/add', - headers: headers, - payload: payload - }) - - expect(res.statusCode).to.equal(200) - const hash = JSON.parse(res.result).Hash - - res = await api.inject({ - method: 'POST', - url: `/api/v0/pin/rm?arg=${hash}&cid-base=base64` - }) - - expect(res.statusCode).to.equal(200) - res.result.Pins.forEach(cid => { - expect(multibase.isEncoded(cid)).to.deep.equal('base64') - }) - }) + it('should not remove pin for invalid cid-base option', async () => { + const res = await http({ + method: 'POST', + url: `/api/v0/pin/rm?arg=${cid}&cid-base=invalid` + }, { ipfs }) - it('should not remove pin for invalid cid-base option', async () => { - const form = new FormData() - form.append('data', Buffer.from('TEST' + Date.now())) - const headers = form.getHeaders() - - const payload = await streamToPromise(form) - let res = await api.inject({ - method: 'POST', - url: '/api/v0/add', - headers: headers, - payload: payload - }) - - expect(res.statusCode).to.equal(200) - const hash = JSON.parse(res.result).Hash - - res = await api.inject({ - method: 'POST', - url: `/api/v0/pin/rm?arg=${hash}&cid-base=invalid` - }) - - expect(res.statusCode).to.equal(400) - expect(res.result.Message).to.include('Invalid request query input') - }) + expect(res).to.have.property('statusCode', 400) + expect(res).to.have.nested.property('result.Message').that.includes('Invalid request query input') }) + }) - describe('/add', () => { - it('only accepts POST', () => { - return testHttpMethod(`/api/v0/pin/add?arg=${pins.root1}`) - }) + describe('/add', () => { + it('only accepts POST', () => { + return testHttpMethod(`/api/v0/pin/add?arg=${cid}`) + }) - it('fails on invalid args', async () => { - const res = await api.inject({ - method: 'POST', - url: '/api/v0/pin/add?arg=invalid' - }) + it('fails on invalid args', async () => { + const res = await http({ + method: 'POST', + url: '/api/v0/pin/add?arg=invalid' + }, { ipfs }) - expect(res.statusCode).to.equal(400) - expect(res.result.Message).to.match(/invalid ipfs ref path/) - }) + expect(res).to.have.property('statusCode', 400) + expect(res).to.have.nested.property('result.Message').that.matches(/invalid ipfs ref path/) + }) - it('recursively', async () => { - const res = await api.inject({ - method: 'POST', - url: `/api/v0/pin/add?arg=${pins.root1}` - }) + it('recursively', async () => { + ipfs.pin.add.withArgs([cid.toString()], sinon.match({ + recursive: true + })).returns([{ + cid + }]) - expect(res.statusCode).to.equal(200) - expect(res.result.Pins).to.deep.eql([pins.root1]) - }) + const res = await http({ + method: 'POST', + url: `/api/v0/pin/add?arg=${cid}` + }, { ipfs }) - it('directly', async () => { - const res = await api.inject({ - method: 'POST', - url: `/api/v0/pin/add?arg=${pins.root1}&recursive=false` - }) - // by directly pinning a node that is already recursively pinned, - // it should error and verifies that the endpoint is parsing - // the recursive arg correctly. - expect(res.statusCode).to.equal(400) - expect(res.result.Message).to.match(/already pinned recursively/) - }) + expect(res).to.have.property('statusCode', 200) + expect(res).to.have.deep.nested.property('result.Pins', [cid.toString()]) + }) - it('should add pin and return base64 encoded CID', async () => { - const form = new FormData() - form.append('data', Buffer.from('TEST' + Date.now())) - const headers = form.getHeaders() - - const payload = await streamToPromise(form) - let res = await api.inject({ - method: 'POST', - url: '/api/v0/add?pin=false', - headers: headers, - payload: payload - }) - - expect(res.statusCode).to.equal(200) - const hash = JSON.parse(res.result).Hash - - res = await api.inject({ - method: 'POST', - url: `/api/v0/pin/add?arg=${hash}&cid-base=base64` - }) - - expect(res.statusCode).to.equal(200) - res.result.Pins.forEach(cid => { - expect(multibase.isEncoded(cid)).to.deep.equal('base64') - }) - }) + it('directly', async () => { + ipfs.pin.add.withArgs([cid.toString()], sinon.match({ + recursive: false + })).returns([{ + cid + }]) - it('should not add pin for invalid cid-base option', async () => { - const form = new FormData() - form.append('data', Buffer.from('TEST' + Date.now())) - const headers = form.getHeaders() - - const payload = await streamToPromise(form) - let res = await api.inject({ - method: 'POST', - url: '/api/v0/add?pin=false', - headers: headers, - payload: payload - }) - expect(res.statusCode).to.equal(200) - const hash = JSON.parse(res.result).Hash - - res = await api.inject({ - method: 'POST', - url: `/api/v0/pin/add?arg=${hash}&cid-base=invalid` - }) - - expect(res.statusCode).to.equal(400) - expect(res.result.Message).to.include('Invalid request query input') - }) + const res = await http({ + method: 'POST', + url: `/api/v0/pin/add?arg=${cid}&recursive=false` + }, { ipfs }) + + expect(res).to.have.property('statusCode', 200) + expect(res).to.have.deep.nested.property('result.Pins', [cid.toString()]) }) - describe('/ls', () => { - it('only accepts POST', () => { - return testHttpMethod('/api/v0/pin/ls') + it('should add pin and return base64 encoded CID', async () => { + ipfs.pin.add.withArgs([cid.toString()], sinon.match({ + recursive: true + })).returns([{ + cid + }]) + + const res = await http({ + method: 'POST', + url: `/api/v0/pin/add?arg=${cid}&cid-base=base64` + }, { ipfs }) + + expect(res).to.have.property('statusCode', 200) + res.result.Pins.forEach(cid => { + expect(multibase.isEncoded(cid)).to.deep.equal('base64') }) + }) - it('fails on invalid args', async () => { - const res = await api.inject({ - method: 'POST', - url: '/api/v0/pin/ls?arg=invalid' - }) + it('should not add pin for invalid cid-base option', async () => { + const res = await http({ + method: 'POST', + url: `/api/v0/pin/add?arg=${cid}&cid-base=invalid` + }, { ipfs }) - expect(res.statusCode).to.equal(400) - expect(res.result.Message).to.match(/invalid ipfs ref path/) - }) + expect(res).to.have.property('statusCode', 400) + expect(res).to.have.nested.property('result.Message').that.includes('Invalid request query input') + }) + }) - it('finds all pinned objects', async () => { - const res = await api.inject({ - method: 'POST', - url: '/api/v0/pin/ls' - }) + describe('/ls', () => { + it('only accepts POST', () => { + return testHttpMethod('/api/v0/pin/ls') + }) - expect(res.statusCode).to.equal(200) - expect(res.result.Keys).to.include.keys(Object.values(pins)) - }) + it('fails on invalid args', async () => { + const res = await http({ + method: 'POST', + url: '/api/v0/pin/ls?arg=invalid' + }, { ipfs }) - it('finds all pinned objects streaming', async () => { - const res = await api.inject({ - method: 'POST', - url: '/api/v0/pin/ls?stream=true' - }) + expect(res).to.have.property('statusCode', 400) + expect(res).to.have.nested.property('result.Message').that.matches(/invalid ipfs ref path/) + }) - expect(res.statusCode).to.equal(200) - const cids = res.result.trim().split('\n').map(json => JSON.parse(json).Cid) - Object.values(pins).forEach(pinnedCid => expect(cids).to.include(pinnedCid)) - }) + it('finds all pinned objects', async () => { + ipfs.pin.ls.returns([{ + cid, + type: 'recursive' + }]) - it('finds specific pinned objects', async () => { - const res = await api.inject({ - method: 'POST', - url: `/api/v0/pin/ls?arg=${pins.c1}` - }) + const res = await http({ + method: 'POST', + url: '/api/v0/pin/ls' + }, { ipfs }) - expect(res.statusCode).to.equal(200) - expect(res.result.Keys[pins.c1].Type) - .to.equal(`indirect through ${pins.root1}`) - }) + expect(res).to.have.property('statusCode', 200) + expect(res).to.have.nested.property('result.Keys').that.includes.keys(cid.toString()) + }) - it('finds pins of type', async () => { - const res = await api.inject({ - method: 'POST', - url: '/api/v0/pin/ls?type=recursive' - }) + it('finds all pinned objects streaming', async () => { + ipfs.pin.ls.returns([{ + cid: cid, + type: 'recursive' + }, { + cid: cid2, + type: 'recursive' + }]) + + const res = await http({ + method: 'POST', + url: '/api/v0/pin/ls?stream=true' + }, { ipfs }) + + expect(res).to.have.property('statusCode', 200) + expect(allNdjson(res)).to.deep.equal([ + { Cid: cid.toString(), Type: 'recursive' }, + { Cid: cid2.toString(), Type: 'recursive' } + ]) + }) - expect(res.statusCode).to.equal(200) - expect(res.result.Keys.QmfGBRT6BbWJd7yUc2uYdaUZJBbnEFvTqehPFoSMQ6wgdr) - .to.deep.eql({ Type: 'recursive' }) + it('finds specific pinned objects', async () => { + ipfs.pin.ls.withArgs(cid.toString(), sinon.match({ + type: 'all' + })).returns([{ + cid, + type: 'recursive' + }]) + + const res = await http({ + method: 'POST', + url: `/api/v0/pin/ls?arg=${cid}` + }, { ipfs }) + + expect(res).to.have.property('statusCode', 200) + expect(res).to.have.nested.property('result.Keys').that.deep.includes({ + [cid.toString()]: { + Type: 'recursive' + } }) + }) - it('should list pins and return base64 encoded CIDs', async () => { - const form = new FormData() - form.append('data', Buffer.from('TEST' + Date.now())) - const headers = form.getHeaders() - - const payload = await streamToPromise(form) - let res = await api.inject({ - method: 'POST', - url: '/api/v0/add', - headers: headers, - payload: payload - }) - expect(res.statusCode).to.equal(200) - - res = await api.inject({ - method: 'POST', - url: '/api/v0/pin/ls?cid-base=base64' - }) - - expect(res.statusCode).to.equal(200) - Object.keys(res.result.Keys).forEach(cid => { - expect(multibase.isEncoded(cid)).to.deep.equal('base64') - }) + it('finds pins of type', async () => { + ipfs.pin.ls.withArgs(undefined, sinon.match({ + type: 'direct' + })).returns([{ + cid, + type: 'direct' + }]) + + const res = await http({ + method: 'POST', + url: '/api/v0/pin/ls?type=direct' + }, { ipfs }) + + expect(res).to.have.property('statusCode', 200) + expect(res).to.have.nested.property('result.Keys').that.deep.includes({ + [cid.toString()]: { + Type: 'direct' + } }) + }) - it('should not list pins for invalid cid-base option', async () => { - const form = new FormData() - form.append('data', Buffer.from('TEST' + Date.now())) - const headers = form.getHeaders() - - const payload = await streamToPromise(form) - let res = await api.inject({ - method: 'POST', - url: '/api/v0/add', - headers: headers, - payload: payload - }) - expect(res.statusCode).to.equal(200) - - res = await api.inject({ - method: 'POST', - url: '/api/v0/pin/ls?cid-base=invalid' - }) - - expect(res.statusCode).to.equal(400) - expect(res.result.Message).to.include('Invalid request query input') + it('should list pins and return base64 encoded CIDs', async () => { + ipfs.pin.ls.returns([{ + cid, + type: 'direct' + }]) + + const res = await http({ + method: 'POST', + url: '/api/v0/pin/ls?cid-base=base64' + }, { ipfs }) + + expect(res).to.have.property('statusCode', 200) + expect(res).to.have.nested.property('result.Keys').that.satisfies((keys) => { + return Object.keys(keys).reduce((acc, curr) => { + return acc && multibase.isEncoded(curr) === 'base64' + }, true) }) }) + + it('should not list pins for invalid cid-base option', async () => { + const res = await http({ + method: 'POST', + url: '/api/v0/pin/ls?cid-base=invalid' + }, { ipfs }) + + expect(res).to.have.property('statusCode', 400) + expect(res).to.have.nested.property('result.Message').that.includes('Invalid request query input') + }) }) -} +}) diff --git a/packages/ipfs/test/http-api/inject/ping.js b/packages/ipfs/test/http-api/inject/ping.js index c4dc0c3d6b..6d17181f3d 100644 --- a/packages/ipfs/test/http-api/inject/ping.js +++ b/packages/ipfs/test/http-api/inject/ping.js @@ -4,46 +4,109 @@ const { expect } = require('interface-ipfs-core/src/utils/mocha') const testHttpMethod = require('../../utils/test-http-method') +const http = require('../../utils/http') +const sinon = require('sinon') +const allNdjson = require('../../utils/all-ndjson') -module.exports = (http) => { - describe('/ping', function () { - let api +describe('/ping', function () { + let ipfs - before(() => { - api = http.api._httpApi._apiServers[0] - }) + beforeEach(() => { + ipfs = { + ping: sinon.stub() + } + }) + + it('only accepts POST', () => { + return testHttpMethod('/api/v0/ping') + }) + + it('returns 400 if both n and count are provided', async () => { + const res = await http({ + method: 'POST', + url: '/api/v0/ping?arg=peerid&n=1&count=1' + }, { ipfs }) + + expect(res).to.have.property('statusCode', 400) + }) + + it('returns 400 if arg is not provided', async () => { + const res = await http({ + method: 'POST', + url: '/api/v0/ping?count=1' + }, { ipfs }) + + expect(res).to.have.property('statusCode', 400) + }) + + it('returns 500 for incorrect Peer Id', async () => { + const peerId = 'peerid' + ipfs.ping.withArgs(peerId).throws(new Error('derp')) + + const res = await http({ + method: 'POST', + url: `/api/v0/ping?arg=${peerId}` + }, { ipfs }) + + expect(res).to.have.property('statusCode', 500) + }) + + it('pings with a count', async () => { + const peerId = 'peerid' + ipfs.ping.withArgs(peerId, sinon.match({ + count: 5 + })).returns([]) - it('only accepts POST', () => { - return testHttpMethod('/api/v0/ping') - }) + const res = await http({ + method: 'POST', + url: `/api/v0/ping?arg=${peerId}&count=5` + }, { ipfs }) - it('returns 400 if both n and count are provided', async () => { - const res = await api.inject({ - method: 'POST', - url: '/api/v0/ping?arg=peerid&n=1&count=1' - }) + expect(res).to.have.property('statusCode', 200) + }) - expect(res.statusCode).to.equal(400) - }) + it('pings with a as n', async () => { + const peerId = 'peerid' + ipfs.ping.withArgs(peerId, sinon.match({ + count: 5 + })).returns([]) - it('returns 400 if arg is not provided', async () => { - const res = await api.inject({ - method: 'POST', - url: '/api/v0/ping?count=1' - }) + const res = await http({ + method: 'POST', + url: `/api/v0/ping?arg=${peerId}&n=5` + }, { ipfs }) - expect(res.statusCode).to.equal(400) - }) + expect(res).to.have.property('statusCode', 200) + }) - it('returns 500 for incorrect Peer Id', async function () { - this.timeout(90 * 1000) + it('pings a remote peer', async () => { + const peerId = 'peerid' + ipfs.ping.withArgs(peerId, sinon.match({ + count: 10 + })).returns([{ + success: true, + time: 1, + text: 'hello' + }, { + success: true, + time: 2, + text: 'world' + }]) - const res = await api.inject({ - method: 'POST', - url: '/api/v0/ping?arg=peerid' - }) + const res = await http({ + method: 'POST', + url: `/api/v0/ping?arg=${peerId}` + }, { ipfs }) - expect(res.statusCode).to.equal(500) - }) + expect(res).to.have.property('statusCode', 200) + expect(allNdjson(res)).to.deep.equal([{ + Success: true, + Time: 1, + Text: 'hello' + }, { + Success: true, + Time: 2, + Text: 'world' + }]) }) -} +}) diff --git a/packages/ipfs/test/http-api/inject/pubsub.js b/packages/ipfs/test/http-api/inject/pubsub.js index 6a5b9c6ebc..0d15918303 100644 --- a/packages/ipfs/test/http-api/inject/pubsub.js +++ b/packages/ipfs/test/http-api/inject/pubsub.js @@ -4,124 +4,133 @@ const { expect } = require('interface-ipfs-core/src/utils/mocha') const testHttpMethod = require('../../utils/test-http-method') +const http = require('../../utils/http') +const sinon = require('sinon') + +describe('/pubsub', () => { + const buf = Buffer.from('some message') + const topic = 'nonScents' + const topicNotSubscribed = 'somethingRandom' + + let ipfs + + beforeEach(() => { + ipfs = { + pubsub: { + subscribe: sinon.stub(), + unsubscribe: sinon.stub(), + publish: sinon.stub(), + ls: sinon.stub(), + peers: sinon.stub() + } + } + }) + + describe('/sub', () => { + it('only accepts POST', () => { + return testHttpMethod('/api/v0/pubsub/sub') + }) + + it('returns 400 if no topic is provided', async () => { + const res = await http({ + method: 'POST', + url: '/api/v0/pubsub/sub' + }, { ipfs }) + + expect(res).to.have.property('statusCode', 400) + }) -module.exports = (http) => { - describe('/pubsub', () => { - let api + it.skip('returns 200 with topic', async () => { + // need to simulate 'disconnect' events somehow in order to close + // the response stream and let the request promise resolve + // https://github.com/hapijs/shot/issues/121 + ipfs.pubsub.unsubscribe.withArgs(topic).resolves(undefined) - const buf = Buffer.from('some message') - const topic = 'nonScents' - const topicNotSubscribed = 'somethingRandom' + const res = await http({ + method: 'POST', + url: `/api/v0/pubsub/sub?arg=${topic}` + }, { ipfs }) - before(() => { - api = http.api._httpApi._apiServers[0] + expect(res).to.have.property('statusCode', 200) + expect(ipfs.pubsub.subscribe.calledWith(topic)).to.be.true() + }) + }) + + describe('/pub', () => { + it('only accepts POST', () => { + return testHttpMethod('/api/v0/pubsub/pub') }) - describe('/sub', () => { - it('only accepts POST', () => { - return testHttpMethod('/api/v0/pubsub/sub') - }) - - it('returns 400 if no topic is provided', async () => { - const res = await api.inject({ - method: 'POST', - url: '/api/v0/pubsub/sub' - }) - - expect(res.statusCode).to.equal(400) - expect(res.result.Code).to.be.eql(1) - }) - - it('returns 200 with topic', async () => { - // TODO: Agree on a better way to test this (currently this hangs) - // Regarding: https://github.com/ipfs/js-ipfs/pull/644#issuecomment-267687194 - // Current Patch: Subscribe to a topic so the other tests run as expected - const ipfs = api.app.ipfs - const handler = (msg) => {} - - await ipfs.pubsub.subscribe(topic, handler) - - await new Promise((resolve, reject) => { - setTimeout(() => { - ipfs.pubsub.unsubscribe(topic, handler) - resolve() - }, 100) - }) - // const res = await api.inject({ - // method: 'POST', - // url: `/api/v0/pubsub/sub/${topic}` - // }) - // console.log(res.result) - // expect(res.statusCode).to.equal(200) - // done() - // }) - }) + it('returns 400 if no buffer is provided', async () => { + const res = await http({ + method: 'POST', + url: '/api/v0/pubsub/pub?arg=&arg=' + }, { ipfs }) + + expect(res).to.have.property('statusCode', 400) }) - describe('/pub', () => { - it('only accepts POST', () => { - return testHttpMethod('/api/v0/pubsub/pub') - }) - - it('returns 400 if no buffer is provided', async () => { - const res = await api.inject({ - method: 'POST', - url: '/api/v0/pubsub/pub?arg=&arg=' - }) - - expect(res.statusCode).to.equal(400) - expect(res.result.Code).to.be.eql(1) - }) - - it('returns 200 with topic and buffer', async () => { - const res = await api.inject({ - method: 'POST', - url: `/api/v0/pubsub/pub?arg=${topic}&arg=${buf}` - }) - - expect(res.statusCode).to.equal(200) - }) + it('returns 200 with topic and buffer', async () => { + const res = await http({ + method: 'POST', + url: `/api/v0/pubsub/pub?arg=${topic}&arg=${buf}` + }, { ipfs }) + + expect(res).to.have.property('statusCode', 200) + expect(ipfs.pubsub.publish.calledWith(topic, buf)).to.be.true() }) + }) - describe.skip('/ls', () => { - it('only accepts POST', () => { - return testHttpMethod('/api/v0/pubsub/ls') - }) - - it('returns 200', async () => { - const res = await api.inject({ - method: 'POST', - url: '/api/v0/pubsub/ls' - }) - expect(res.statusCode).to.equal(200) - expect(res.result.Strings).to.be.eql([topic]) - }) + describe('/ls', () => { + it('only accepts POST', () => { + return testHttpMethod('/api/v0/pubsub/ls') }) - describe('/peers', () => { - it('only accepts POST', () => { - return testHttpMethod('/api/v0/pubsub/peers') - }) - - it('returns 200 if not subscribed to a topic', async () => { - const res = await api.inject({ - method: 'POST', - url: `/api/v0/pubsub/peers?arg=${topicNotSubscribed}` - }) - - expect(res.statusCode).to.equal(200) - expect(res.result.Strings).to.be.eql([]) - }) - - it('returns 200 with topic', async () => { - const res = await api.inject({ - method: 'POST', - url: `/api/v0/pubsub/peers?arg=${topic}` - }) - - expect(res.statusCode).to.equal(200) - expect(res.result.Strings).to.be.eql([]) - }) + it('returns 200', async () => { + ipfs.pubsub.ls.returns([ + topic + ]) + + const res = await http({ + method: 'POST', + url: '/api/v0/pubsub/ls' + }, { ipfs }) + expect(res).to.have.property('statusCode', 200) + expect(res).to.have.deep.nested.property('result.Strings', [topic]) + }) + }) + + describe('/peers', () => { + it('only accepts POST', () => { + return testHttpMethod('/api/v0/pubsub/peers') + }) + + it('returns 200 if not subscribed to a topic', async () => { + ipfs.pubsub.peers.withArgs(topicNotSubscribed).returns([]) + + const res = await http({ + method: 'POST', + url: `/api/v0/pubsub/peers?arg=${topicNotSubscribed}` + }, { ipfs }) + + expect(res).to.have.property('statusCode', 200) + expect(res).to.have.deep.nested.property('result.Strings', []) + }) + + it('returns 200 with topic', async () => { + ipfs.pubsub.peers.withArgs(topic).returns([ + 'peer' + ]) + + const res = await http({ + method: 'POST', + url: `/api/v0/pubsub/peers?arg=${topic}` + }, { ipfs }) + + expect(res).to.have.property('statusCode', 200) + expect(res).to.have.deep.nested.property('result.Strings', [ + 'peer' + ]) }) }) -} +}) diff --git a/packages/ipfs/test/http-api/inject/resolve.js b/packages/ipfs/test/http-api/inject/resolve.js index 16f3d534bf..b257b14b14 100644 --- a/packages/ipfs/test/http-api/inject/resolve.js +++ b/packages/ipfs/test/http-api/inject/resolve.js @@ -2,43 +2,32 @@ 'use strict' const { expect } = require('interface-ipfs-core/src/utils/mocha') -const FormData = require('form-data') -const streamToPromise = require('stream-to-promise') const testHttpMethod = require('../../utils/test-http-method') +const http = require('../../utils/http') +const sinon = require('sinon') +const CID = require('cids') -module.exports = (http) => { - describe('/resolve', () => { - let api +describe('/resolve', () => { + const cid = new CID('QmfGBRT6BbWJd7yUc2uYdaUZJBbnEFvTqehPFoSMQ6wgdr') + let ipfs - before(() => { - api = http.api._httpApi._apiServers[0] - }) - - it('only accepts POST', () => { - return testHttpMethod('/api/v0/resolve') - }) + beforeEach(() => { + ipfs = { + resolve: sinon.stub() + } + }) - it('should not resolve a path for invalid cid-base option', async () => { - const form = new FormData() - form.append('data', Buffer.from('TEST' + Date.now())) - const headers = form.getHeaders() + it('only accepts POST', () => { + return testHttpMethod('/api/v0/resolve') + }) - const payload = await streamToPromise(form) - let res = await api.inject({ - method: 'POST', - url: '/api/v0/add', - headers: headers, - payload: payload - }) - expect(res.statusCode).to.equal(200) - const hash = JSON.parse(res.result).Hash + it('should not resolve a path for invalid cid-base option', async () => { + const res = await http({ + method: 'POST', + url: `/api/v0/resolve?arg=${cid}&cid-base=invalid` + }, { ipfs }) - res = await api.inject({ - method: 'POST', - url: `/api/v0/resolve?arg=/ipfs/${hash}&cid-base=invalid` - }) - expect(res.statusCode).to.equal(400) - expect(res.result.Message).to.include('Invalid request query input') - }) + expect(res).to.have.property('statusCode', 400) + expect(res).to.have.nested.property('result.Message').that.includes('Invalid request query input') }) -} +}) diff --git a/packages/ipfs/test/http-api/inject/version.js b/packages/ipfs/test/http-api/inject/version.js index 02a8b4003c..b463376e17 100644 --- a/packages/ipfs/test/http-api/inject/version.js +++ b/packages/ipfs/test/http-api/inject/version.js @@ -2,30 +2,37 @@ 'use strict' const { expect } = require('interface-ipfs-core/src/utils/mocha') -const pkgversion = require('./../../../package.json').version const testHttpMethod = require('../../utils/test-http-method') +const http = require('../../utils/http') +const sinon = require('sinon') -module.exports = (http) => { - describe('/version', () => { - let api +describe('/version', () => { + let ipfs - before(() => { - api = http.api._httpApi._apiServers[0] - }) + beforeEach(() => { + ipfs = { + version: sinon.stub() + } + }) - it('only accepts POST', () => { - return testHttpMethod('/api/v0/version') + it('only accepts POST', () => { + return testHttpMethod('/api/v0/version') + }) + + it('get the version', async () => { + ipfs.version.returns({ + version: 'version', + commit: 'commit', + repo: 'repo' }) - it('get the version', async () => { - const res = await api.inject({ - method: 'POST', - url: '/api/v0/version' - }) + const res = await http({ + method: 'POST', + url: '/api/v0/version' + }, { ipfs }) - expect(res.result).to.have.a.property('Version', pkgversion) - expect(res.result).to.have.a.property('Commit') - expect(res.result).to.have.a.property('Repo') - }) + expect(res).to.have.nested.property('result.Version', 'version') + expect(res).to.have.nested.property('result.Commit', 'commit') + expect(res).to.have.nested.property('result.Repo', 'repo') }) -} +}) diff --git a/packages/ipfs/test/http-api/routes.js b/packages/ipfs/test/http-api/routes.js index 6faeff31aa..1f09c83e5c 100644 --- a/packages/ipfs/test/http-api/routes.js +++ b/packages/ipfs/test/http-api/routes.js @@ -1,88 +1,21 @@ /* eslint-env mocha */ 'use strict' -const fs = require('fs') -const { nanoid } = require('nanoid') -const Daemon = require('../../src/cli/daemon') -const { promisify } = require('util') -const ncp = promisify(require('ncp').ncp) -const path = require('path') -const clean = require('../utils/clean') - +require('./inject/bitswap') +require('./inject/block') +require('./inject/bootstrap') +require('./inject/config') +require('./inject/dag') +require('./inject/dht') +require('./inject/dns') +require('./inject/files') +require('./inject/id') +require('./inject/key') require('./inject/mfs') - -describe('HTTP API', () => { - const repoExample = path.join(__dirname, '../fixtures/go-ipfs-repo') - const repoTests = path.join(__dirname, '../repo-tests-run') - - // bootstrap nodes get the set up too slow and gets timed out - const testsForCustomConfig = ['dht.js', 'files.js', 'name.js', 'pin.js', 'ping.js'] - - const http = {} - - const startHttpAPI = async (config) => { - http.api = new Daemon({ - repo: repoTests, - pass: nanoid(), - config, - preload: { enabled: false } - }) - await ncp(repoExample, repoTests) - await http.api.start() - } - - describe('custom config', () => { - const config = { - Bootstrap: [], - Discovery: { - MDNS: { - Enabled: false - }, - webRTCStar: { - Enabled: false - } - } - } - - before(async function () { - this.timeout(60 * 1000) - - await startHttpAPI(config) - }) - - after(async function () { - this.timeout(50 * 1000) - - await http.api.stop() - await clean(repoTests) - }) - - describe('## http-api spec tests for custom config', () => { - fs.readdirSync(path.join(`${__dirname}/inject/`)) - .forEach((file) => testsForCustomConfig.includes(file) && require(`./inject/${file}`)(http)) - }) - }) - - describe('default config', () => { - const config = { - Bootstrap: [] - } - - before(async function () { - this.timeout(60 * 1000) - await startHttpAPI(config) - }) - - after(async function () { - this.timeout(50 * 1000) - - await http.api.stop() - await clean(repoTests) - }) - - describe('## http-api spec tests for default config', () => { - fs.readdirSync(path.join(`${__dirname}/inject/`)) - .forEach((file) => !testsForCustomConfig.includes(file) && require(`./inject/${file}`)(http)) - }) - }) -}) +require('./inject/name') +require('./inject/object') +require('./inject/pin') +require('./inject/ping') +require('./inject/pubsub') +require('./inject/resolve') +require('./inject/version') diff --git a/packages/ipfs/test/utils/all-ndjson.js b/packages/ipfs/test/utils/all-ndjson.js new file mode 100644 index 0000000000..9b29caadd8 --- /dev/null +++ b/packages/ipfs/test/utils/all-ndjson.js @@ -0,0 +1,9 @@ +'use strict' + +module.exports = (res) => { + return res.result + .split('\n') + .map(line => line.trim()) + .filter(line => Boolean(line)) + .map(line => JSON.parse(line)) +} diff --git a/packages/ipfs/test/utils/test-http-method.js b/packages/ipfs/test/utils/test-http-method.js index 49d1d226a8..92a8f7a07e 100644 --- a/packages/ipfs/test/utils/test-http-method.js +++ b/packages/ipfs/test/utils/test-http-method.js @@ -19,6 +19,6 @@ module.exports = async (url, ipfs) => { url }, { ipfs }) - expect(res.statusCode).to.equal(405) + expect(res).to.have.property('statusCode', 405) } }