diff --git a/src/adapters/ipfs-files/index.js b/src/adapters/ipfs-files/index.js index 4e609b1..be9afab 100644 --- a/src/adapters/ipfs-files/index.js +++ b/src/adapters/ipfs-files/index.js @@ -48,6 +48,7 @@ class IpfsFilesAdapter { this.getFileMetadata = this.getFileMetadata.bind(this) this.getPins = this.getPins.bind(this) this.waitForRPCResponse = this.waitForRPCResponse.bind(this) + this.pinClaim = this.pinClaim.bind(this) } // This handler is triggered when RPC data comes in over IPFS. @@ -63,7 +64,7 @@ class IpfsFilesAdapter { this.rpcDataQueue.push(data) } catch (err) { console.error('Error in files/rpcHandler(): ', err) - // Do not throw error. This is a top-level function. + // Do not throw error. This is a top-level function. } } @@ -80,7 +81,7 @@ class IpfsFilesAdapter { // Add names to the IPFS IDs for each provider. const initialServiceProviders = - this.ipfs.ipfsCoordAdapter.state.ipfsFileProviders + this.ipfs.ipfsCoordAdapter.state.ipfsFileProviders const serviceProviders = [] for (let i = 0; i < initialServiceProviders.length; i++) { const thisProvider = initialServiceProviders[i] @@ -142,7 +143,7 @@ class IpfsFilesAdapter { // Throw an error if this IPFS node has not yet made a connection to a // wallet service provider. const selectedProvider = - this.ipfs.ipfsCoordAdapter.state.selectedIpfsFileProvider + this.ipfs.ipfsCoordAdapter.state.selectedIpfsFileProvider if (!selectedProvider) { throw new Error('No IPFS File Pin Service provider available yet.') } @@ -185,7 +186,7 @@ class IpfsFilesAdapter { // Throw an error if this IPFS node has not yet made a connection to a // wallet service provider. const selectedProvider = - this.ipfs.ipfsCoordAdapter.state.selectedIpfsFileProvider + this.ipfs.ipfsCoordAdapter.state.selectedIpfsFileProvider if (!selectedProvider) { throw new Error('No IPFS File Pin Service provider available yet.') } @@ -261,7 +262,7 @@ class IpfsFilesAdapter { cnt++ - // Exit if data was returned, or the window for a response expires. + // Exit if data was returned, or the window for a response expires. } while (!dataFound && cnt < 10) // console.log(`dataFound: ${dataFound}, cnt: ${cnt}`) @@ -271,6 +272,53 @@ class IpfsFilesAdapter { throw err } } + + async pinClaim (inObj = {}) { + try { + // Throw an error if this IPFS node has not yet made a connection to a + // ipfs service provider. + const selectedProvider = + this.ipfs.ipfsCoordAdapter.state.selectedIpfsFileProvider + if (!selectedProvider) { + throw new Error('No IPFS File Provider Service is available yet. Try again in a few seconds.') + } + + const { proofOfBurnTxid, cid, claimTxid, filename, address } = inObj + + const rpcData = { + endpoint: 'pinClaim', + proofOfBurnTxid, + cid, + claimTxid, + filename, + address + } + + // Generate a UUID for the call. + const rpcId = this.uid() + + // Generate a JSON RPC command. + const cmd = this.jsonrpc.request(rpcId, 'file-pin', rpcData) + const cmdStr = JSON.stringify(cmd) + // console.log('cmdStr: ', cmdStr) + + // Send the RPC command to selected wallet service. + const thisNode = this.ipfs.ipfsCoordAdapter.ipfsCoord.thisNode + await this.ipfs.ipfsCoordAdapter.ipfsCoord.useCases.peer.sendPrivateMessage( + selectedProvider, + cmdStr, + thisNode + ) + + // Wait for data to come back from the wallet service. + const data = await this.waitForRPCResponse(rpcId) + + return data + } catch (err) { + console.error('Error in pinClaim()') + throw err + } + } } // module.exports = P2wdbAdapter diff --git a/src/controllers/rest-api/ipfs/controller.js b/src/controllers/rest-api/ipfs/controller.js index c296d5b..452c656 100644 --- a/src/controllers/rest-api/ipfs/controller.js +++ b/src/controllers/rest-api/ipfs/controller.js @@ -41,6 +41,7 @@ class IpfsRESTControllerLib { this.getPins = this.getPins.bind(this) this.cid2json = this.cid2json.bind(this) this.downloadFile = this.downloadFile.bind(this) + this.pinClaim = this.pinClaim.bind(this) } /** @@ -300,6 +301,29 @@ class IpfsRESTControllerLib { } } + /** + * @api {post} /ipfs/pin-claim Process pin claim. + * @apiPermission public + * @apiName PinClaim + * @apiGroup REST IPFS + * @apiDescription Process pin claim. + * + * @apiExample Example usage: + * curl -H "Content-Type: application/json" -X POST -d '{ "proofOfBurnTxid": "be4b63156c93f58ed311d403d9f756deda9abbc81d0fef8fbe5d769538b4261c", "cid": "bafybeied3zdwdiro7fqytyha2yfband4lwcrtozmf6shynylt3kexh26dq", "claimTxid": "c71e2f2cdf8658d90c61ac6183b8ffeeb359779807b317386044705d8352f0f2", "filename": "mutable-67ccefcca67097473e78ca10.json", "address": "bitcoincash:qqs2wrahl6azn9qdyrmp9ygeejqvzr8ruv7e9m30fr" }' http://localhost:5001/ipfs/pin-claim * + */ + async pinClaim (ctx) { + try { + const body = ctx.request.body + + const result = await this.useCases.ipfs.pinClaim(body) + + ctx.body = result + } catch (err) { + console.log('Error in ipfs/controller.js/cid2json(): ', err.message) + this.handleError(ctx, err) + } + } + // DRY error handler handleError (ctx, err) { // console.log('handleError() err.status: ', err.status) diff --git a/src/controllers/rest-api/ipfs/index.js b/src/controllers/rest-api/ipfs/index.js index 016f321..de003a6 100644 --- a/src/controllers/rest-api/ipfs/index.js +++ b/src/controllers/rest-api/ipfs/index.js @@ -62,7 +62,7 @@ class IpfsRouter { this.router.get('/file-info/:cid', this.ipfsRESTController.getFileInfo) this.router.get('/pins', this.ipfsRESTController.getPins) this.router.get('/cid2json/:cid', this.ipfsRESTController.cid2json) - + this.router.post('/pin-claim', this.ipfsRESTController.pinClaim) // Attach the Controller routes to the Koa app. app.use(this.router.routes()) app.use(this.router.allowedMethods()) diff --git a/src/use-cases/ipfs-use-cases.js b/src/use-cases/ipfs-use-cases.js index 8611c70..cff9db0 100644 --- a/src/use-cases/ipfs-use-cases.js +++ b/src/use-cases/ipfs-use-cases.js @@ -37,6 +37,7 @@ class IpfsUseCases { this.getWritePrice = this.getWritePrice.bind(this) this.getCidMetadata = this.getCidMetadata.bind(this) this.cid2json = this.cid2json.bind(this) + this.pinClaim = this.pinClaim.bind(this) // State this.lastWritePriceUpdate = null // Used to periodically update write price. @@ -225,9 +226,11 @@ class IpfsUseCases { // Throw an error if this is not a JSON file const filename = fileMetadata.filename + if (!filename.endsWith('.json')) { throw new Error(`CID ${cid} does not resolve to a JSON file.`) } + // Retrieve the CID content and store it in a Buffer. const helia = this.adapters.ipfs.ipfs const fileChunks = [] @@ -281,6 +284,20 @@ class IpfsUseCases { } } + async pinClaim (inObj = {}) { + try { + // Throw an error if ipfs-bch-wallet-consumer can not communicate with the ipfs-file-pin-service. + const metadata = await this.adapters.ipfsFiles.pinClaim(inObj) + + return { + success: metadata.success, + message: metadata.message + } + } catch (err) { + console.error('Error in ipfs-use-cases.js/pinClaim(): ', err) + throw err + } + } // async downloadCid2 (inObj = {}) { // try { // const { cid } = inObj diff --git a/test/unit/controllers/rest-api/ipfs/ipfs.rest.controller.unit.js b/test/unit/controllers/rest-api/ipfs/ipfs.rest.controller.unit.js index 34483f5..8c8a1d0 100644 --- a/test/unit/controllers/rest-api/ipfs/ipfs.rest.controller.unit.js +++ b/test/unit/controllers/rest-api/ipfs/ipfs.rest.controller.unit.js @@ -248,4 +248,243 @@ describe('#IPFS REST API', () => { assert.property(ctx.body, 'thisNode') }) }) + describe('#viewFile', () => { + it('should return 422 status on biz logic error', async () => { + try { + // Force an error + ctx.params = { + cid: 'bafybeib2rphswe7clvclverw7xi7nejkpkh4mdfcurdmcgw7wcup7za6wy' + } + sandbox.stub(uut.useCases.ipfs, 'downloadCid').throws(new Error('test error')) + uut.adapters.ipfs.ipfsCoordAdapter = {} + + ctx.request.body = {} + + await uut.viewFile(ctx) + + assert.fail('Unexpected result') + } catch (err) { + // console.log('err: ', err) + assert.equal(err.status, 422) + assert.include(err.message, 'test error') + } + }) + + it('should return 200 status on success', async () => { + ctx.params = { + cid: 'bafybeib2rphswe7clvclverw7xi7nejkpkh4mdfcurdmcgw7wcup7za6wy' + } + // Mock dependencies + sandbox.stub(uut.useCases.ipfs, 'downloadCid').resolves({ success: true, readStream: 'readStream' }) + + uut.adapters.ipfs.ipfsCoordAdapter = { + ipfsCoord: { + thisNode: {} + } + } + + ctx.request.body = {} + + await uut.viewFile(ctx) + + assert.exists(ctx.body) + }) + }) + describe('#downloadFile', () => { + it('should return 422 status on biz logic error', async () => { + try { + ctx.params = { + cid: 'bafybeib2rphswe7clvclverw7xi7nejkpkh4mdfcurdmcgw7wcup7za6wy' + } + // Force an error + sandbox.stub(uut.useCases.ipfs, 'downloadCid').throws(new Error('test error')) + uut.adapters.ipfs.ipfsCoordAdapter = {} + + ctx.request.body = {} + + await uut.downloadFile(ctx) + + assert.fail('Unexpected result') + } catch (err) { + // console.log('err: ', err) + assert.equal(err.status, 422) + assert.include(err.message, 'test error') + } + }) + + it('should return 200 status on success', async () => { + ctx.params = { + cid: 'bafybeib2rphswe7clvclverw7xi7nejkpkh4mdfcurdmcgw7wcup7za6wy' + } + // Mock dependencies + sandbox.stub(uut.useCases.ipfs, 'downloadCid').resolves({ success: true, readStream: 'readStream' }) + + uut.adapters.ipfs.ipfsCoordAdapter = { + ipfsCoord: { + thisNode: {} + } + } + + ctx.request.body = {} + + await uut.downloadFile(ctx) + + assert.exists(ctx.body) + }) + }) + describe('#getService', () => { + it('should return 422 status on biz logic error', async () => { + try { + // Force an error + uut.adapters.ipfs.ipfsCoordAdapter = {} + + ctx.request.body = {} + + await uut.getService(ctx) + + assert.fail('Unexpected result') + } catch (err) { + // console.log('err: ', err) + assert.equal(err.status, 422) + assert.include(err.message, 'Cannot read properties of undefined') + } + }) + + it('should return 200 status on success', async () => { + // Mock dependencies + + uut.adapters.ipfs.ipfsCoordAdapter = { + state: { + selectedIpfsFileProvider: 'provider' + } + } + + ctx.request.body = {} + + await uut.getService(ctx) + + assert.isTrue(ctx.body.success) + }) + }) + describe('#getFileInfo', () => { + it('should return 422 status on biz logic error', async () => { + try { + ctx.params = { + cid: 'bafybeib2rphswe7clvclverw7xi7nejkpkh4mdfcurdmcgw7wcup7za6wy' + } + sandbox.stub(uut.adapters.ipfsFiles, 'getFileMetadata').throws(new Error('test error')) + + ctx.request.body = {} + + await uut.getFileInfo(ctx) + + assert.fail('Unexpected result') + } catch (err) { + // console.log('err: ', err) + assert.equal(err.status, 422) + assert.include(err.message, 'test error') + } + }) + + it('should return 200 status on success', async () => { + ctx.params = { + cid: 'bafybeib2rphswe7clvclverw7xi7nejkpkh4mdfcurdmcgw7wcup7za6wy' + } + // Mock dependencies + sandbox.stub(uut.adapters.ipfsFiles, 'getFileMetadata').resolves({ success: true, filename: 'filename' }) + + ctx.request.body = {} + + await uut.getFileInfo(ctx) + + assert.isTrue(ctx.body.success) + }) + }) + describe('#getPins', () => { + it('should return 422 status on biz logic error', async () => { + try { + sandbox.stub(uut.adapters.ipfsFiles, 'getPins').throws(new Error('test error')) + + ctx.request.body = {} + + await uut.getPins(ctx) + + assert.fail('Unexpected result') + } catch (err) { + assert.equal(err.status, 422) + assert.include(err.message, 'test error') + } + }) + + it('should return 200 status on success', async () => { + // Mock dependencies + sandbox.stub(uut.adapters.ipfsFiles, 'getPins').resolves({ success: true }) + + ctx.request.body = {} + + await uut.getPins(ctx) + + assert.isTrue(ctx.body.success) + }) + }) + describe('#cid2json', () => { + it('should return 422 status on biz logic error', async () => { + try { + ctx.params = { + cid: 'bafybeib2rphswe7clvclverw7xi7nejkpkh4mdfcurdmcgw7wcup7za6wy' + } + sandbox.stub(uut.useCases.ipfs, 'cid2json').throws(new Error('test error')) + + ctx.request.body = {} + + await uut.cid2json(ctx) + + assert.fail('Unexpected result') + } catch (err) { + assert.equal(err.status, 422) + assert.include(err.message, 'test error') + } + }) + + it('should return 200 status on success', async () => { + ctx.params = { + cid: 'bafybeib2rphswe7clvclverw7xi7nejkpkh4mdfcurdmcgw7wcup7za6wy' + } + // Mock dependencies + sandbox.stub(uut.useCases.ipfs, 'cid2json').resolves({ success: true }) + + ctx.request.body = {} + + await uut.cid2json(ctx) + + assert.isTrue(ctx.body.success) + }) + }) + describe('#pinClaim', () => { + it('should return 422 status on biz logic error', async () => { + try { + sandbox.stub(uut.useCases.ipfs, 'pinClaim').throws(new Error('test error')) + + ctx.request.body = {} + + await uut.pinClaim(ctx) + + assert.fail('Unexpected result') + } catch (err) { + assert.equal(err.status, 422) + assert.include(err.message, 'test error') + } + }) + + it('should return 200 status on success', async () => { + // Mock dependencies + sandbox.stub(uut.useCases.ipfs, 'pinClaim').resolves({ success: true }) + + ctx.request.body = {} + + await uut.pinClaim(ctx) + + assert.isTrue(ctx.body.success) + }) + }) }) diff --git a/test/unit/mocks/adapters/index.js b/test/unit/mocks/adapters/index.js index 68890f6..2d8964d 100644 --- a/test/unit/mocks/adapters/index.js +++ b/test/unit/mocks/adapters/index.js @@ -1,10 +1,26 @@ /* Mocks for the Adapter library. */ - +class BlockstoreMock { + constructor () { + this.get = async () => {} + } +} +class FsMock { + constructor () { + this.ls = async function* () { + yield { path: 'test.txt', cid: 'QmS4ghgMgfFvqPjB4WKXHaN15ZyT4K4JYZxY5X5x5x5x5' } + } + this.cat = async function* () { + yield Buffer.from('test data') + } + } +} class IpfsAdapter { constructor () { this.ipfs = { + blockstore: new BlockstoreMock(), + fs: new FsMock(), files: { stat: () => {} } @@ -34,7 +50,9 @@ class IpfsCoordAdapter { this.peerInputHandler = () => {} this.state = { - serviceProviders: [] + serviceProviders: [], + serviceProvidersByType: '', + selectedIpfsFileProvider:'' } } } @@ -138,5 +156,29 @@ class BchUseCaseMock { } } const bch = new BchUseCaseMock() +const wallet = { + bchWallet: bch -export default { ipfs, localdb, bch } +} + +const ipfsFiles = { + pinClaim: () => { + return { + success: true, + message: 'Pin claimed' + } + }, + getFileMetadata: () => { + return { + success: true, + message: 'Files metadata' + } + }, + getPins: () => { + return { + success: true, + message: 'Pins' + } + } +} +export default { ipfs, localdb, bch, wallet, ipfsFiles } diff --git a/test/unit/mocks/use-cases/index.js b/test/unit/mocks/use-cases/index.js index 7ba11dc..b64e5f0 100644 --- a/test/unit/mocks/use-cases/index.js +++ b/test/unit/mocks/use-cases/index.js @@ -67,6 +67,28 @@ class UsageUseCaseMock { } } +class IpfsUseCaseMock { + async downloadCid() { + return {} + } + + async cid2json() { + return {} + } + + async getCidMetadata() { + return {} + } + + async getService() { + return {} + } + + async pinClaim() { + return {} + } +} + class UseCasesMock { constuctor(localConfig = {}) { // this.user = new UserUseCaseMock(localConfig) @@ -77,6 +99,7 @@ class UseCasesMock { user = new UserUseCaseMock() bch = new BchUseCaseMock() usage = new UsageUseCaseMock() + ipfs = new IpfsUseCaseMock() } export default UseCasesMock; diff --git a/test/unit/use-cases/ipfs.use-case.unit.js b/test/unit/use-cases/ipfs.use-case.unit.js new file mode 100644 index 0000000..ca81a37 --- /dev/null +++ b/test/unit/use-cases/ipfs.use-case.unit.js @@ -0,0 +1,270 @@ +/* + Unit tests for the ipfs-use-cases.js file. +*/ + +// Public npm libraries +import { assert } from 'chai' + +import sinon from 'sinon' + +// Local support libraries +// const testUtils = require('../../utils/test-utils') + +// Unit under test (uut) +import UseCases from '../../../src/use-cases/ipfs-use-cases.js' + +import adapters from '../mocks/adapters/index.js' + +describe('#ipfs-use-cases', () => { + let uut + let sandbox + + before(async () => { + // Delete all previous users in the database. + // await testUtils.deleteAllUsers() + }) + + beforeEach(() => { + sandbox = sinon.createSandbox() + + uut = new UseCases({ adapters }) + uut.adapters.ipfs.ipfsCoordAdapter = { + state: { + selectedIpfsFileProvider: 'provider' + } + } + }) + + afterEach(() => sandbox.restore()) + + describe('#constructor', () => { + it('should throw an error if adapters are not passed in', () => { + try { + uut = new UseCases() + + assert.fail('Unexpected code path') + + // This is here to prevent the linter from complaining. + assert.isOk(uut) + } catch (err) { + assert.include( + err.message, + 'Instance of adapters must be passed in when instantiating IPFS Use Cases library.' + ) + } + }) + }) + + describe('#downloadCid', () => { + it('should download a file from IPFS', async () => { + sandbox.stub(uut, 'getCidMetadata').resolves('file.txt') + + const result = await uut.downloadCid({ cid: 'bafybeib2rphswe7clvclverw7xi7nejkpkh4mdfcurdmcgw7wcup7za6wy' }) + + assert.property(result, 'filename') + assert.property(result, 'readStream') + }) + it('should handle directory-based CIDs', async () => { + sandbox.stub(uut, 'getCidMetadata').resolves('file.txt') + sandbox.stub(uut.adapters.ipfs.ipfs.fs, 'ls').callsFake(async function * () { + yield { path: 'dir1/test.txt', cid: 'QmS4ghgMgfFvqPjB4WKXHaN15ZyT4K4JYZxY5X5x5x5x5' } + }) + const result = await uut.downloadCid({ cid: 'bafybeib2rphswe7clvclverw7xi7nejkpkh4mdfcurdmcgw7wcup7za6wy' }) + + assert.property(result, 'filename') + assert.property(result, 'readStream') + }) + it('should throw an error if cid is not provided ', async () => { + try { + await uut.downloadCid({}) + } catch (error) { + assert.include(error.message, 'CID is undefined') + } + }) + }) + describe('#getWritePrice', () => { + it('should get price', async () => { + const result = await uut.getWritePrice() + assert.isNumber(result) + }) + it('should handle undefined psffpp instance', async () => { + uut.psffpp = null + const result = await uut.getWritePrice() + assert.isNumber(result) + }) + + it('should handle error ', async () => { + try { + uut.psffpp = { + getMcWritePrice: () => { + throw new Error('test error') + } + } + await uut.getWritePrice() + } catch (error) { + assert.include(error.message, 'test error') + } + }) + }) + describe('#getCidMetadata', () => { + it('should get cid metadata', async () => { + sandbox.stub(uut.adapters.ipfsFiles, 'getFileMetadata').resolves({ success: true, fileMetadata: { dataPinned: true, filename: 'test.json' } }) + const result = await uut.getCidMetadata({ cid: 'bafybeib2rphswe7clvclverw7xi7nejkpkh4mdfcurdmcgw7wcup7za6wy' }) + assert.isString(result) + assert.equal(result, 'test.json') + }) + + it('should handle error ', async () => { + try { + sandbox.stub(uut.adapters.ipfsFiles, 'getFileMetadata').throws(new Error('test error')) + await uut.getCidMetadata({ cid: 'bafybeib2rphswe7clvclverw7xi7nejkpkh4mdfcurdmcgw7wcup7za6wy' }) + } catch (error) { + assert.include(error.message, 'test error') + } + }) + + it('should handle non-success response from getFileMetadata', async () => { + try { + sandbox.stub(uut.adapters.ipfsFiles, 'getFileMetadata').resolves({ success: false, message: 'test error' }) + await uut.getCidMetadata({ cid: 'bafybeib2rphswe7clvclverw7xi7nejkpkh4mdfcurdmcgw7wcup7za6wy' }) + } catch (error) { + assert.include(error.message, 'test error') + } + }) + }) + describe('#cid2json', () => { + it('should get cid2json', async () => { + uut.adapters.ipfs.ipfsCoordAdapter = { + state: { + selectedIpfsFileProvider: 'provider' + } + } + sandbox.stub(uut.adapters.ipfsFiles, 'getFileMetadata').resolves({ fileMetadata: { dataPinned: true, filename: 'test.json' } }) + sandbox.stub(uut.adapters.ipfs.ipfs.fs, 'ls').callsFake(function * () { + yield { name: 'test.json' } + }) + sandbox.stub(JSON, 'parse').returns(true) + const result = await uut.cid2json({ cid: 'bafybeib2rphswe7clvclverw7xi7nejkpkh4mdfcurdmcgw7wcup7za6wy' }) + assert.isObject(result) + assert.property(result, 'success') + assert.property(result, 'json') + }) + + it('should throw error if selectedIpfsFileProvider is not defined', async () => { + try { + uut.adapters.ipfs.ipfsCoordAdapter = { + state: { + selectedIpfsFileProvider: null + } + } + await uut.cid2json({ cid: 'bafybeib2rphswe7clvclverw7xi7nejkpkh4mdfcurdmcgw7wcup7za6wy' }) + assert.fail('Unexpected code path') + } catch (error) { + assert.include(error.message, 'No IPFS File Provider Service is available yet. Try again in a few seconds') + } + }) + it('should throw error if file metadata cant be fetched', async () => { + try { + sandbox.stub(uut.adapters.ipfsFiles, 'getFileMetadata').resolves({ success: false }) + await uut.cid2json({ cid: 'bafybeib2rphswe7clvclverw7xi7nejkpkh4mdfcurdmcgw7wcup7za6wy' }) + assert.fail('Unexpected code path') + } catch (error) { + assert.include(error.message, 'Could not communicate with instance of ipfs-file-pin-service') + } + }) + it('should throw error if cid is not pinned', async () => { + try { + sandbox.stub(uut.adapters.ipfsFiles, 'getFileMetadata').resolves({ success: true, fileMetadata: { dataPinned: false } }) + await uut.cid2json({ cid: 'bafybeib2rphswe7clvclverw7xi7nejkpkh4mdfcurdmcgw7wcup7za6wy' }) + assert.fail('Unexpected code path') + } catch (error) { + assert.include(error.message, 'has not been pinned by ipfs-file-pin-service instance') + } + }) + it('should throw error if filename has no .json extension', async () => { + try { + sandbox.stub(uut.adapters.ipfsFiles, 'getFileMetadata').resolves({ success: true, fileMetadata: { dataPinned: true, filename: 'test' } }) + await uut.cid2json({ cid: 'bafybeib2rphswe7clvclverw7xi7nejkpkh4mdfcurdmcgw7wcup7za6wy' }) + assert.fail('Unexpected code path') + } catch (error) { + assert.include(error.message, 'does not resolve to a JSON file') + } + }) + it('should handle error if file cant be retrieved', async () => { + try { + sandbox.stub(uut.adapters.ipfsFiles, 'getFileMetadata').resolves({ fileMetadata: { dataPinned: true, filename: 'test.json' } }) + sandbox.stub(uut.adapters.ipfs.ipfs.fs, 'cat').throws(new Error('test error')) + sandbox.stub(uut.adapters.ipfs.ipfs.fs, 'ls').callsFake(function * () { + yield { name: 'test.json' } + }) + await uut.cid2json({ cid: 'bafybeib2rphswe7clvclverw7xi7nejkpkh4mdfcurdmcgw7wcup7za6wy' }) + assert.fail('Unexpected code path') + } catch (error) { + assert.include(error.message, 'test error') + } + }) + + it('should handle error if cid is a directory', async () => { + try { + sandbox.stub(uut.adapters.ipfsFiles, 'getFileMetadata').resolves({ fileMetadata: { dataPinned: true, filename: 'test.json' } }) + sandbox.stub(uut.adapters.ipfs.ipfs.fs, 'cat') + .onCall(0).throws(new Error('test error')) + .onCall(1).callsFake(function * () { + yield { name: 'test.json' } + }) + sandbox.stub(uut.adapters.ipfs.ipfs.fs, 'ls').callsFake(function * () { + yield { name: 'test/directory/' } + }) + await uut.cid2json({ cid: 'bafybeib2rphswe7clvclverw7xi7nejkpkh4mdfcurdmcgw7wcup7za6wy' }) + } catch (error) { + assert.include(error.message, 'file does not resolve to a valid JSON file') + } + }) + it('should get content from cid/name.json format', async () => { + sandbox.stub(uut.adapters.ipfsFiles, 'getFileMetadata').resolves({ fileMetadata: { dataPinned: true, filename: 'test.json' } }) + sandbox.stub(uut.adapters.ipfs.ipfs.fs, 'cat') + .onCall(0).throws(new Error('test error')) + .onCall(1).callsFake(function * () { + yield Buffer.from('{"test": "test"}') + }) + sandbox.stub(uut.adapters.ipfs.ipfs.fs, 'ls').callsFake(function * () { + yield { name: 'test/directory/test.json' } + }) + + const result = await uut.cid2json({ cid: 'bafybeib2rphswe7clvclverw7xi7nejkpkh4mdfcurdmcgw7wcup7za6wy' }) + assert.isObject(result) + assert.property(result, 'success') + assert.property(result, 'json') + }) + it('should handle if content cant be parsed', async () => { + try { + sandbox.stub(uut.adapters.ipfsFiles, 'getFileMetadata').resolves({ fileMetadata: { dataPinned: true, filename: 'test.json' } }) + sandbox.stub(uut.adapters.ipfs.ipfs.fs, 'ls').callsFake(function * () { + yield { name: 'test.json' } + }) + sandbox.stub(JSON, 'parse').throws(new Error()) + + await uut.cid2json({ cid: 'bafybeib2rphswe7clvclverw7xi7nejkpkh4mdfcurdmcgw7wcup7za6wy' }) + } catch (error) { + assert.include(error.message, 'not resolve to a valid JSON object') + } + }) + }) + describe('#pinClaim', () => { + it('should process pinClaim', async () => { + const result = await uut.pinClaim() + assert.isObject(result) + assert.property(result, 'success') + assert.property(result, 'message') + }) + + it('should handle error ', async () => { + try { + sandbox.stub(uut.adapters.ipfsFiles, 'pinClaim').throws(new Error('test error')) + await uut.pinClaim() + } catch (error) { + assert.include(error.message, 'test error') + } + }) + }) +})