diff --git a/SPEC/PIN.md b/SPEC/PIN.md index 0958ce80b..70a108ecf 100644 --- a/SPEC/PIN.md +++ b/SPEC/PIN.md @@ -8,11 +8,11 @@ > Adds an IPFS object to the pinset and also stores it to the IPFS repo. pinset is the set of hashes currently pinned (not gc'able). -##### `ipfs.pin.add(hash, [options])` +##### `ipfs.pin.add(source, [options])` Where: -- `hash` is an IPFS multihash. +- `source` is a [CID], an array of CIDs or an (async) iterable that yields CIDs - `options` is an object that can contain the following keys - `recursive` (`boolean`) - Recursively pin the object linked. Type: bool. Default: `true` - `timeout` (`number`|`string`) - Throw an error if the request does not complete within the specified milliseconds timeout. If `timeout` is a string, the value is parsed as a [human readable duration](https://www.npmjs.com/package/parse-duration). There is no timeout by default. @@ -21,9 +21,9 @@ Where: | Type | Description | | -------- | -------- | -| `Promise<{ cid: CID }>` | An array of objects that represent the files that were pinned | +| `AsyncIterable<{ cid: CID }>` | An async iterable that yields objects containing the CIDs that were pinned | -an array of objects is returned, each of the form: +Each yielded object has the form: ```JavaScript { @@ -34,10 +34,12 @@ an array of objects is returned, each of the form: **Example:** ```JavaScript -const pinset = await ipfs.pin.add('QmWATWQ7fVPP2EFGu71UkfnqhYXDYH566qy47CnJDgvs8u') -console.log(pinset) +for await (const pin of ipfs.pin.add(new CID('QmWATWQ7fVPP2EFGu71UkfnqhYXDYH566qy47CnJDgvs8u'))) { + console.log(pin) +} + // Logs: -// [ { cid: CID('QmWATWQ7fVPP2EFGu71UkfnqhYXDYH566qy47CnJDgvs8u') } ] +// { cid: CID('QmWATWQ7fVPP2EFGu71UkfnqhYXDYH566qy47CnJDgvs8u') } ``` A great source of [examples][] can be found in the tests for this API. @@ -77,10 +79,10 @@ A great source of [examples][] can be found in the tests for this API. > Remove a hash from the pinset -##### `ipfs.pin.rm(hash, [options])` +##### `ipfs.pin.rm(source, [options])` Where: -- `hash` is a multihash. +- `source` is a [CID], an array of CIDs or an (async) iterable that yields CIDs - `options` is an object that can contain the following keys - 'recursive' - Recursively unpin the object linked. Type: bool. Default: `true` @@ -88,15 +90,17 @@ Where: | Type | Description | | -------- | -------- | -| `Promise<{ cid: CID }>` | An array of unpinned objects | +| `AsyncIterable<{ cid: CID }>` | An async iterable that yields objects containing the CIDs that were unpinned | **Example:** ```JavaScript -const pinset = await ipfs.pin.rm('QmWATWQ7fVPP2EFGu71UkfnqhYXDYH566qy47CnJDgvs8u') -console.log(pinset) +for await (const unpinned of ipfs.pin.rm(new CID('QmWATWQ7fVPP2EFGu71UkfnqhYXDYH566qy47CnJDgvs8u'))) { + console.log(unpinned) +} + // prints the hashes that were unpinned -// [ { cid: CID('QmWATWQ7fVPP2EFGu71UkfnqhYXDYH566qy47CnJDgvs8u') } ] +// { cid: CID('QmWATWQ7fVPP2EFGu71UkfnqhYXDYH566qy47CnJDgvs8u') } ``` A great source of [examples][] can be found in the tests for this API. diff --git a/package.json b/package.json index 912c95a57..95c149c33 100644 --- a/package.json +++ b/package.json @@ -53,6 +53,7 @@ "is-ipfs": "~0.6.1", "it-all": "^1.0.1", "it-concat": "^1.0.0", + "it-drain": "^1.0.0", "it-last": "^1.0.1", "it-pushable": "^1.3.1", "multiaddr": "^7.2.1", diff --git a/src/block/rm.js b/src/block/rm.js index 37cd17d98..ee43bdf20 100644 --- a/src/block/rm.js +++ b/src/block/rm.js @@ -4,6 +4,8 @@ const { getDescribe, getIt, expect } = require('../utils/mocha') const hat = require('hat') const all = require('it-all') +const last = require('it-last') +const drain = require('it-drain') /** @typedef { import("ipfsd-ctl/src/factory") } Factory */ /** @@ -142,13 +144,12 @@ module.exports = (common, options) => { format: 'raw', hashAlg: 'sha2-256' }) - await ipfs.pin.add(cid.toString()) + await drain(ipfs.pin.add(cid)) - const result = await all(ipfs.block.rm(cid)) + const result = await last(ipfs.block.rm(cid)) - expect(result).to.be.an('array').and.to.have.lengthOf(1) - expect(result[0]).to.have.property('error') - expect(result[0].error.message).to.include('pinned') + expect(result).to.have.property('error').that.is.an('Error') + .with.property('message').that.includes('pinned') }) }) } diff --git a/src/name/resolve.js b/src/name/resolve.js index 334e6e322..f27b71a70 100644 --- a/src/name/resolve.js +++ b/src/name/resolve.js @@ -6,6 +6,7 @@ const delay = require('delay') const CID = require('cids') const all = require('it-all') const last = require('it-last') +const drain = require('it-drain') /** @typedef { import("ipfsd-ctl/src/factory") } Factory */ /** @@ -121,7 +122,7 @@ module.exports = (common, options) => { // go only has 1 possible error https://github.com/ipfs/go-ipfs/blob/master/namesys/interface.go#L51 // so here we just expect an Error and don't match the error type to expiration try { - await last(ipfs.name.resolve(nodeId)) + await drain(ipfs.name.resolve(nodeId)) } catch (error) { expect(error).to.exist() } @@ -170,7 +171,7 @@ module.exports = (common, options) => { it('should fail to resolve /ipns/ipfs.a', async () => { try { - await last(ipfs.name.resolve('ipfs.a')) + await drain(ipfs.name.resolve('ipfs.a')) } catch (error) { expect(error).to.exist() } diff --git a/src/pin/add.js b/src/pin/add.js index 5e4cb2f3d..6c2e3559a 100644 --- a/src/pin/add.js +++ b/src/pin/add.js @@ -28,8 +28,8 @@ module.exports = (common, options) => { after(() => common.clean()) it('should add a pin', async () => { - const pinset = await ipfs.pin.add(fixtures.files[0].cid, { recursive: false }) - expect(pinset.map(p => p.cid.toString())).to.include(fixtures.files[0].cid) + const pinset = await all(ipfs.pin.add(fixtures.files[0].cid, { recursive: false })) + expect(pinset.map(p => p.cid)).to.deep.include(fixtures.files[0].cid) }) }) } diff --git a/src/pin/index.js b/src/pin/index.js index b3723d07c..ad97a96df 100644 --- a/src/pin/index.js +++ b/src/pin/index.js @@ -4,7 +4,8 @@ const { createSuite } = require('../utils/suite') const tests = { ls: require('./ls'), rm: require('./rm'), - add: require('./add') + add: require('./add'), + core: require('./pins') } module.exports = createSuite(tests) diff --git a/src/pin/ls.js b/src/pin/ls.js index ffdfe7986..29d21f2e9 100644 --- a/src/pin/ls.js +++ b/src/pin/ls.js @@ -4,6 +4,7 @@ const { fixtures } = require('./utils') const { getDescribe, getIt, expect } = require('../utils/mocha') const all = require('it-all') +const drain = require('it-drain') /** @typedef { import("ipfsd-ctl/src/factory") } Factory */ /** @@ -23,22 +24,21 @@ module.exports = (common, options) => { ipfs = (await common.spawn()).api // two files wrapped in directories, only root CID pinned recursively const dir = fixtures.directory.files.map((file) => ({ path: file.path, content: file.data })) - await all(ipfs.add(dir, { pin: false, cidVersion: 0 })) - await ipfs.pin.add(fixtures.directory.cid, { recursive: true }) + await drain(ipfs.add(dir, { pin: false, cidVersion: 0 })) + await drain(ipfs.pin.add(fixtures.directory.cid, { recursive: true })) // a file (CID pinned recursively) - await all(ipfs.add(fixtures.files[0].data, { pin: false, cidVersion: 0 })) - await ipfs.pin.add(fixtures.files[0].cid, { recursive: true }) + await drain(ipfs.add(fixtures.files[0].data, { pin: false, cidVersion: 0 })) + await drain(ipfs.pin.add(fixtures.files[0].cid, { recursive: true })) // a single CID (pinned directly) - await all(ipfs.add(fixtures.files[1].data, { pin: false, cidVersion: 0 })) - await ipfs.pin.add(fixtures.files[1].cid, { recursive: false }) + await drain(ipfs.add(fixtures.files[1].data, { pin: false, cidVersion: 0 })) + await drain(ipfs.pin.add(fixtures.files[1].cid, { recursive: false })) }) after(() => common.clean()) // 1st, because ipfs.add pins automatically it('should list all recursive pins', async () => { - const pinset = (await all(ipfs.pin.ls({ type: 'recursive' }))) - .map(p => ({ ...p, cid: p.cid.toString() })) + const pinset = await all(ipfs.pin.ls({ type: 'recursive' })) expect(pinset).to.deep.include({ type: 'recursive', @@ -51,8 +51,7 @@ module.exports = (common, options) => { }) it('should list all indirect pins', async () => { - const pinset = (await all(ipfs.pin.ls({ type: 'indirect' }))) - .map(p => ({ ...p, cid: p.cid.toString() })) + const pinset = await all(ipfs.pin.ls({ type: 'indirect' })) expect(pinset).to.not.deep.include({ type: 'recursive', @@ -77,8 +76,7 @@ module.exports = (common, options) => { }) it('should list all types of pins', async () => { - const pinset = (await all(ipfs.pin.ls())) - .map(p => ({ ...p, cid: p.cid.toString() })) + const pinset = await all(ipfs.pin.ls()) expect(pinset).to.not.be.empty() // check the three "roots" @@ -107,15 +105,19 @@ module.exports = (common, options) => { it('should list all direct pins', async () => { const pinset = await all(ipfs.pin.ls({ type: 'direct' })) expect(pinset).to.have.lengthOf(1) - expect(pinset[0].type).to.equal('direct') - expect(pinset[0].cid.toString()).to.equal(fixtures.files[1].cid) + expect(pinset).to.deep.include({ + type: 'direct', + cid: fixtures.files[1].cid + }) }) it('should list pins for a specific hash', async () => { const pinset = await all(ipfs.pin.ls(fixtures.files[0].cid)) expect(pinset).to.have.lengthOf(1) - expect(pinset[0].type).to.equal('recursive') - expect(pinset[0].cid.toString()).to.equal(fixtures.files[0].cid) + expect(pinset).to.deep.include({ + type: 'recursive', + cid: fixtures.files[0].cid + }) }) it('should throw an error on missing direct pins for existing path', () => { @@ -136,22 +138,26 @@ module.exports = (common, options) => { it('should list indirect pins for a specific path', async () => { const pinset = await all(ipfs.pin.ls(`/ipfs/${fixtures.directory.cid}/files/ipfs.txt`, { type: 'indirect' })) expect(pinset).to.have.lengthOf(1) - expect(pinset[0].type).to.equal(`indirect through ${fixtures.directory.cid}`) - expect(pinset[0].cid.toString()).to.equal(fixtures.directory.files[1].cid) + expect(pinset).to.deep.include({ + type: `indirect through ${fixtures.directory.cid}`, + cid: fixtures.directory.files[1].cid + }) }) it('should list recursive pins for a specific hash', async () => { const pinset = await all(ipfs.pin.ls(fixtures.files[0].cid, { type: 'recursive' })) expect(pinset).to.have.lengthOf(1) - expect(pinset[0].type).to.equal('recursive') - expect(pinset[0].cid.toString()).to.equal(fixtures.files[0].cid) + expect(pinset).to.deep.include({ + type: 'recursive', + cid: fixtures.files[0].cid + }) }) it('should list pins for multiple CIDs', async () => { const pinset = await all(ipfs.pin.ls([fixtures.files[0].cid, fixtures.files[1].cid])) - const cids = pinset.map(p => p.cid.toString()) - expect(cids).to.include(fixtures.files[0].cid) - expect(cids).to.include(fixtures.files[1].cid) + const cids = pinset.map(p => p.cid) + expect(cids).to.deep.include(fixtures.files[0].cid) + expect(cids).to.deep.include(fixtures.files[1].cid) }) }) } diff --git a/src/pin/pins.js b/src/pin/pins.js new file mode 100644 index 000000000..a171d0b91 --- /dev/null +++ b/src/pin/pins.js @@ -0,0 +1,460 @@ +/* eslint-env mocha */ +'use strict' + +const { getDescribe, getIt, expect } = require('../utils/mocha') +const { + DAGNode +} = require('ipld-dag-pb') +const all = require('it-all') +const last = require('it-last') +const drain = require('it-drain') +const CID = require('cids') + +// fixture structure: +// planets/ +// solar-system.md +// mercury/ +// wiki.md +/* const pins = { + root: new CID('QmTAMavb995EHErSrKo7mB8dYkpaSJxu6ys1a6XJyB2sys'), + solarWiki: new CID('QmTMbkDfvHwq3Aup6Nxqn3KKw9YnoKzcZvuArAfQ9GF3QG'), + mercuryDir: new CID('QmbJCNKXJqVK8CzbjpNFz2YekHwh3CSHpBA86uqYg3sJ8q'), + mercuryWiki: new CID('QmVgSHAdMxFAuMP2JiMAYkB8pCWP1tcB9djqvq8GKAFiHi') +} */ +let pins +const pinTypes = { + direct: 'direct', + recursive: 'recursive', + indirect: 'indirect', + all: 'all' +} + +/** @typedef { import("ipfsd-ctl/src/factory") } Factory */ +/** + * @param {Factory} common + * @param {Object} options + */ +module.exports = (common, options) => { + const describe = getDescribe(options) + const it = getIt(options) + + describe('pin', function () { + this.timeout(50 * 1000) + + const fixtures = [{ + path: 'planets/mercury/wiki.md', + content: 'solar system content' + }, { + path: 'planets/solar-system.md', + content: 'wiki content' + }] + + let ipfs + + async function isPinnedWithType (path, type) { + try { + for await (const _ of ipfs.pin.ls(path, { type })) { // eslint-disable-line no-unused-vars + return true + } + return false + } catch (err) { + return false + } + } + + async function expectPinned (cid, type = pinTypes.all, pinned = true) { + if (typeof type === 'boolean') { + pinned = type + type = pinTypes.all + } + + const result = await isPinnedWithType(cid, type) + expect(result).to.eql(pinned) + } + + async function clearPins () { + for await (const { cid } of ipfs.pin.ls({ type: pinTypes.recursive })) { + await drain(ipfs.pin.rm(cid)) + } + + for await (const { cid } of ipfs.pin.ls({ type: pinTypes.direct })) { + await drain(ipfs.pin.rm(cid)) + } + } + + before(async () => { + ipfs = (await common.spawn()).api + const added = (await all(ipfs.add(fixtures))).reduce((acc, curr) => { + acc[curr.path] = curr.cid + + return acc + }, {}) + + pins = { + root: added.planets, + solarWiki: added['planets/solar-system.md'], + mercuryDir: added['planets/mercury'], + mercuryWiki: added['planets/mercury/wiki.md'] + } + }) + + after(() => common.clean()) + + describe('pinned status', function () { + beforeEach(async () => { + await clearPins() + await drain(ipfs.pin.add(pins.root)) + }) + + it('should be pinned when added', async () => { + await drain(ipfs.pin.add(pins.solarWiki)) + return expectPinned(pins.solarWiki) + }) + + it('should not be pinned when not in datastore', () => { + const falseHash = `${`${pins.root}`.slice(0, -2)}ss` + return expectPinned(falseHash, false) + }) + + it('should not be pinned when in datastore but not added', async () => { + await drain(ipfs.pin.rm(pins.root)) + return expectPinned(pins.root, false) + }) + + it('should be pinned recursively when added', () => { + return expectPinned(pins.root, pinTypes.recursive) + }) + + it('should be pinned indirectly', () => { + return expectPinned(pins.mercuryWiki, pinTypes.indirect) + }) + + it('should be pinned directly', async () => { + await drain(ipfs.pin.add(pins.mercuryDir, { recursive: false })) + return expectPinned(pins.mercuryDir, pinTypes.direct) + }) + + it('should not be pinned when not in datastore or added', async () => { + await clearPins() + return expectPinned(pins.mercuryDir, pinTypes.direct, false) + }) + }) + + describe('add', function () { + beforeEach(function () { + return clearPins() + }) + + it('should add recursively', async () => { + await drain(ipfs.pin.add(pins.root)) + await expectPinned(pins.root, pinTypes.recursive) + + const pinChecks = Object.values(pins).map(hash => expectPinned(hash)) + return Promise.all(pinChecks) + }) + + it('should add directly', async () => { + await drain(ipfs.pin.add(pins.root, { recursive: false })) + await Promise.all([ + expectPinned(pins.root, pinTypes.direct), + expectPinned(pins.solarWiki, false) + ]) + }) + + it('should recursively pin parent of direct pin', async () => { + await drain(ipfs.pin.add(pins.solarWiki, { recursive: false })) + await drain(ipfs.pin.add(pins.root)) + await Promise.all([ + // solarWiki is pinned both directly and indirectly o.O + expectPinned(pins.solarWiki, pinTypes.direct), + expectPinned(pins.solarWiki, pinTypes.indirect) + ]) + }) + + it('should fail to directly pin a recursive pin', async () => { + await drain(ipfs.pin.add(pins.root)) + return expect(last(ipfs.pin.add(pins.root, { recursive: false }))) + .to.eventually.be.rejected() + .with(/already pinned recursively/) + }) + + it('should fail to pin a hash not in datastore', function () { + this.timeout(5 * 1000) + const falseHash = `${`${pins.root}`.slice(0, -2)}ss` + return expect(last(ipfs.pin.add(falseHash, { timeout: '2s' }))) + .to.eventually.be.rejected() + // TODO: http api TimeoutErrors do not have this property + // .with.a.property('code').that.equals('ERR_TIMEOUT') + }) + + // TODO block rm breaks subsequent tests + // it.skip('needs all children in datastore to pin recursively', () => { + // return ipfs.block.rm(pins.mercuryWiki) + // .then(() => expectTimeout(pin.add(pins.root), 4000)) + // }) + }) + + describe('ls', function () { + before(async () => { + await clearPins() + await drain(ipfs.pin.add(pins.root)) + await drain(ipfs.pin.add(pins.mercuryDir, { recursive: false })) + }) + + it('should list pins of a particular CID', async () => { + const out = await all(ipfs.pin.ls(pins.mercuryDir)) + expect(out[0].cid).to.deep.equal(pins.mercuryDir) + expect(out[0].type).to.eql(pinTypes.direct) + }) + + it('should list indirect pins that supersede direct pins', async () => { + const ls = await all(ipfs.pin.ls()) + + const pinType = ls.find(out => out.cid.equals(pins.mercuryDir)).type + expect(pinType).to.eql(pinTypes.indirect) + }) + + it('should list all pins', async () => { + const out = await all(ipfs.pin.ls()) + + expect(out).to.deep.include.members([ + { + type: 'recursive', + cid: new CID(pins.root) + }, + { + type: 'indirect', + cid: new CID(pins.solarWiki) + }, + { + type: 'indirect', + cid: new CID(pins.mercuryDir) + }, + { + type: 'indirect', + cid: new CID(pins.mercuryWiki) + } + ]) + }) + + it('should list all direct pins', async () => { + const out = await all(ipfs.pin.ls({ type: 'direct' })) + + expect(out).to.deep.include.members([ + { + type: 'direct', + cid: new CID(pins.mercuryDir) + } + ]) + }) + + it('should list all recursive pins', async () => { + const out = await all(ipfs.pin.ls({ type: 'recursive' })) + + expect(out).to.deep.include.members([ + { + type: 'recursive', + cid: new CID(pins.root) + } + ]) + }) + + it('should list all indirect pins', async () => { + const out = await all(ipfs.pin.ls({ type: 'indirect' })) + + expect(out).to.deep.include.members([ + { + type: 'indirect', + cid: new CID(pins.solarWiki) + }, + { + type: 'indirect', + cid: new CID(pins.mercuryDir) + }, + { + type: 'indirect', + cid: new CID(pins.mercuryWiki) + } + ]) + }) + + it('should list direct pins for CID', async () => { + const out = await all(ipfs.pin.ls(pins.mercuryDir, { type: 'direct' })) + + expect(out).to.have.deep.members([ + { + type: 'direct', + cid: new CID(pins.mercuryDir) + } + ]) + }) + + it('should list direct pins for path', async () => { + const out = await all(ipfs.pin.ls(`/ipfs/${pins.root}/mercury/`, { type: 'direct' })) + + expect(out).to.have.deep.members([ + { + type: 'direct', + cid: new CID(pins.mercuryDir) + } + ]) + }) + + it('should list direct pins for path (no match)', () => { + return expect(all(ipfs.pin.ls(`/ipfs/${pins.root}/mercury/wiki.md`, { type: 'direct' }))) + .to.eventually.be.rejected() + }) + + it('should list direct pins for CID (no match)', () => { + return expect(all(ipfs.pin.ls(pins.root, { type: 'direct' }))) + .to.eventually.be.rejected() + }) + + it('should list recursive pins for CID', async () => { + const out = await all(ipfs.pin.ls(pins.root, { type: 'recursive' })) + + expect(out).to.have.deep.members([ + { + type: 'recursive', + cid: new CID(pins.root) + } + ]) + }) + + it('should list recursive pins for CID (no match)', () => { + return expect(all(ipfs.pin.ls(pins.mercuryDir, { type: 'recursive' }))) + .to.eventually.be.rejected() + }) + + it('should list indirect pins for CID', async () => { + const out = await all(ipfs.pin.ls(pins.solarWiki, { type: 'indirect' })) + + expect(out).to.have.deep.members([ + { + type: `indirect through ${pins.root}`, + cid: new CID(pins.solarWiki) + } + ]) + }) + + it('should list indirect pins for CID (no match)', () => { + return expect(all(ipfs.pin.ls(pins.root, { type: 'indirect' }))) + .to.eventually.be.rejected() + }) + }) + + describe('rm', function () { + beforeEach(async () => { + await clearPins() + await drain(ipfs.pin.add(pins.root)) + }) + + it('should remove a recursive pin', async () => { + await drain(ipfs.pin.rm(pins.root)) + await Promise.all([ + expectPinned(pins.root, false), + expectPinned(pins.mercuryWiki, false) + ]) + }) + + it('should remove a direct pin', async () => { + await clearPins() + await drain(ipfs.pin.add(pins.mercuryDir, { recursive: false })) + await drain(ipfs.pin.rm(pins.mercuryDir)) + await expectPinned(pins.mercuryDir, false) + }) + + it('should fail to remove an indirect pin', async () => { + await expect(last(ipfs.pin.rm(pins.solarWiki))) + .to.eventually.be.rejected() + .with(/is pinned indirectly under/) + await expectPinned(pins.solarWiki) + }) + + it('should fail when an item is not pinned', async () => { + await drain(ipfs.pin.rm(pins.root)) + await expect(last(ipfs.pin.rm(pins.root))) + .to.eventually.be.rejected() + .with(/is not pinned/) + }) + }) + + describe('non-dag-pb nodes', function () { + it('should pin dag-cbor', async () => { + const cid = await ipfs.dag.put({}, { + format: 'dag-cbor', + hashAlg: 'sha2-256' + }) + + await drain(ipfs.pin.add(cid)) + + const pins = await all(ipfs.pin.ls()) + + expect(pins).to.deep.include({ + type: 'recursive', + cid + }) + }) + + it('should pin raw', async () => { + const cid = await ipfs.dag.put(Buffer.alloc(0), { + format: 'raw', + hashAlg: 'sha2-256' + }) + + await drain(ipfs.pin.add(cid)) + + const pins = await all(ipfs.pin.ls()) + + expect(pins).to.deep.include({ + type: 'recursive', + cid + }) + }) + + it('should pin dag-cbor with dag-pb child', async () => { + const child = await ipfs.dag.put(new DAGNode(Buffer.from(`${Math.random()}`)), { + format: 'dag-pb', + hashAlg: 'sha2-256' + }) + const parent = await ipfs.dag.put({ + child + }, { + format: 'dag-cbor', + hashAlg: 'sha2-256' + }) + + await drain(ipfs.pin.add(parent, { + recursive: true + })) + + const pins = await all(ipfs.pin.ls()) + + expect(pins).to.deep.include({ + cid: parent, + type: 'recursive' + }) + expect(pins).to.deep.include({ + cid: child, + type: 'indirect' + }) + }) + }) + + describe('ls', () => { + it('should throw error for invalid non-string pin type option', () => { + return expect(all(ipfs.pin.ls({ type: 6 }))) + .to.eventually.be.rejected() + // TODO: go-ipfs does not return error codes + // .with.property('code').that.equals('ERR_INVALID_PIN_TYPE') + }) + + it('should throw error for invalid string pin type option', () => { + return expect(all(ipfs.pin.ls({ type: '__proto__' }))) + .to.eventually.be.rejected() + // TODO: go-ipfs does not return error codes + // .with.property('code').that.equals('ERR_INVALID_PIN_TYPE') + }) + }) + }) +} diff --git a/src/pin/rm.js b/src/pin/rm.js index 756538168..91bc802ea 100644 --- a/src/pin/rm.js +++ b/src/pin/rm.js @@ -4,6 +4,7 @@ const { fixtures } = require('./utils') const { getDescribe, getIt, expect } = require('../utils/mocha') const all = require('it-all') +const drain = require('it-drain') /** @typedef { import("ipfsd-ctl/src/factory") } Factory */ /** @@ -20,31 +21,31 @@ module.exports = (common, options) => { let ipfs before(async () => { ipfs = (await common.spawn()).api - await all(ipfs.add(fixtures.files[0].data, { pin: false })) - await ipfs.pin.add(fixtures.files[0].cid, { recursive: true }) - await all(ipfs.add(fixtures.files[1].data, { pin: false })) - await ipfs.pin.add(fixtures.files[1].cid, { recursive: false }) + await drain(ipfs.add(fixtures.files[0].data, { pin: false })) + await drain(ipfs.pin.add(fixtures.files[0].cid, { recursive: true })) + await drain(ipfs.add(fixtures.files[1].data, { pin: false })) + await drain(ipfs.pin.add(fixtures.files[1].cid, { recursive: false })) }) after(() => common.clean()) it('should remove a recursive pin', async () => { - const removedPinset = await ipfs.pin.rm(fixtures.files[0].cid, { recursive: true }) - expect(removedPinset.map(p => p.cid.toString())).to.deep.equal([fixtures.files[0].cid]) + const removedPinset = await all(ipfs.pin.rm(fixtures.files[0].cid, { recursive: true })) + expect(removedPinset.map(p => p.cid)).to.deep.equal([fixtures.files[0].cid]) const pinset = await all(ipfs.pin.ls({ type: 'recursive' })) - expect(pinset.map(p => ({ ...p, cid: p.cid.toString() }))).to.not.deep.include({ + expect(pinset).to.not.deep.include({ cid: fixtures.files[0].cid, type: 'recursive' }) }) it('should remove a direct pin', async () => { - const removedPinset = await ipfs.pin.rm(fixtures.files[1].cid, { recursive: false }) - expect(removedPinset.map(p => p.cid.toString())).to.deep.equal([fixtures.files[1].cid]) + const removedPinset = await all(ipfs.pin.rm(fixtures.files[1].cid, { recursive: false })) + expect(removedPinset.map(p => p.cid)).to.deep.equal([fixtures.files[1].cid]) const pinset = await all(ipfs.pin.ls({ type: 'direct' })) - expect(pinset.map(p => p.cid.toString())).to.not.include(fixtures.files[1].cid) + expect(pinset.map(p => p.cid)).to.not.deep.include(fixtures.files[1].cid) }) }) } diff --git a/src/pin/utils.js b/src/pin/utils.js index 3ee67f0d5..34b33eee6 100644 --- a/src/pin/utils.js +++ b/src/pin/utils.js @@ -1,26 +1,27 @@ 'use strict' const loadFixture = require('aegir/fixtures') +const CID = require('cids') exports.fixtures = Object.freeze({ // NOTE: files under 'directory' need to be different than standalone ones in 'files' directory: Object.freeze({ - cid: 'QmY8KdYQSYKFU5hM7F5ioZ5yYSgV5VZ1kDEdqfRL3rFgcd', + cid: new CID('QmY8KdYQSYKFU5hM7F5ioZ5yYSgV5VZ1kDEdqfRL3rFgcd'), files: Object.freeze([Object.freeze({ path: 'test-folder/ipfs-add.js', data: loadFixture('test/fixtures/test-folder/ipfs-add.js', 'interface-ipfs-core'), - cid: 'QmbKtKBrmeRHjNCwR4zAfCJdMVu6dgmwk9M9AE9pUM9RgG' + cid: new CID('QmbKtKBrmeRHjNCwR4zAfCJdMVu6dgmwk9M9AE9pUM9RgG') }), Object.freeze({ path: 'test-folder/files/ipfs.txt', data: loadFixture('test/fixtures/test-folder/files/ipfs.txt', 'interface-ipfs-core'), - cid: 'QmdFyxZXsFiP4csgfM5uPu99AvFiKH62CSPDw5TP92nr7w' + cid: new CID('QmdFyxZXsFiP4csgfM5uPu99AvFiKH62CSPDw5TP92nr7w') })]) }), files: Object.freeze([Object.freeze({ data: loadFixture('test/fixtures/testfile.txt', 'interface-ipfs-core'), - cid: 'Qma4hjFTnCasJ8PVp3mZbZK5g2vGDT4LByLJ7m8ciyRFZP' + cid: new CID('Qma4hjFTnCasJ8PVp3mZbZK5g2vGDT4LByLJ7m8ciyRFZP') }), Object.freeze({ data: loadFixture('test/fixtures/test-folder/files/hello.txt', 'interface-ipfs-core'), - cid: 'QmY9cxiHqTFoWamkQVkpmmqzBrY3hCBEL2XNu3NtX74Fuu' + cid: new CID('QmY9cxiHqTFoWamkQVkpmmqzBrY3hCBEL2XNu3NtX74Fuu') })]) }) diff --git a/src/repo/gc.js b/src/repo/gc.js index da4966250..dcf55652a 100644 --- a/src/repo/gc.js +++ b/src/repo/gc.js @@ -4,6 +4,7 @@ const { getDescribe, getIt, expect } = require('../utils/mocha') const { DAGNode } = require('ipld-dag-pb') const all = require('it-all') +const drain = require('it-drain') /** @typedef { import("ipfsd-ctl/src/factory") } Factory */ /** @@ -29,8 +30,8 @@ module.exports = (common, options) => { const pinset = await all(ipfs.pin.ls()) expect(pinset.map(obj => obj.cid.toString())).includes(res[0].cid.toString()) - await ipfs.pin.rm(res[0].cid) - await all(ipfs.repo.gc()) + await drain(ipfs.pin.rm(res[0].cid)) + await drain(ipfs.repo.gc()) const finalPinset = await all(ipfs.pin.ls()) expect(finalPinset.map(obj => obj.cid.toString())).not.includes(res[0].cid.toString()) @@ -53,7 +54,7 @@ module.exports = (common, options) => { expect(refsAfterAdd.map(r => r.ref)).includes(cid.toString()) // Run garbage collection - await all(ipfs.repo.gc()) + await drain(ipfs.repo.gc()) // Get the list of local blocks after GC, should still contain the hash, // because the file is still pinned @@ -61,7 +62,7 @@ module.exports = (common, options) => { expect(refsAfterGc.map(r => r.ref)).includes(cid.toString()) // Unpin the data - await ipfs.pin.rm(cid) + await drain(ipfs.pin.rm(cid)) // Run garbage collection await all(ipfs.repo.gc()) @@ -88,7 +89,7 @@ module.exports = (common, options) => { expect(refsAfterAdd.map(r => r.ref)).includes(hash) // Run garbage collection - await all(ipfs.repo.gc()) + await drain(ipfs.repo.gc()) // Get the list of local blocks after GC, should still contain the hash, // because the file is in MFS @@ -99,7 +100,7 @@ module.exports = (common, options) => { await ipfs.files.rm('/test') // Run garbage collection - await all(ipfs.repo.gc()) + await drain(ipfs.repo.gc()) // The list of local blocks should no longer contain the hash const refsAfterUnpinAndGc = await all(ipfs.refs.local()) @@ -131,7 +132,7 @@ module.exports = (common, options) => { expect(hashesAfterAdd).includes(dataCid.toString()) // Run garbage collection - await all(ipfs.repo.gc()) + await drain(ipfs.repo.gc()) // Get the list of local blocks after GC, should still contain the hash, // because the file is pinned and in MFS @@ -143,7 +144,7 @@ module.exports = (common, options) => { await ipfs.files.rm('/test') // Run garbage collection - await all(ipfs.repo.gc()) + await drain(ipfs.repo.gc()) // Get the list of local blocks after GC, should still contain the hash, // because the file is still pinned @@ -153,10 +154,10 @@ module.exports = (common, options) => { expect(hashesAfterRmAndGc).includes(dataCid.toString()) // Unpin the data - await ipfs.pin.rm(dataCid) + await drain(ipfs.pin.rm(dataCid)) // Run garbage collection - await all(ipfs.repo.gc()) + await drain(ipfs.repo.gc()) // The list of local blocks should no longer contain the hashes const refsAfterUnpinAndGc = await all(ipfs.refs.local()) @@ -174,7 +175,7 @@ module.exports = (common, options) => { const dataCid = addRes[0].cid // Unpin the data - await ipfs.pin.rm(dataCid) + await drain(ipfs.pin.rm(dataCid)) // Create a link to the data from an object const obj = await new DAGNode(Buffer.from('fruit'), [{ @@ -198,14 +199,14 @@ module.exports = (common, options) => { expect(hashesAfterAdd).includes(dataCid.toString()) // Recursively pin the object - await ipfs.pin.add(objCid, { recursive: true }) + await drain(ipfs.pin.add(objCid, { recursive: true })) // The data should now be indirectly pinned const pins = await all(ipfs.pin.ls()) expect(pins.find(p => p.cid.toString() === dataCid.toString()).type).to.eql('indirect') // Run garbage collection - await all(ipfs.repo.gc()) + await drain(ipfs.repo.gc()) // Get the list of local blocks after GC, should still contain the data // hash, because the data is still (indirectly) pinned @@ -213,10 +214,10 @@ module.exports = (common, options) => { expect(refsAfterGc.map(r => r.ref)).includes(dataCid.toString()) // Recursively unpin the object - await ipfs.pin.rm(objCid.toString()) + await drain(ipfs.pin.rm(objCid.toString())) // Run garbage collection - await all(ipfs.repo.gc()) + await drain(ipfs.repo.gc()) // The list of local blocks should no longer contain the hashes const refsAfterUnpinAndGc = await all(ipfs.refs.local())