diff --git a/config/index.js b/config/index.js index 05ccfd8..7bb25ee 100644 --- a/config/index.js +++ b/config/index.js @@ -4,7 +4,7 @@ import development from './env/development.js' import production from './env/production.js' import test from './env/test.js' -const env = process.env.SVC_ENV || 'development' +const env = process.env.CONSUMER_ENV || 'development' console.log(`Loading config for this environment: ${env}`) let config = development 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/src/controllers/json-rpc/index.js b/src/controllers/json-rpc/index.js index eb7b005..1552b9c 100644 --- a/src/controllers/json-rpc/index.js +++ b/src/controllers/json-rpc/index.js @@ -107,7 +107,7 @@ class JSONRPC { // Default return string let retObj = _this.defaultResponse() - + console.log('parsedData ', parsedData) // Forward data on to bch adapter if this is the response of a BCH query. try { if (parsedData.payload.result.method === 'bch') { diff --git a/src/use-cases/ipfs-use-cases.js b/src/use-cases/ipfs-use-cases.js index cff9db0..3080018 100644 --- a/src/use-cases/ipfs-use-cases.js +++ b/src/use-cases/ipfs-use-cases.js @@ -155,7 +155,8 @@ class IpfsUseCases { // Periodically re-validate the write-price (by re-instantiating the // PSFFPP library) in case it's changed. const now = new Date() - const sixHours = now.getTime() * 1000 * 60 * 60 * 6 + const sixHours = 1000 * 60 * 60 * 6 + if (this.lastWritePriceUpdate + sixHours < now.getTime()) { this.psffpp = new PSFFPP({ wallet }) 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/controllers/json-rpc/a10-rpc.unit.js b/test/unit/controllers/json-rpc/a10-rpc.unit.js index 777ee4e..cb20933 100644 --- a/test/unit/controllers/json-rpc/a10-rpc.unit.js +++ b/test/unit/controllers/json-rpc/a10-rpc.unit.js @@ -162,6 +162,40 @@ describe('#JSON RPC', () => { assert.equal(obj.result.method, 'auth') assert.equal(obj.id, id) }) + it('should route to bch handler on success payload', async () => { + const id = uid() + const json = jsonrpc.success(id, { method: 'bch' }) + const str = JSON.stringify(json) + + // Mock the controller. + sandbox.stub(uut.adapters.bch, 'rpcHandler').resolves('true') + + const result = await uut.router(str, 'peerA') + // console.log(result) + + const obj = JSON.parse(result.retStr) + // console.log('obj: ', obj) + + assert.equal(obj.result.value, 'true') + assert.equal(obj.id, id) + }) + it('should route to bch handler on success payload', async () => { + const id = uid() + const json = jsonrpc.success(id, { method: 'file-pin' }) + const str = JSON.stringify(json) + + // Mock the controller. + sandbox.stub(uut.adapters.ipfsFiles, 'rpcHandler').resolves('true') + + const result = await uut.router(str, 'peerA') + // console.log(result) + + const obj = JSON.parse(result.retStr) + // console.log('obj: ', obj) + + assert.equal(obj.result.value, 'true') + assert.equal(obj.id, id) + }) it('should route to about handler', async () => { const id = uid() @@ -188,6 +222,31 @@ describe('#JSON RPC', () => { assert.equal(obj.result.method, 'about') assert.equal(obj.id, id) }) + it('should route to bch handler', async () => { + const id = uid() + const userCall = jsonrpc.request(id, 'bch', { endpoint: 'getAll' }) + const jsonStr = JSON.stringify(userCall, null, 2) + + // Mock the controller. + sandbox.stub(uut.adapters.bch, 'rpcHandler').resolves('true') + + // Force ipfs-coord communication. + uut.ipfsCoord.ipfs = { + orbitdb: { + sendToDb: () => {} + } + } + + const result = await uut.router(jsonStr, 'peerA') + // console.log(result) + + const obj = JSON.parse(result.retStr) + // console.log('obj: ', obj) + + assert.equal(obj.result.value, 'true') + assert.equal(obj.result.method, 'bch') + assert.equal(obj.id, id) + }) it('should exit quietly for duplicate RPC message', async () => { const id = uid() diff --git a/test/unit/controllers/rest-api/bch/bch.rest.controller.unit.js b/test/unit/controllers/rest-api/bch/bch.rest.controller.unit.js index 9c3ca1a..165e4af 100644 --- a/test/unit/controllers/rest-api/bch/bch.rest.controller.unit.js +++ b/test/unit/controllers/rest-api/bch/bch.rest.controller.unit.js @@ -62,6 +62,18 @@ describe('#BCH-REST-Controller', () => { }) describe('#handleError', () => { + it('should pass an error message', () => { + try { + const err = { + status: 422, + message: 'Unprocessable Entity' + } + + uut.handleError(ctx, err) + } catch (err) { + assert.include(err.message, 'Unprocessable Entity') + } + }) it('should still throw error if there is no message', () => { try { const err = { @@ -467,4 +479,67 @@ describe('#BCH-REST-Controller', () => { assert.equal(ctx.status, 200) }) }) + + describe('#getService', () => { + it('should return 422 status on arbitrary error', async () => { + try { + sandbox.stub(uut.adapters.ipfs.ipfsCoordAdapter, 'state').value(null) + ctx.request.body = { + service: 'blah' + } + + 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') + } + }) + + it('should return 200 status on success', async () => { + sandbox.stub(uut.adapters.ipfs.ipfsCoordAdapter, 'state').value({ selectedServiceProvider: 'selected service' }) + ctx.request.body = { + service: 'selected service' + } + + await uut.getService(ctx) + + // Assert the expected HTTP response + assert.equal(ctx.status, 200) + }) + }) + + describe('#postProvider', () => { + it('should return 422 status on arbitrary error', async () => { + try { + uut.config.freezeProvider = true + ctx.request.body = { + providerId: 'selected provider id' + } + + await uut.postProvider(ctx) + + assert.fail('Unexpected result') + } catch (err) { + console.log('err: ', err) + assert.equal(err.status, 422) + assert.include(err.message, 'Consumer has preferredProvider set in environment variable. Refusing to switch providers.') + } + }) + + it('should return 200 status on success', async () => { + uut.config.freezeProvider = false + ctx.request.body = { + providerId: 'selected provider id' + } + + sandbox.stub(uut.adapters.bch, 'selectProvider').resolves(true) + await uut.postProvider(ctx) + + // Assert the expected HTTP response + assert.equal(ctx.status, 200) + }) + }) }) diff --git a/test/unit/controllers/rest-api/bch/bch.rest.router.unit.js b/test/unit/controllers/rest-api/bch/bch.rest.router.unit.js index f7851b7..03aaeaa 100644 --- a/test/unit/controllers/rest-api/bch/bch.rest.router.unit.js +++ b/test/unit/controllers/rest-api/bch/bch.rest.router.unit.js @@ -18,7 +18,7 @@ let sandbox // const mockContext = require('../../../../unit/mocks/ctx-mock').context -describe('#Users-REST-Router', () => { +describe('#BCH-REST-Router', () => { // const testUser = {} beforeEach(() => { @@ -75,4 +75,108 @@ describe('#Users-REST-Router', () => { } }) }) + describe('#getStatus', () => { + it('should route to controller', async () => { + const spy = sandbox.stub(uut.bchRESTController, 'getStatus').resolves(true) + + await uut.getStatus() + assert.isTrue(spy.calledOnce) + }) + }) + describe('#postProvider', () => { + it('should route to controller', async () => { + const spy = sandbox.stub(uut.bchRESTController, 'postProvider').resolves(true) + + await uut.postProvider() + assert.isTrue(spy.calledOnce) + }) + }) + describe('#postBalance', () => { + it('should route to controller', async () => { + const spy = sandbox.stub(uut.bchRESTController, 'balance').resolves(true) + + await uut.postBalance() + assert.isTrue(spy.calledOnce) + }) + }) + describe('#postUtxos', () => { + it('should route to controller', async () => { + const spy = sandbox.stub(uut.bchRESTController, 'utxos').resolves(true) + + await uut.postUtxos() + assert.isTrue(spy.calledOnce) + }) + }) + describe('#postUtxosBulk', () => { + it('should route to controller', async () => { + const spy = sandbox.stub(uut.bchRESTController, 'utxosBulk').resolves(true) + + await uut.postUtxosBulk() + assert.isTrue(spy.calledOnce) + }) + }) + describe('#postBroadcast', () => { + it('should route to controller', async () => { + const spy = sandbox.stub(uut.bchRESTController, 'broadcast').resolves(true) + + await uut.postBroadcast() + assert.isTrue(spy.calledOnce) + }) + }) + describe('#postTxHistory', () => { + it('should route to controller', async () => { + const spy = sandbox.stub(uut.bchRESTController, 'txHistory').resolves(true) + + await uut.postTxHistory() + assert.isTrue(spy.calledOnce) + }) + }) + describe('#postTxData', () => { + it('should route to controller', async () => { + const spy = sandbox.stub(uut.bchRESTController, 'txData').resolves(true) + + await uut.postTxData() + assert.isTrue(spy.calledOnce) + }) + }) + describe('#postPubKey', () => { + it('should route to controller', async () => { + const spy = sandbox.stub(uut.bchRESTController, 'pubKey').resolves(true) + + await uut.postPubKey() + assert.isTrue(spy.calledOnce) + }) + }) + describe('#utxoIsValid', () => { + it('should route to controller', async () => { + const spy = sandbox.stub(uut.bchRESTController, 'utxoIsValid').resolves(true) + + await uut.utxoIsValid() + assert.isTrue(spy.calledOnce) + }) + }) + describe('#getTokenData', () => { + it('should route to controller', async () => { + const spy = sandbox.stub(uut.bchRESTController, 'getTokenData').resolves(true) + + await uut.getTokenData() + assert.isTrue(spy.calledOnce) + }) + }) + describe('#getTokenData2', () => { + it('should route to controller', async () => { + const spy = sandbox.stub(uut.bchRESTController, 'getTokenData2').resolves(true) + + await uut.getTokenData2() + assert.isTrue(spy.calledOnce) + }) + }) + describe('#getService', () => { + it('should route to controller', async () => { + const spy = sandbox.stub(uut.bchRESTController, 'getService').resolves(true) + + await uut.getService() + assert.isTrue(spy.calledOnce) + }) + }) }) diff --git a/test/unit/controllers/rest-api/p2wdb/p2wdb.rest.controller.unit.js b/test/unit/controllers/rest-api/p2wdb/p2wdb.rest.controller.unit.js new file mode 100644 index 0000000..4ab363c --- /dev/null +++ b/test/unit/controllers/rest-api/p2wdb/p2wdb.rest.controller.unit.js @@ -0,0 +1,223 @@ +/* + Unit tests for the REST API handler for the /p2wdb endpoints. +*/ + +// Public npm libraries +import { assert } from 'chai' +import sinon from 'sinon' + +// Local support libraries +import adapters from '../../../mocks/adapters/index.js' +import UseCasesMock from '../../../mocks/use-cases/index.js' + +import P2wdbController from '../../../../../src/controllers/rest-api/p2wdb/controller.js' +import { context } from '../../../mocks/ctx-mock.js' +// import blah '../../../../unit/mocks/ctx-mock.js' +// console.log('blah: ', blah) +let uut +let sandbox +let ctx + +describe('#P2WDB-REST-Controller', () => { + // const testUser = {} + + beforeEach(() => { + const useCases = new UseCasesMock() + uut = new P2wdbController({ adapters, useCases }) + + sandbox = sinon.createSandbox() + + // Mock the context object. + ctx = context() + }) + + afterEach(() => sandbox.restore()) + + describe('#constructor', () => { + it('should throw an error if adapters are not passed in', () => { + try { + uut = new P2wdbController() + + assert.fail('Unexpected code path') + } catch (err) { + assert.include( + err.message, + 'Instance of Adapters library required when instantiating /p2wdb REST Controller.' + ) + } + }) + + it('should throw an error if useCases are not passed in', () => { + try { + uut = new P2wdbController({ adapters }) + + assert.fail('Unexpected code path') + } catch (err) { + assert.include( + err.message, + 'Instance of Use Cases library required when instantiating /p2wdb REST Controller.' + ) + } + }) + }) + + describe('#handleError', () => { + it('should pass an error message', () => { + try { + const err = { + status: 422, + message: 'Unprocessable Entity' + } + + uut.handleError(ctx, err) + } catch (err) { + assert.include(err.message, 'Unprocessable Entity') + } + }) + it('should still throw error if there is no message', () => { + try { + const err = { + status: 404 + } + + uut.handleError(ctx, err) + } catch (err) { + assert.include(err.message, 'Not Found') + } + }) + }) + + describe('#getStatus', () => { + it('should return 422 status on arbitrary biz logic error', async () => { + try { + // Force an error + sandbox + .stub(uut.adapters.p2wdb, 'getStatus') + .rejects(new Error('test error')) + + await uut.getStatus(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 () => { + sandbox.stub(uut.adapters.p2wdb, 'getStatus').resolves('p2wdb') + + await uut.getStatus(ctx) + + // Assert the expected HTTP response + assert.equal(ctx.status, 200) + + // Assert that expected properties exist in the returned data. + assert.equal(ctx.response.body.status, 'p2wdb') + }) + }) + + describe('#postProvider', () => { + it('should return 422 status on arbitrary error', async () => { + try { + // Force an error + sandbox + .stub(uut.adapters.p2wdb, 'selectProvider') + .rejects(new Error('test error')) + + ctx.request.body = { + addresses: 'blah' + } + + await uut.postProvider(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 () => { + sandbox.stub(uut.adapters.p2wdb, 'selectProvider').resolves({ status: 200 }) + + ctx.request.body = { + addresses: 'blah' + } + + await uut.postProvider(ctx) + + // Assert the expected HTTP response + assert.equal(ctx.status, 200) + }) + }) + describe('#getEntryByHash', () => { + it('should return 422 status on arbitrary error', async () => { + try { + // Force an error + sandbox + .stub(uut.adapters.p2wdb, 'getEntryByHash') + .rejects(new Error('test error')) + + ctx.request.body = { + hash: 'blah' + } + + await uut.getEntryByHash(ctx) + } catch (err) { + assert.equal(err.status, 422) + assert.include(err.message, 'test error') + } + }) + + it('should return 200 status on success', async () => { + sandbox.stub(uut.adapters.p2wdb, 'getEntryByHash').resolves({ status: 200 }) + + ctx.request.body = { + hash: 'blah' + } + + await uut.getEntryByHash(ctx) + assert.equal(ctx.status, 200) + }) + }) + + describe('#writeEntry', () => { + it('should return 422 status on arbitrary error', async () => { + try { + // Force an error + sandbox + .stub(uut.adapters.p2wdb, 'writeEntry') + .rejects(new Error('test error')) + + ctx.request.body = { + txid: 'blah', + signature: 'blah', + message: 'blah', + data: 'blah' + } + + await uut.writeEntry(ctx) + } catch (err) { + assert.equal(err.status, 422) + assert.include(err.message, 'test error') + } + }) + + it('should return 200 status on success', async () => { + sandbox.stub(uut.adapters.p2wdb, 'writeEntry').resolves({ status: 200 }) + + ctx.request.body = { + txid: 'blah', + signature: 'blah', + message: 'blah', + data: 'blah' + } + + await uut.writeEntry(ctx) + assert.equal(ctx.status, 200) + }) + }) +}) diff --git a/test/unit/controllers/rest-api/p2wdb/p2wdb.rest.router.unit.js b/test/unit/controllers/rest-api/p2wdb/p2wdb.rest.router.unit.js new file mode 100644 index 0000000..f136fed --- /dev/null +++ b/test/unit/controllers/rest-api/p2wdb/p2wdb.rest.router.unit.js @@ -0,0 +1,110 @@ +/* + Unit tests for the REST API handler for the /users endpoints. +*/ + +// Public npm libraries +import { assert } from 'chai' +import sinon from 'sinon' + +// Local support libraries +import adapters from '../../../mocks/adapters/index.js' +import UseCasesMock from '../../../mocks/use-cases/index.js' +// const app = require('../../../mocks/app-mock') + +import P2wdbRouter from '../../../../../src/controllers/rest-api/p2wdb/index.js' +let uut +let sandbox +// let ctx + +// const mockContext = require('../../../../unit/mocks/ctx-mock').context + +describe('#P2WDB-REST-Router', () => { + // const testUser = {} + + beforeEach(() => { + const useCases = new UseCasesMock() + uut = new P2wdbRouter({ adapters, useCases }) + + sandbox = sinon.createSandbox() + + // Mock the context object. + // ctx = mockContext() + }) + + afterEach(() => sandbox.restore()) + + describe('#constructor', () => { + it('should throw an error if adapters are not passed in', () => { + try { + uut = new P2wdbRouter() + + assert.fail('Unexpected code path') + } catch (err) { + assert.include( + err.message, + 'Instance of Adapters library required when instantiating P2WDB REST Controller.' + ) + } + }) + + it('should throw an error if useCases are not passed in', () => { + try { + uut = new P2wdbRouter({ adapters }) + + assert.fail('Unexpected code path') + } catch (err) { + assert.include( + err.message, + 'Instance of Use Cases library required when instantiating P2WDB REST Controller.' + ) + } + }) + }) + + describe('#attach', () => { + it('should throw an error if app is not passed in.', () => { + try { + uut.attach() + + assert.fail('Unexpected code path') + } catch (err) { + assert.include( + err.message, + 'Must pass app object when attaching REST API controllers.' + ) + } + }) + }) + describe('#getStatus', () => { + it('should route to controller', async () => { + const spy = sandbox.stub(uut.p2wdbRESTController, 'getStatus').resolves(true) + + await uut.getStatus() + assert.isTrue(spy.calledOnce) + }) + }) + describe('#postProvider', () => { + it('should route to controller', async () => { + const spy = sandbox.stub(uut.p2wdbRESTController, 'postProvider').resolves(true) + + await uut.postProvider() + assert.isTrue(spy.calledOnce) + }) + }) + describe('#entryFromHash', () => { + it('should route to controller', async () => { + const spy = sandbox.stub(uut.p2wdbRESTController, 'getEntryByHash').resolves(true) + + await uut.entryFromHash() + assert.isTrue(spy.calledOnce) + }) + }) + describe('#write', () => { + it('should route to controller', async () => { + const spy = sandbox.stub(uut.p2wdbRESTController, 'writeEntry').resolves(true) + + await uut.write() + assert.isTrue(spy.calledOnce) + }) + }) +}) diff --git a/test/unit/controllers/rest-api/price/price.rest.controller.unit.js b/test/unit/controllers/rest-api/price/price.rest.controller.unit.js new file mode 100644 index 0000000..6df9dad --- /dev/null +++ b/test/unit/controllers/rest-api/price/price.rest.controller.unit.js @@ -0,0 +1,157 @@ +/* + Unit tests for the REST API handler for the /price endpoints. +*/ + +// Public npm libraries +import { assert } from 'chai' +import sinon from 'sinon' + +// Local support libraries +import adapters from '../../../mocks/adapters/index.js' +import UseCasesMock from '../../../mocks/use-cases/index.js' + +import PriceController from '../../../../../src/controllers/rest-api/price/controller.js' +import { context } from '../../../mocks/ctx-mock.js' +// import blah '../../../../unit/mocks/ctx-mock.js' +// console.log('blah: ', blah) +let uut +let sandbox +let ctx + +describe('#PRICE-REST-Controller', () => { + // const testUser = {} + + beforeEach(() => { + const useCases = new UseCasesMock() + uut = new PriceController({ adapters, useCases }) + + sandbox = sinon.createSandbox() + + // Mock the context object. + ctx = context() + }) + + afterEach(() => sandbox.restore()) + + describe('#constructor', () => { + it('should throw an error if adapters are not passed in', () => { + try { + uut = new PriceController() + + assert.fail('Unexpected code path') + } catch (err) { + assert.include( + err.message, + 'Instance of Adapters library required when instantiating /price REST Controller.' + ) + } + }) + + it('should throw an error if useCases are not passed in', () => { + try { + uut = new PriceController({ adapters }) + + assert.fail('Unexpected code path') + } catch (err) { + assert.include( + err.message, + 'Instance of Use Cases library required when instantiating /price REST Controller.' + ) + } + }) + }) + + describe('#handleError', () => { + it('should pass an error message', () => { + try { + const err = { + status: 422, + message: 'Unprocessable Entity' + } + + uut.handleError(ctx, err) + } catch (err) { + assert.include(err.message, 'Unprocessable Entity') + } + }) + it('should still throw error if there is no message', () => { + try { + const err = { + status: 404 + } + + uut.handleError(ctx, err) + } catch (err) { + assert.include(err.message, 'Not Found') + } + }) + }) + + describe('#getUSD', () => { + it('should return 422 status on arbitrary biz logic error', async () => { + try { + // Force an error + sandbox + .stub(uut.axios, 'request') + .rejects(new Error('test error')) + + await uut.getUSD(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 () => { + sandbox.stub(uut.axios, 'request').resolves({ data: { data: { rates: { USD: 1.00 } } } }) + + await uut.getUSD(ctx) + + // Assert the expected HTTP response + assert.equal(ctx.status, 200) + + // Assert that expected properties exist in the returned data. + assert.equal(ctx.response.body.usd, 1.00) + }) + }) + + describe('#getXecPrice', () => { + it('should return 422 status on arbitrary biz logic error', async () => { + try { + sandbox.stub(uut.axios, 'request').rejects(new Error('test error')) + await uut.getXecPrice(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 () => { + sandbox.stub(uut.axios, 'request').resolves({ data: { data: { ticker: { last: 1.00 } } } }) + await uut.getXecPrice(ctx) + assert.equal(ctx.status, 200) + assert.equal(ctx.response.body.usd, 1.00) + }) + }) + describe('#getPsffppWritePrice', () => { + it('should return 200 status on success', async () => { + sandbox.stub(uut.useCases.ipfs, 'getWritePrice').resolves(1.00) + await uut.getPsffppWritePrice(ctx) + assert.equal(ctx.status, 200) + assert.equal(ctx.response.body.psfPrice, 1.00) + }) + it('should return 422 status on arbitrary biz logic error', async () => { + try { + sandbox.stub(uut.useCases.ipfs, 'getWritePrice').rejects(new Error('test error')) + await uut.getPsffppWritePrice(ctx) + assert.fail('Unexpected result') + } catch (err) { + assert.equal(err.status, 422) + assert.include(err.message, 'test error') + } + }) + }) +}) diff --git a/test/unit/controllers/rest-api/price/price.rest.router.unit.js b/test/unit/controllers/rest-api/price/price.rest.router.unit.js new file mode 100644 index 0000000..a0fb333 --- /dev/null +++ b/test/unit/controllers/rest-api/price/price.rest.router.unit.js @@ -0,0 +1,102 @@ +/* + Unit tests for the REST API handler for the /users endpoints. +*/ + +// Public npm libraries +import { assert } from 'chai' +import sinon from 'sinon' + +// Local support libraries +import adapters from '../../../mocks/adapters/index.js' +import UseCasesMock from '../../../mocks/use-cases/index.js' +// const app = require('../../../mocks/app-mock') + +import PriceRouter from '../../../../../src/controllers/rest-api/price/index.js' +let uut +let sandbox +// let ctx + +// const mockContext = require('../../../../unit/mocks/ctx-mock').context + +describe('#Price-REST-Router', () => { + // const testUser = {} + + beforeEach(() => { + const useCases = new UseCasesMock() + uut = new PriceRouter({ adapters, useCases }) + + sandbox = sinon.createSandbox() + + // Mock the context object. + // ctx = mockContext() + }) + + afterEach(() => sandbox.restore()) + + describe('#constructor', () => { + it('should throw an error if adapters are not passed in', () => { + try { + uut = new PriceRouter() + + assert.fail('Unexpected code path') + } catch (err) { + assert.include( + err.message, + 'Instance of Adapters library required when instantiating Price REST Controller.' + ) + } + }) + + it('should throw an error if useCases are not passed in', () => { + try { + uut = new PriceRouter({ adapters }) + + assert.fail('Unexpected code path') + } catch (err) { + assert.include( + err.message, + 'Instance of Use Cases library required when instantiating Price REST Controller.' + ) + } + }) + }) + + describe('#attach', () => { + it('should throw an error if app is not passed in.', () => { + try { + uut.attach() + + assert.fail('Unexpected code path') + } catch (err) { + assert.include( + err.message, + 'Must pass app object when attaching REST API controllers.' + ) + } + }) + }) + describe('#getPrice', () => { + it('should route to controller', async () => { + const spy = sandbox.stub(uut.priceRESTController, 'getUSD').resolves(true) + + await uut.getPrice() + assert.isTrue(spy.calledOnce) + }) + }) + + describe('#getXecPrice', () => { + it('should route to controller', async () => { + const spy = sandbox.stub(uut.priceRESTController, 'getXecPrice').resolves(true) + + await uut.getXecPrice() + assert.isTrue(spy.calledOnce) + }) + }) + describe('#getPsffppWritePrice', () => { + it('should route to controller', async () => { + const spy = sandbox.stub(uut.priceRESTController, 'getPsffppWritePrice').resolves(true) + await uut.getPsffppWritePrice() + assert.isTrue(spy.calledOnce) + }) + }) +}) diff --git a/test/unit/misc/config.js b/test/unit/misc/config.js index 54bce08..66e0a35 100644 --- a/test/unit/misc/config.js +++ b/test/unit/misc/config.js @@ -9,12 +9,12 @@ let currentEnv describe('#config', () => { before(() => { // Backup the current environment setting. - currentEnv = process.env.SVC_ENV + currentEnv = process.env.CONSUMER_ENV }) after(() => { // Restore the environment setting before starting these tests. - process.env.SVC_ENV = currentEnv + process.env.CONSUMER_ENV = currentEnv }) it('Should return development environment config by default', async () => { @@ -29,7 +29,7 @@ describe('#config', () => { // Hack to dynamically import a library multiple times: // https://github.com/denoland/deno/issues/6946 - process.env.SVC_ENV = 'test' + process.env.CONSUMER_ENV = 'test' const importedConfig2 = await import('../../../config/index.js?foo=bar1') const config = importedConfig2.default @@ -39,7 +39,7 @@ describe('#config', () => { }) it('Should return prod environment config', async () => { - process.env.SVC_ENV = 'prod' + process.env.CONSUMER_ENV = 'prod' process.env.WALLET_INTERFACE = 'web2' process.env.APISERVER = 'https://api.fullstack.cash/v5/' diff --git a/test/unit/mocks/adapters/index.js b/test/unit/mocks/adapters/index.js index fad2a78..109e853 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: { @@ -187,6 +187,9 @@ class BchUseCaseMock { async waitForRPCResponse () { return {} } + async selectProvider (){ + return true + } } const bch = new BchUseCaseMock() const wallet = { @@ -212,6 +215,18 @@ const ipfsFiles = { success: true, message: 'Pins' } + }, + rpcHandler: () => { + return { + success: true, + message: 'RPC handler' + } } } -export default { ipfs, localdb, bch, wallet, ipfsFiles } +const p2wdb = { + getStatus: async () => {}, + selectProvider: async () => {}, + getEntryByHash: async () => {}, + writeEntry: async () => {} +} +export default { ipfs, localdb, bch, wallet, ipfsFiles, p2wdb } 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 = { diff --git a/test/unit/mocks/use-cases/index.js b/test/unit/mocks/use-cases/index.js index eef55c6..e7df4a5 100644 --- a/test/unit/mocks/use-cases/index.js +++ b/test/unit/mocks/use-cases/index.js @@ -95,6 +95,10 @@ class IpfsUseCaseMock { async pinClaim() { return {} } + + async getWritePrice() { + return 1.00 + } } class UseCasesMock { diff --git a/test/unit/use-cases/ipfs.use-case.unit.js b/test/unit/use-cases/ipfs.use-case.unit.js index ca81a37..9b91bdd 100644 --- a/test/unit/use-cases/ipfs.use-case.unit.js +++ b/test/unit/use-cases/ipfs.use-case.unit.js @@ -14,7 +14,6 @@ import sinon from 'sinon' 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 @@ -92,15 +91,28 @@ describe('#ipfs-use-cases', () => { const result = await uut.getWritePrice() assert.isNumber(result) }) + it('should re-instantiate psffpp instance', async () => { + const psffppMock = {} + uut.psffpp = psffppMock + const lastWritePriceUpdate = new Date() + lastWritePriceUpdate.setHours(lastWritePriceUpdate.getHours() - 10) + uut.lastWritePriceUpdate = lastWritePriceUpdate.getTime() + const result = await uut.getWritePrice() + assert.isNumber(result) + assert.notEqual(uut.lastWritePriceUpdate, lastWritePriceUpdate.getTime()) + assert.notEqual(uut.psffpp, psffppMock) + }) it('should handle error ', async () => { try { + uut.lastWritePriceUpdate = new Date().getTime() uut.psffpp = { getMcWritePrice: () => { throw new Error('test error') } } await uut.getWritePrice() + assert.fail('Unexpected code path') } catch (error) { assert.include(error.message, 'test error') }