From 0e51dc7f1527e2d373a6a9162edc1416336d08d0 Mon Sep 17 00:00:00 2001 From: Daniel Gonzalez Date: Tue, 23 Sep 2025 16:34:09 -0400 Subject: [PATCH] Improved adapters unit tests --- src/adapters/bch/index.js | 3 + src/adapters/ipfs-files/index.js | 8 + src/adapters/ipfs/ipfs-coord.js | 10 +- src/adapters/p2wdb/index.js | 7 + test/unit/adapters/bch.adapter.unit.js | 186 ++++++++++++++ test/unit/adapters/ipfs-coord.adapter.unit.js | 220 +++++++++++++--- test/unit/adapters/ipfs-files.adapter.unit.js | 243 ++++++++++++++++++ test/unit/adapters/p2wdb.adapter.unit.js | 223 ++++++++++++++++ test/unit/mocks/adapters/index.js | 2 +- test/unit/mocks/adapters/ipfs-coord-mocks.js | 112 +++++++- 10 files changed, 979 insertions(+), 35 deletions(-) create mode 100644 test/unit/adapters/ipfs-files.adapter.unit.js create mode 100644 test/unit/adapters/p2wdb.adapter.unit.js diff --git a/src/adapters/bch/index.js b/src/adapters/bch/index.js index e2f6124..333a739 100644 --- a/src/adapters/bch/index.js +++ b/src/adapters/bch/index.js @@ -110,6 +110,9 @@ class BchAdapter { // Choose the BCH wallet service to use. async selectProvider (providerId) { try { + if (!providerId || typeof providerId !== 'string') { + throw new Error('providerId must be a string!') + } this.ipfs.ipfsCoordAdapter.config.preferredProvider = providerId return true diff --git a/src/adapters/ipfs-files/index.js b/src/adapters/ipfs-files/index.js index fde62ab..6e119aa 100644 --- a/src/adapters/ipfs-files/index.js +++ b/src/adapters/ipfs-files/index.js @@ -119,6 +119,10 @@ class IpfsFilesAdapter { // Choose the ipfs-file-pin-service to use. async selectProvider (providerId) { try { + if (!providerId || typeof providerId !== 'string') { + throw new Error('providerId must be a string!') + } + this.ipfs.ipfsCoordAdapter.config.selectedIpfsFileProvider = providerId return true @@ -228,6 +232,10 @@ class IpfsFilesAdapter { // Returns a promise that resolves to data when the RPC response is recieved. async waitForRPCResponse (rpcId) { try { + if (!rpcId) { + throw new Error('rpcId can not be false or undefined') + } + // Initialize variables for tracking the return data. let dataFound = false let cnt = 0 diff --git a/src/adapters/ipfs/ipfs-coord.js b/src/adapters/ipfs/ipfs-coord.js index dc409e3..80a9495 100644 --- a/src/adapters/ipfs/ipfs-coord.js +++ b/src/adapters/ipfs/ipfs-coord.js @@ -219,9 +219,10 @@ class IpfsCoordAdapter { } } } catch (err) { + console.log('Error in pollForP2wdbServices()', err) // catch and handle known failure mode. if ( - err.message.includes("Cannot read property 'peerList' of undefined") + err.message.includes('Cannot read') ) { return } @@ -286,6 +287,9 @@ class IpfsCoordAdapter { _this.config.preferredProvider && thisPeer === _this.config.preferredProvider ) { + console.log('preferredProvider: ', _this.config.preferredProvider) + console.log('thisPeer: ', thisPeer) + console.log('selectedServiceProvider: ', _this.state.selectedServiceProvider) if (_this.state.selectedServiceProvider !== thisPeer) { console.log(`---->BCH wallet service switched to preferred peer: ${thisPeer}`) } @@ -310,7 +314,7 @@ class IpfsCoordAdapter { } catch (err) { // catch and handle known failure mode. if ( - err.message.includes("Cannot read property 'peerList' of undefined") + err.message.includes('Cannot read') ) { return } @@ -400,7 +404,7 @@ class IpfsCoordAdapter { } catch (err) { // catch and handle known failure mode. if ( - err.message.includes("Cannot read property 'peerList' of undefined") + err.message.includes('Cannot read') ) { return } diff --git a/src/adapters/p2wdb/index.js b/src/adapters/p2wdb/index.js index 0a57e6a..313f181 100644 --- a/src/adapters/p2wdb/index.js +++ b/src/adapters/p2wdb/index.js @@ -109,6 +109,9 @@ class P2wdbAdapter { // Choose the BCH wallet service to use. async selectProvider (providerId) { try { + if (!providerId || typeof providerId !== 'string') { + throw new Error('providerId must be a string!') + } this.ipfs.ipfsCoordAdapter.config.preferredP2wdbProvider = providerId return true @@ -215,6 +218,10 @@ class P2wdbAdapter { // Returns a promise that resolves to data when the RPC response is recieved. async waitForRPCResponse (rpcId) { try { + if (!rpcId) { + throw new Error('rpcId can not be false or undefined') + } + // Initialize variables for tracking the return data. let dataFound = false let cnt = 0 diff --git a/test/unit/adapters/bch.adapter.unit.js b/test/unit/adapters/bch.adapter.unit.js index 18d3349..b0863ce 100644 --- a/test/unit/adapters/bch.adapter.unit.js +++ b/test/unit/adapters/bch.adapter.unit.js @@ -17,6 +17,7 @@ import cloneDeep from 'lodash.clonedeep' import BchAdapter from '../../../src/adapters/bch/index.js' import adapters from '../mocks/adapters/index.js' import mockDataLib from '../mocks/use-cases/rest-api-mocks.js' +import ipfsCoordMocks from '../mocks/adapters/ipfs-coord-mocks.js' const eventEmitter = new EventEmitter() @@ -91,7 +92,30 @@ describe('#bch-use-case', () => { assert.property(result, 'balances') assert.isArray(result.balances) }) + it('should return false if data is not found', async () => { + // Mock dependencies + uut.ipfs.ipfsCoordAdapter.wallet = { + bchjs: { + Util: { + sleep: () => {} + } + } + } + // Mock data. + const rpcId = '123' + // Fill the queue with false values. + for (let i = 0; i < 10; i++) { + uut.rpcDataQueue.push({ payload: { id: '1234567890' } }) + } + sandbox.stub(uut, 'sleep').resolves() + + const result = await uut.waitForRPCResponse(rpcId) + // console.log('result: ', result) + + assert.property(result, 'success') + assert.isFalse(result.success) + }) it('should catch and throw an error', async () => { try { await uut.waitForRPCResponse() @@ -114,12 +138,46 @@ describe('#bch-use-case', () => { uut.rpcHandler(data) }) + it('should skip errors', async () => { + try { + sandbox.stub(uut, 'rpcDataQueue').throws(new Error('test error')) + await uut.rpcHandler() + } catch (err) { + assert.fail('Unexpected code path') + } + }) }) describe('#getStatus', () => { it('should get the status from ipfs-coord', async () => { + uut.ipfs.ipfsCoordAdapter.state.serviceProviders = ipfsCoordMocks.peers + uut.ipfs.ipfsCoordAdapter.ipfsCoord.thisNode.peerData = ipfsCoordMocks.peerData const result = await uut.getStatus() console.log('result: ', result) + assert.isObject(result) + }) + it('should handle error', async () => { + try { + uut.ipfs.ipfsCoordAdapter.ipfsCoord.thisNode = null + await uut.getStatus() + assert.fail('Unexpected code path') + } catch (err) { + assert.include(err.message, 'Cannot read') + } + }) + }) + describe('#selectProvider', () => { + it('should select provider', async () => { + await uut.selectProvider('providerId to select') + assert.equal(uut.ipfs.ipfsCoordAdapter.config.preferredProvider, 'providerId to select') + }) + it('should handle error', async () => { + try { + await uut.selectProvider() + assert.fail('Unexpected code path') + } catch (err) { + assert.include(err.message, 'providerId must be a string!') + } }) }) @@ -154,6 +212,18 @@ describe('#bch-use-case', () => { assert.equal(err.message, 'test error') } }) + it('should throw an error if selected provider is not defined', async () => { + try { + uut.ipfs.ipfsCoordAdapter.state.selectedServiceProvider = null + + await uut.getBalances() + + assert.fail('Unexpected code path') + } catch (err) { + // console.log(err) + assert.equal(err.message, 'No BCH Wallet Service provider available yet.') + } + }) }) describe('#getUtxos', () => { @@ -184,6 +254,19 @@ describe('#bch-use-case', () => { assert.equal(err.message, 'addr required when calling getUtxos') } }) + it('should throw an error if selected provider is not defined', async () => { + try { + uut.ipfs.ipfsCoordAdapter.state.selectedServiceProvider = null + + const addr = 'addr' + + await uut.getUtxos(addr) + assert.fail('Unexpected code path') + } catch (err) { + // console.log(err) + assert.equal(err.message, 'No BCH Wallet Service provider available yet.') + } + }) }) describe('#getUtxosBulk', () => { @@ -230,6 +313,19 @@ describe('#bch-use-case', () => { assert.equal(err.message, 'addresses parameter must not exceed 20 elements') } }) + it('should throw an error if selected provider is not defined', async () => { + try { + uut.ipfs.ipfsCoordAdapter.state.selectedServiceProvider = null + + const addrs = ['addr'] + + await uut.getUtxosBulk(addrs) + assert.fail('Unexpected code path') + } catch (err) { + // console.log(err) + assert.equal(err.message, 'No BCH Wallet Service provider available yet.') + } + }) }) describe('#broadcast', () => { @@ -260,6 +356,19 @@ describe('#bch-use-case', () => { assert.equal(err.message, 'hex required when calling broadcast') } }) + it('should throw an error if selected provider is not defined', async () => { + try { + uut.ipfs.ipfsCoordAdapter.state.selectedServiceProvider = null + + const hex = 'abc123' + + await uut.broadcast(hex) + assert.fail('Unexpected code path') + } catch (err) { + // console.log(err) + assert.equal(err.message, 'No BCH Wallet Service provider available yet.') + } + }) }) describe('#getTransactions', () => { @@ -290,6 +399,19 @@ describe('#bch-use-case', () => { assert.equal(err.message, 'address required when calling getTransactions()') } }) + it('should throw an error if selected provider is not defined', async () => { + try { + uut.ipfs.ipfsCoordAdapter.state.selectedServiceProvider = null + + const addr = 'abc123' + + await uut.getTransactions(addr) + assert.fail('Unexpected code path') + } catch (err) { + // console.log(err) + assert.equal(err.message, 'No BCH Wallet Service provider available yet.') + } + }) }) describe('#getTransaction', () => { @@ -320,6 +442,19 @@ describe('#bch-use-case', () => { assert.equal(err.message, 'txids required when calling getTransaction()') } }) + it('should throw an error if selected provider is not defined', async () => { + try { + uut.ipfs.ipfsCoordAdapter.state.selectedServiceProvider = null + + const txid = 'abc123' + + await uut.getTransaction(txid) + assert.fail('Unexpected code path') + } catch (err) { + // console.log(err) + assert.equal(err.message, 'No BCH Wallet Service provider available yet.') + } + }) }) describe('#getPubKey', () => { @@ -350,6 +485,19 @@ describe('#bch-use-case', () => { assert.equal(err.message, 'address required when calling getPubKey()') } }) + it('should throw an error if selected provider is not defined', async () => { + try { + uut.ipfs.ipfsCoordAdapter.state.selectedServiceProvider = null + + const addr = 'abc123' + + await uut.getPubKey(addr) + assert.fail('Unexpected code path') + } catch (err) { + // console.log(err) + assert.equal(err.message, 'No BCH Wallet Service provider available yet.') + } + }) }) describe('#utxoIsValid', () => { @@ -380,6 +528,18 @@ describe('#bch-use-case', () => { assert.equal(err.message, 'utxo required when calling utxoIsValid()') } }) + it('should throw an error if selected provider is not defined', async () => { + try { + uut.ipfs.ipfsCoordAdapter.state.selectedServiceProvider = null + + const utxo = { a: 'b' } + await uut.utxoIsValid(utxo) + assert.fail('Unexpected code path') + } catch (err) { + // console.log(err) + assert.equal(err.message, 'No BCH Wallet Service provider available yet.') + } + }) }) describe('#getTokenData', () => { @@ -410,6 +570,19 @@ describe('#bch-use-case', () => { assert.equal(err.message, 'tokenId required when calling getTokenData()') } }) + it('should throw an error if selected provider is not defined', async () => { + try { + uut.ipfs.ipfsCoordAdapter.state.selectedServiceProvider = null + + const tokenId = 'blah' + + await uut.getTokenData(tokenId) + assert.fail('Unexpected code path') + } catch (err) { + // console.log(err) + assert.equal(err.message, 'No BCH Wallet Service provider available yet.') + } + }) }) describe('#getTokenData2', () => { @@ -440,5 +613,18 @@ describe('#bch-use-case', () => { assert.equal(err.message, 'tokenId required when calling getTokenData2()') } }) + it('should throw an error if selected provider is not defined', async () => { + try { + uut.ipfs.ipfsCoordAdapter.state.selectedServiceProvider = null + + const tokenId = 'blah' + + await uut.getTokenData2(tokenId) + assert.fail('Unexpected code path') + } catch (err) { + // console.log(err) + assert.equal(err.message, 'No BCH Wallet Service provider available yet.') + } + }) }) }) diff --git a/test/unit/adapters/ipfs-coord.adapter.unit.js b/test/unit/adapters/ipfs-coord.adapter.unit.js index fbb1677..e851b70 100644 --- a/test/unit/adapters/ipfs-coord.adapter.unit.js +++ b/test/unit/adapters/ipfs-coord.adapter.unit.js @@ -151,7 +151,21 @@ describe('#IPFS', () => { } }) }) + describe('#subscribeToChat', () => { + it('should subscribe to the chat channel', async () => { + // Mock dependencies + uut.ipfsCoord = { + adapters: { + pubsub: { + subscribeToPubsubChannel: async () => { + } + } + } + } + await uut.subscribeToChat() + }) + }) describe('#pollForBchServices', () => { it('should find and select the wallet service', () => { uut.ipfsCoord = { @@ -169,46 +183,194 @@ describe('#IPFS', () => { 'QmWkjYRRTaxVEuGK8ip2X3trVyJShFs6U9g1h9x6fK5mZ2' ) }) + it('should use preferredProvider if set', async () => { + // Mock dependencies + uut.ipfsCoord = { + thisNode: { + peerList: mockData.peers, + peerData: mockData.peerData + } + } + // Define already selected service provider. + uut.state.serviceProviders = [mockData.peers[2]] + uut.state.selectedServiceProvider = mockData.peers[2] + // Define preferred provider. + uut.config.preferredProvider = mockData.peers[0] + sandbox.stub(uut.semver, 'gt').returns(true) - it('should catch and report errors', () => { - uut.pollForBchServices() + await uut.pollForBchServices() + assert.equal(uut.state.selectedServiceProvider, uut.config.preferredProvider) + }) + + it('should log unknown errors', async () => { + // Mock dependencies + uut.ipfsCoord = { + thisNode: { + peerList: mockData.peers, + peerData: mockData.peerData + } + } + sandbox.stub(uut.semver, 'gt').throws(new Error('test error')) + + await uut.pollForBchServices() + + assert.equal(uut.state.selectedServiceProvider, '') + assert.isOk(true, 'Not throwing an error is a success.') + }) + it('should handle ipfsCoord errors', async () => { + uut.ipfsCoord = { + } + + await uut.pollForBchServices() + + assert.equal(uut.state.selectedServiceProvider, '') assert.isOk(true, 'Not throwing an error is a success.') }) }) - // describe('#peerInputHandler', () => { - // it('should emit an event trigger', () => { - // const data = 'some data' - // - // uut.peerInputHandler(data) - // - // assert.isOk(true, 'Not throwing an error is a success') - // }) - // - // it('should catch and report errors', () => { - // // Force an error - // sandbox.stub(uut.eventEmitter, 'emit').throws(new Error('test error')) - // - // uut.peerInputHandler() - // - // assert.isOk(true, 'Not throwing an error is a success.') - // }) - // }) - - describe('#subscribeToChat', () => { - it('should subscribe to the chat channel', async () => { + describe('#pollForP2wdbServices', () => { + it('should poll for P2WDB services', async () => { // Mock dependencies uut.ipfsCoord = { - adapters: { - pubsub: { - subscribeToPubsubChannel: async () => { - } - } + thisNode: { + peerList: mockData.peers, + peerData: mockData.peerData + } + } + sandbox.stub(uut.semver, 'gt').returns(true) + + await uut.pollForP2wdbServices() + + assert.equal(uut.state.selectedP2wdbProvider, mockData.peers[2]) + }) + it('should use preferredP2wdbProvider if set', async () => { + // Mock dependencies + uut.ipfsCoord = { + thisNode: { + peerList: mockData.peers, + peerData: mockData.peerData } } - await uut.subscribeToChat() + uut.config.preferredP2wdbProvider = mockData.peers[2] + sandbox.stub(uut.semver, 'gt').returns(true) + + await uut.pollForP2wdbServices() + + assert.equal(uut.state.selectedP2wdbProvider, uut.config.preferredP2wdbProvider) + }) + it('should handle a peer that does not match the protocol', async () => { + // Mock dependencies + uut.ipfsCoord = { + thisNode: { + peerList: mockData.peers, + peerData: mockData.peerData + } + } + sandbox.stub(uut.semver, 'gt').returns(false) + + await uut.pollForP2wdbServices() + + assert.equal(uut.state.selectedP2wdbProvider, '') + }) + it('should log unknown errors', async () => { + // Mock dependencies + uut.ipfsCoord = { + thisNode: { + peerList: mockData.peers, + peerData: mockData.peerData + } + } + sandbox.stub(uut.semver, 'gt').throws(new Error('test error')) + + await uut.pollForP2wdbServices() + + assert.equal(uut.state.selectedP2wdbProvider, '') + assert.isOk(true, 'Not throwing an error is a success.') + }) + it('should handle ipfsCoord errors', async () => { + uut.ipfsCoord = { + } + + await uut.pollForP2wdbServices() + + assert.equal(uut.state.selectedP2wdbProvider, '') + assert.isOk(true, 'Not throwing an error is a success.') + }) + }) + + describe('#pollForIpfsFileServices', () => { + it('should poll for IPFS File services', async () => { + // Mock dependencies + uut.ipfsCoord = { + thisNode: { + peerList: mockData.peers, + peerData: mockData.peerData + } + } + sandbox.stub(uut.semver, 'gt').returns(true) + + await uut.pollForIpfsFileServices() + + assert.equal(uut.state.selectedIpfsFileProvider, mockData.peers[3]) + }) + it('should use preferredIpfsFileProvider if set', async () => { + // Mock dependencies + uut.ipfsCoord = { + thisNode: { + peerList: mockData.peers, + peerData: mockData.peerData + } + } + // Define already selected service provider. + uut.state.ipfsFileProviders = [mockData.peers[0]] + uut.state.selectedIpfsFileProvider = mockData.peers[0] + + uut.config.preferredIpfsFileProvider = mockData.peers[3] + sandbox.stub(uut.semver, 'satisfies').returns(true) + + await uut.pollForIpfsFileServices() + + assert.equal(uut.state.selectedIpfsFileProvider, uut.config.preferredIpfsFileProvider) + }) + it('should handle a peer that does not match the protocol', async () => { + // Mock dependencies + uut.ipfsCoord = { + thisNode: { + peerList: mockData.peers, + peerData: mockData.peerData + } + } + sandbox.stub(uut.semver, 'satisfies').returns(false) + + await uut.pollForIpfsFileServices() + + assert.equal(uut.state.selectedIpfsFileProvider, '') + }) + it('should log unknown errors', async () => { + // Mock dependencies + uut.ipfsCoord = { + thisNode: { + peerList: mockData.peers, + peerData: mockData.peerData + } + } + sandbox.stub(uut.semver, 'satisfies').throws(new Error('test error')) + + await uut.pollForIpfsFileServices() + + assert.equal(uut.state.selectedIpfsFileProvider, '') + assert.isOk(true, 'Not throwing an error is a success.') + }) + it('should handle ipfsCoord errors', async () => { + uut.ipfsCoord = { + } + + await uut.pollForIpfsFileServices() + + assert.equal(uut.state.selectedIpfsFileProvider, '') + assert.isOk(true, 'Not throwing an error is a success.') }) }) }) diff --git a/test/unit/adapters/ipfs-files.adapter.unit.js b/test/unit/adapters/ipfs-files.adapter.unit.js new file mode 100644 index 0000000..6a77793 --- /dev/null +++ b/test/unit/adapters/ipfs-files.adapter.unit.js @@ -0,0 +1,243 @@ +/* + Unit tests for the IPFS Files Adapter. +*/ + +import { assert } from 'chai' +import sinon from 'sinon' +import cloneDeep from 'lodash.clonedeep' +import adapters from '../mocks/adapters/index.js' +import IPFSFilesAdapter from '../../../src/adapters/ipfs-files/index.js' +// import IPFSMock from '../mocks/ipfs-mock.js' + +import mockDataLib from '../mocks/adapters/ipfs-coord-mocks.js' +import mockDataRPC from '../mocks/use-cases/rest-api-mocks.js' + +describe('#IPFS-Files', () => { + let uut + let sandbox + let mockData + + beforeEach(() => { + const ipfs = adapters.ipfs + uut = new IPFSFilesAdapter({ ipfs }) + + sandbox = sinon.createSandbox() + + mockData = cloneDeep(mockDataLib) + }) + + afterEach(() => { + sandbox.restore() + + clearInterval(uut.pollBchServiceInterval) + clearInterval(uut.pollP2wdbServiceInterval) + }) + + describe('#constructor', () => { + it('should throw an error if ipfs instance is not included', () => { + try { + uut = new IPFSFilesAdapter() + + assert.fail('Unexpected code path') + } catch (err) { + assert.include( + err.message, + 'An instance of IPFS must be passed when instantiating the IPFS Files Adapter library.' + ) + } + }) + }) + describe('#rpcHandler', () => { + it('should add RPC data to queue', () => { + const data = { + payload: { + id: '123' + } + } + + uut.rpcHandler(data) + }) + it('should skip errors', async () => { + try { + sandbox.stub(uut.rpcDataQueue, 'push').throws(new Error('test error')) + await uut.rpcHandler() + } catch (err) { + assert.fail('Unexpected code path') + } + }) + }) + describe('#getStatus', () => { + it('should get the status from ipfs-coord', async () => { + uut.ipfs.ipfsCoordAdapter.state.ipfsFileProviders = mockData.peers + uut.ipfs.ipfsCoordAdapter.ipfsCoord.thisNode = { peerData: mockData.peerData } + + await uut.getStatus() + }) + it('should handle error', async () => { + try { + uut.ipfs.ipfsCoordAdapter.ipfsCoord.thisNode = null + await uut.getStatus() + assert.fail('Unexpected code path') + } catch (err) { + assert.include(err.message, 'Cannot read') + } + }) + }) + describe('#selectProvider', () => { + it('should select provider', async () => { + await uut.selectProvider('providerId to select') + assert.equal(uut.ipfs.ipfsCoordAdapter.config.selectedIpfsFileProvider, 'providerId to select') + }) + it('should handle error', async () => { + try { + await uut.selectProvider() + assert.fail('Unexpected code path') + } catch (err) { + assert.include(err.message, 'providerId must be a string!') + } + }) + }) + describe('#getFileMetadata', () => { + it('should get file metadata', async () => { + uut.ipfs.ipfsCoordAdapter.state.selectedIpfsFileProvider = mockData.peers[0] + sandbox.stub(uut, 'waitForRPCResponse').resolves({ + success: true + }) + const result = await uut.getFileMetadata({ cid: 'cid to get' }) + assert.equal(result.success, true) + }) + it('should handle error if no cid is provided', async () => { + try { + await uut.getFileMetadata() + assert.fail('Unexpected code path') + } catch (err) { + assert.include(err.message, 'getFileMetadata() cid input hash must be a string.') + } + }) + it('should handle error if no provider is selected', async () => { + try { + uut.ipfs.ipfsCoordAdapter.state.selectedIpfsFileProvider = null + await uut.getFileMetadata({ cid: 'cid to get' }) + assert.fail('Unexpected code path') + } catch (err) { + assert.include(err.message, 'No IPFS File Pin Service provider available yet.') + } + }) + }) + describe('#getPins', () => { + it('should get pins', async () => { + uut.ipfs.ipfsCoordAdapter.state.selectedIpfsFileProvider = mockData.peers[0] + sandbox.stub(uut, 'waitForRPCResponse').resolves({ + success: true + }) + const result = await uut.getPins() + assert.equal(result.success, true) + }) + it('should handle error if no provider is selected', async () => { + try { + uut.ipfs.ipfsCoordAdapter.state.selectedIpfsFileProvider = null + await uut.getPins() + assert.fail('Unexpected code path') + } catch (err) { + assert.include(err.message, 'No IPFS File Pin Service provider available yet.') + } + }) + }) + describe('#waitForRPCResponse', () => { + it('should resolve when data is received', async () => { + // Mock dependencies + uut.ipfs.ipfsCoordAdapter.wallet = { + bchjs: { + Util: { + sleep: () => {} + } + } + } + + // Mock data. + const rpcId = '123' + uut.rpcDataQueue.push(mockDataRPC.rpcData) + + const result = await uut.waitForRPCResponse(rpcId) + // console.log('result: ', result) + + assert.property(result, 'success') + assert.equal(result.success, true) + assert.property(result, 'balances') + assert.isArray(result.balances) + }) + it('should return false if data is not found', async () => { + // Mock dependencies + uut.ipfs.ipfsCoordAdapter.wallet = { + bchjs: { + Util: { + sleep: () => {} + } + } + } + + // Mock data. + const rpcId = '123' + // Fill the queue with false values. + for (let i = 0; i < 10; i++) { + uut.rpcDataQueue.push({ payload: { id: '1234567890' } }) + } + sandbox.stub(uut.ipfs.ipfsCoordAdapter.bchjs.Util, 'sleep').resolves() + + const result = await uut.waitForRPCResponse(rpcId) + // console.log('result: ', result) + + assert.property(result, 'success') + assert.isFalse(result.success) + }) + it('should catch and throw an error', async () => { + try { + uut.ipfs.ipfsCoordAdapter.wallet = { + bchjs: { + Util: { + sleep: () => {} + } + } + } + + await uut.waitForRPCResponse() + + assert.fail('Unexpected code path') + } catch (err) { + // console.log('error: ', err) + assert.include(err.message, 'rpcId can not be false or undefined') + } + }) + }) + describe('#pinClaim', () => { + it('should process pinClaim', async () => { + uut.ipfs.ipfsCoordAdapter.state.selectedIpfsFileProvider = mockData.peers[0] + sandbox.stub(uut, 'waitForRPCResponse').resolves({ + success: true + }) + const result = await uut.pinClaim({ + proofOfBurnTxid: 'proofOfBurnTxid to pin', + cid: 'cid to pin', + claimTxid: 'claimTxid to pin', + filename: 'filename to pin', + address: 'address to pin' + }) + assert.equal(result.success, true) + }) + it('should handle error if no provider is selected', async () => { + try { + uut.ipfs.ipfsCoordAdapter.state.selectedIpfsFileProvider = null + await uut.pinClaim({ + proofOfBurnTxid: 'proofOfBurnTxid to pin', + cid: 'cid to pin', + claimTxid: 'claimTxid to pin', + filename: 'filename to pin', + address: 'address to pin' + }) + assert.fail('Unexpected code path') + } catch (err) { + assert.include(err.message, 'No IPFS File Provider Service is available yet. Try again in a few seconds.') + } + }) + }) +}) diff --git a/test/unit/adapters/p2wdb.adapter.unit.js b/test/unit/adapters/p2wdb.adapter.unit.js new file mode 100644 index 0000000..c9834ae --- /dev/null +++ b/test/unit/adapters/p2wdb.adapter.unit.js @@ -0,0 +1,223 @@ +/* + Unit tests for P2WDB Adapter. +*/ + +import { assert } from 'chai' +import sinon from 'sinon' +import cloneDeep from 'lodash.clonedeep' +import adapters from '../mocks/adapters/index.js' +import P2WDBAdapter from '../../../src/adapters/p2wdb/index.js' +// import IPFSMock from '../mocks/ipfs-mock.js' + +import mockDataLib from '../mocks/adapters/ipfs-coord-mocks.js' +import mockDataRPC from '../mocks/use-cases/rest-api-mocks.js' + +describe('#P2WDB Adapter', () => { + let uut + let sandbox + let mockData + + beforeEach(() => { + const ipfs = adapters.ipfs + uut = new P2WDBAdapter({ ipfs }) + + sandbox = sinon.createSandbox() + + mockData = cloneDeep(mockDataLib) + }) + + afterEach(() => { + sandbox.restore() + + clearInterval(uut.pollBchServiceInterval) + clearInterval(uut.pollP2wdbServiceInterval) + }) + + describe('#constructor', () => { + it('should throw an error if ipfs instance is not included', () => { + try { + uut = new P2WDBAdapter() + + assert.fail('Unexpected code path') + } catch (err) { + assert.include( + err.message, + 'An instance of IPFS must be passed when instantiating the P2WDB Adapter library.' + ) + } + }) + }) + describe('#rpcHandler', () => { + it('should add RPC data to queue', () => { + const data = { + payload: { + id: '123' + } + } + + uut.rpcHandler(data) + }) + it('should skip errors', async () => { + try { + sandbox.stub(uut.rpcDataQueue, 'push').throws(new Error('test error')) + await uut.rpcHandler() + } catch (err) { + assert.fail('Unexpected code path') + } + }) + }) + describe('#getStatus', () => { + it('should get the status from ipfs-coord', async () => { + uut.ipfs.ipfsCoordAdapter.state.p2wdbProviders = mockData.peers + uut.ipfs.ipfsCoordAdapter.ipfsCoord.thisNode = { peerData: mockData.peerData } + + await uut.getStatus() + }) + it('should handle error', async () => { + try { + uut.ipfs.ipfsCoordAdapter.ipfsCoord.thisNode = null + await uut.getStatus() + assert.fail('Unexpected code path') + } catch (err) { + assert.include(err.message, 'Cannot read') + } + }) + }) + describe('#selectProvider', () => { + it('should select provider', async () => { + await uut.selectProvider('providerId to select') + assert.equal(uut.ipfs.ipfsCoordAdapter.config.preferredP2wdbProvider, 'providerId to select') + }) + it('should handle error', async () => { + try { + await uut.selectProvider() + assert.fail('Unexpected code path') + } catch (err) { + assert.include(err.message, 'providerId must be a string!') + } + }) + }) + + describe('#waitForRPCResponse', () => { + it('should resolve when data is received', async () => { + // Mock dependencies + uut.ipfs.ipfsCoordAdapter.wallet = { + bchjs: { + Util: { + sleep: () => {} + } + } + } + + // Mock data. + const rpcId = '123' + uut.rpcDataQueue.push(mockDataRPC.rpcData) + + const result = await uut.waitForRPCResponse(rpcId) + // console.log('result: ', result) + + assert.property(result, 'success') + assert.equal(result.success, true) + assert.property(result, 'balances') + assert.isArray(result.balances) + }) + it('should return false if data is not found', async () => { + // Mock dependencies + uut.ipfs.ipfsCoordAdapter.wallet = { + bchjs: { + Util: { + sleep: () => {} + } + } + } + + // Mock data. + const rpcId = '123' + // Fill the queue with false values. + for (let i = 0; i < 10; i++) { + uut.rpcDataQueue.push({ payload: { id: '1234567890' } }) + } + sandbox.stub(uut.ipfs.ipfsCoordAdapter.bchjs.Util, 'sleep').resolves() + + const result = await uut.waitForRPCResponse(rpcId) + // console.log('result: ', result) + + assert.property(result, 'success') + assert.isFalse(result.success) + }) + it('should catch and throw an error', async () => { + try { + uut.ipfs.ipfsCoordAdapter.wallet = { + bchjs: { + Util: { + sleep: () => {} + } + } + } + + await uut.waitForRPCResponse() + + assert.fail('Unexpected code path') + } catch (err) { + // console.log('error: ', err) + assert.include(err.message, 'rpcId can not be false or undefined') + } + }) + }) + describe('#writeEntry', () => { + it('should handle error if no provider is selected', async () => { + try { + uut.ipfs.ipfsCoordAdapter.state.selectedP2wdbProvider = null + await uut.writeEntry({ + txid: 'txid to write', + signature: 'signature to write', + message: 'message to write', + data: 'data to write' + }) + assert.fail('Unexpected code path') + } catch (err) { + assert.include(err.message, 'No P2WDB Service provider available yet.') + } + }) + it('should process writeEntry', async () => { + uut.ipfs.ipfsCoordAdapter.state.selectedP2wdbProvider = mockData.peers[0] + sandbox.stub(uut, 'waitForRPCResponse').resolves({ + success: true + }) + const result = await uut.writeEntry({ + txid: 'txid to write', + signature: 'signature to write', + message: 'message to write', + data: 'data to write' + }) + assert.equal(result.success, true) + }) + }) + describe('#getEntryByHash', () => { + it('should handle error if hash is not provided', async () => { + try { + await uut.getEntryByHash() + assert.fail('Unexpected code path') + } catch (err) { + assert.include(err.message, 'getEntry() input hash must be a string.') + } + }) + it('should handle error if no provider is selected', async () => { + try { + uut.ipfs.ipfsCoordAdapter.state.selectedP2wdbProvider = null + await uut.getEntryByHash('hash to get') + assert.fail('Unexpected code path') + } catch (err) { + assert.include(err.message, 'No P2WDB Service provider available yet.') + } + }) + it('should process getEntryByHash', async () => { + uut.ipfs.ipfsCoordAdapter.state.selectedP2wdbProvider = mockData.peers[0] + sandbox.stub(uut, 'waitForRPCResponse').resolves({ + success: true + }) + const result = await uut.getEntryByHash('hash to get') + assert.equal(result.success, true) + }) + }) +}) diff --git a/test/unit/mocks/adapters/index.js b/test/unit/mocks/adapters/index.js index fad2a78..cdd465c 100644 --- a/test/unit/mocks/adapters/index.js +++ b/test/unit/mocks/adapters/index.js @@ -30,7 +30,7 @@ class IpfsAdapter { class IpfsCoordAdapter { constructor () { - + this.config = {} this.ipfsCoord = { adapters: { ipfs: { diff --git a/test/unit/mocks/adapters/ipfs-coord-mocks.js b/test/unit/mocks/adapters/ipfs-coord-mocks.js index f381abd..05eb3c8 100644 --- a/test/unit/mocks/adapters/ipfs-coord-mocks.js +++ b/test/unit/mocks/adapters/ipfs-coord-mocks.js @@ -4,7 +4,9 @@ const peers = [ 'QmWkjYRRTaxVEuGK8ip2X3trVyJShFs6U9g1h9x6fK5mZ2', - 'QmQqjVVD3CS6zaJmoSdt6GwX1c1tD5T9FZas4qxLbru8xN' + 'QmQqjVVD3CS6zaJmoSdt6GwX1c1tD5T9FZas4qxLbru8xN', + 'QmWkjYRRTaxVEuGK8ip2X3trVyJShFs6U9g1h9x6fK5mT3', + 'QmWkjYRRTaxVEuGK8ip2X3trVyJShFs6U9g1h9x6fK5mD4' ] const peerData = [ @@ -110,7 +112,113 @@ const peerData = [ }, updatedAt: '2021-08-24T23:13:20.411Z' } - } + }, + { + from: 'QmWkjYRRTaxVEuGK8ip2X3trVyJShFs6U9g1h9x6fK5mT3', + channel: 'psf-ipfs-coordination-001', + data: { + apiName: 'ipfs-coord-announce', + apiVersion: '1.3.2', + apiInfo: + 'You should put an IPFS hash or web URL here to your documentation.', + ipfsId: 'QmWkjYRRTaxVEuGK8ip2X3trVyJShFs6U9g1h9x6fK5mZ2', + type: 'node.js', + ipfsMultiaddrs: [ + '/ip4/127.0.0.1/tcp/5701/p2p/QmWkjYRRTaxVEuGK8ip2X3trVyJShFs6U9g1h9x6fK5mZ2', + '/ip4/127.0.0.1/tcp/5702/ws/p2p/QmWkjYRRTaxVEuGK8ip2X3trVyJShFs6U9g1h9x6fK5mZ2', + '/ip4/192.168.0.2/tcp/5701/p2p/QmWkjYRRTaxVEuGK8ip2X3trVyJShFs6U9g1h9x6fK5mZ2', + '/ip4/192.168.0.2/tcp/5702/ws/p2p/QmWkjYRRTaxVEuGK8ip2X3trVyJShFs6U9g1h9x6fK5mZ2' + ], + orbitdb: + '/orbitdb/zdpuAntCMCcmZuFy2hq5F7kpcti3yHa34LLZHzyVqorkoSpAJ/QmWkjYRRTaxVEuGK8ip2X3trVyJShFs6U9g1h9x6fK5mZ221082417', + circuitRelays: [], + isCircuitRelay: false, + cryptoAddresses: [ + { + blockchain: 'BCH', + type: 'cashAddr', + address: 'bitcoincash:qru6kq3p4tv6z2lmy0n560lyhh3z2feay5gjzggc37' + }, + { + blockchain: 'BCH', + type: 'slpAddr', + address: 'simpleledger:qru6kq3p4tv6z2lmy0n560lyhh3z2feay5yffnac0q' + } + ], + encryptPubKey: + '021db6e97650659653ba61dd493dc348a7429cdcbafd4fb73b08b1223bf5bd98df', + jsonLd: { + '@context': 'https://schema.org/', + '@type': 'WebAPI', + name: 'trout-bch-wallet-service-dev', + version: '1.11.12', + protocol: 'p2wdb', + description: + 'IPFS service providing BCH blockchain access needed by a wallet.', + documentation: 'https://ipfs-bch-wallet-service.fullstack.cash/', + provider: { + '@type': 'Organization', + name: 'Permissionless Software Foundation', + url: 'https://PSFoundation.cash' + }, + identifier: 'QmWkjYRRTaxVEuGK8ip2X3trVyJShFs6U9g1h9x6fK5mZ2' + }, + updatedAt: '2021-08-24T23:13:19.306Z' + } + }, + { + from: 'QmWkjYRRTaxVEuGK8ip2X3trVyJShFs6U9g1h9x6fK5mD4', + channel: 'psf-ipfs-coordination-001', + data: { + apiName: 'ipfs-coord-announce', + apiVersion: '1.3.2', + apiInfo: + 'You should put an IPFS hash or web URL here to your documentation.', + ipfsId: 'QmWkjYRRTaxVEuGK8ip2X3trVyJShFs6U9g1h9x6fK5mZ2', + type: 'node.js', + ipfsMultiaddrs: [ + '/ip4/127.0.0.1/tcp/5701/p2p/QmWkjYRRTaxVEuGK8ip2X3trVyJShFs6U9g1h9x6fK5mZ2', + '/ip4/127.0.0.1/tcp/5702/ws/p2p/QmWkjYRRTaxVEuGK8ip2X3trVyJShFs6U9g1h9x6fK5mZ2', + '/ip4/192.168.0.2/tcp/5701/p2p/QmWkjYRRTaxVEuGK8ip2X3trVyJShFs6U9g1h9x6fK5mZ2', + '/ip4/192.168.0.2/tcp/5702/ws/p2p/QmWkjYRRTaxVEuGK8ip2X3trVyJShFs6U9g1h9x6fK5mZ2' + ], + orbitdb: + '/orbitdb/zdpuAntCMCcmZuFy2hq5F7kpcti3yHa34LLZHzyVqorkoSpAJ/QmWkjYRRTaxVEuGK8ip2X3trVyJShFs6U9g1h9x6fK5mZ221082417', + circuitRelays: [], + isCircuitRelay: false, + cryptoAddresses: [ + { + blockchain: 'BCH', + type: 'cashAddr', + address: 'bitcoincash:qru6kq3p4tv6z2lmy0n560lyhh3z2feay5gjzggc37' + }, + { + blockchain: 'BCH', + type: 'slpAddr', + address: 'simpleledger:qru6kq3p4tv6z2lmy0n560lyhh3z2feay5yffnac0q' + } + ], + encryptPubKey: + '021db6e97650659653ba61dd493dc348a7429cdcbafd4fb73b08b1223bf5bd98df', + jsonLd: { + '@context': 'https://schema.org/', + '@type': 'WebAPI', + name: 'trout-bch-wallet-service-dev', + version: '1.11.12', + protocol: 'ipfs-file-pin-service', + description: + 'IPFS service providing BCH blockchain access needed by a wallet.', + documentation: 'https://ipfs-bch-wallet-service.fullstack.cash/', + provider: { + '@type': 'Organization', + name: 'Permissionless Software Foundation', + url: 'https://PSFoundation.cash' + }, + identifier: 'QmWkjYRRTaxVEuGK8ip2X3trVyJShFs6U9g1h9x6fK5mZ2' + }, + updatedAt: '2021-08-24T23:13:19.306Z' + } + }, ] // module.exports = {