Compare commits

..
40 Commits
Author SHA1 Message Date
Chris Troutner ff371a79b9 Merge pull request #76 from Permissionless-Software-Foundation/ct-unstable
fix(reboot): Auto-reboot every 12 hours, to fix connectivity issues
2025-09-29 13:24:21 -07:00
Chris Troutner 487b72bfa2 fix(reboot): Auto-reboot every 12 hours, to fix connectivity issues 2025-09-29 13:22:39 -07:00
Chris Troutner 7d5715be39 Merge pull request #75 from Permissionless-Software-Foundation/dh-test-coverage
feat(tests): Increased unit test coverage
2025-09-24 15:49:50 -07:00
Daniel Gonzalez fc99cafb1d Added more tests 2025-09-24 17:22:05 -04:00
Daniel Gonzalez 0e51dc7f15 Improved adapters unit tests 2025-09-23 16:34:09 -04:00
Daniel Gonzalez c0e0e42fc6 Enviroments fix 2025-09-23 16:33:16 -04:00
Chris Troutner 932110698c Merge pull request #74 from Permissionless-Software-Foundation/ct-unstable
Syncing with upstream
2025-08-21 08:48:27 -07:00
Chris Troutner 59ed8d9e7a Merge branch 'master' into ct-unstable 2025-08-21 08:46:09 -07:00
Chris Troutner 883f9aca67 linting 2025-08-21 08:45:28 -07:00
Chris Troutner 9b88d7f40c Fixing merge conflict and broken test 2025-08-21 08:45:18 -07:00
Chris Troutner 18ea03a4d6 Merge pull request #178 from Permissionless-Software-Foundation/dh-test-coverage
feat(tests): Increased unit test coverage
2025-06-28 07:28:04 -07:00
Daniel Gonzalez 3d911d1062 feat(tests): Increased unit test coverage 2025-06-27 20:11:27 -04:00
Chris Troutner 6ceec71dd6 Merge pull request #73 from Permissionless-Software-Foundation/ct-unstable
Syncing with upstream. Usage improvements.
2025-06-05 18:42:47 -07:00
Chris Troutner f1296fafd6 fix(upstream): Syncing with upstream and fixing merge conflicts 2025-06-05 18:40:55 -07:00
Chris Troutner 479299d747 fix(usage): Removing debugging logs 2025-06-05 18:30:09 -07:00
Chris Troutner cf88fadfdc Merge pull request #177 from Permissionless-Software-Foundation/ct-unstable
Usage: back up to DB to track usage over restarts.
2025-06-05 18:27:06 -07:00
Chris Troutner 4977ae7003 Increasing backup to 10 minutes 2025-06-05 18:25:45 -07:00
Chris Troutner f0f6970a08 Added timer controllers for managing usage stats 2025-06-05 18:25:10 -07:00
Chris Troutner fcb4acb11d feat(usage-use-cases.js): Added functions for persisting usage data to database 2025-06-04 12:26:03 -07:00
Chris Troutner 28e012feb6 Merge pull request #176 from Permissionless-Software-Foundation/e2e-tests
Separating E2E tests from Unit tests
2025-05-30 17:18:40 -07:00
Chris Troutner 958c637c24 Commenting out failing test 2025-05-30 17:17:22 -07:00
Daniel Gonzalez 34381e6e93 Improved e2e tests 2025-05-30 11:53:57 -04:00
Daniel Gonzalez 8843b3e2b0 Separate e2e tests 2025-05-28 20:52:25 -04:00
Chris Troutner 3f91653d23 feat(e2e): separating e2e from unit tests 2025-05-23 07:26:51 -07:00
Chris Troutner 70da4d8000 fix(getTransaction): Reducing debug logs 2025-05-20 06:03:32 -07:00
Chris Troutner 3a4569c33f Merge pull request #175 from Permissionless-Software-Foundation/dh-env-var-admin
feat(admin): added env var setting so only admin can create new users
2025-05-19 13:06:40 -07:00
Daniel Gonzalez 05a0620663 feat(admin): added env var setting so only admin can create new users 2025-05-19 12:17:09 -04:00
Chris Troutner b8c1156de7 Merge pull request #72 from Permissionless-Software-Foundation/ct-unstable
Adding debugging to usage tracking
2025-05-18 15:40:31 -07:00
Chris Troutner 5146155555 Merge remote-tracking branch 'upstream/master' into ct-unstable 2025-05-18 15:38:43 -07:00
Chris Troutner af83c06b43 Merge pull request #174 from Permissionless-Software-Foundation/ct-unstable
fix(usage): Debugging issue with usage tracking
2025-05-18 15:37:46 -07:00
Chris Troutner e6317c3282 fix(usage): Debugging issue with usage tracking 2025-05-18 15:36:32 -07:00
Chris Troutner 354321c956 Merge pull request #173 from Permissionless-Software-Foundation/ct-unstable
fix(wallet.adapter): Binding this object to all methods
2025-04-22 19:07:06 -07:00
Chris Troutner af2572a493 fix(wallet.adapter): Binding this object to all methods 2025-04-22 18:58:03 -07:00
Chris Troutner ac451fd601 Merge pull request #71 from Permissionless-Software-Foundation/ct-unstable
Passing pagination paramter through to PSFFPP
2025-04-16 07:38:00 -07:00
Chris Troutner 6857fe0166 linting 2025-04-16 07:29:40 -07:00
Chris Troutner 89a03e561a feat(GET /pins/:page): Adding pagenation to endpoint 2025-04-16 07:29:27 -07:00
Chris Troutner 22e32a6fc1 Merge pull request #70 from Permissionless-Software-Foundation/ct-unstable
fix(psffpp): Updating to psffpp v1.2.0
2025-04-12 21:05:52 -07:00
Chris Troutner fcf5db7fa4 fix(psffpp): Updating to psffpp v1.2.0 2025-04-12 21:04:17 -07:00
Chris Troutner f8cf903fb9 Merge pull request #69 from Permissionless-Software-Foundation/dh-pin-claim-endpoint
feat(pin): Added pin-claim endpoint to ipfs-bch-wallet-consumer
2025-03-29 10:57:16 -07:00
Daniel Gonzalez c7986d1986 feat(pin): Added pin-claim endpoint to ipfs-bch-wallet-consumer 2025-03-27 11:01:30 -04:00
57 changed files with 3225 additions and 295 deletions
+6 -1
View File
@@ -172,6 +172,11 @@ export default {
// Preferred P2WDB provider
preferredIpfsFileProvider: process.env.PREFERRED_IPFS_FILE_PROVIDER
? process.env.PREFERRED_IPFS_FILE_PROVIDER
: ''
: '',
// Account Configuration
disableNewAccounts: process.env.DISABLE_NEW_ACCOUNTS ? true : false,
// Admin password
adminPassword: process.env.ADMIN_PASSWORD
}
+1 -1
View File
@@ -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
+5 -3
View File
@@ -1,6 +1,8 @@
{
"password": "BJdugaoiq10ZJnA7Uskd",
"email": "system@system.com",
"id": "67c114fd4935ba05e48f6bb2",
"token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpZCI6IjY3YzExNGZkNDkzNWJhMDVlNDhmNmJiMiIsImlhdCI6MTc0MDcwNzA2OX0.R5nU8J5yhGorp7I5kc8_dRESbo4hwCIrTtqJiVYVhLA"
"name": "admin",
"password": "SyP9YCFkgxu2FPNxFI8W",
"type": "admin",
"id": "68d47521e6060ba7332301b1",
"token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpZCI6IjY4ZDQ3NTIxZTYwNjBiYTczMzIzMDFiMSIsImlhdCI6MTc1OTE3NjQ3Mn0.pGJh_Io1_aZ3IwWdzOnC5Bt9Nw0x4MRhAAp7RkZrtmA"
}
+5 -3
View File
@@ -1,6 +1,8 @@
{
"password": "RCJqq5FjgYzvTXOyS1vo",
"email": "system@system.com",
"id": "645e59c19d49119bbb8329e7",
"token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpZCI6IjY0NWU1OWMxOWQ0OTExOWJiYjgzMjllNyIsImlhdCI6MTY4MzkwNDk2MX0.2yi-hkY31nkgMm_S1QrPs4dIm5DBnJ4GsJEPiSPYHDA"
"name": "admin",
"password": "UdbCjMgm7T8efeyUfcdN",
"type": "admin",
"id": "68daea62e9b47af49f86dae9",
"token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpZCI6IjY4ZGFlYTYyZTliNDdhZjQ5Zjg2ZGFlOSIsImlhdCI6MTc1OTE3NzMxNH0.-mFCzyDri0OLMz-oM03qyYPj7srEH_6HFLGzgCf_JLI"
}
+8 -8
View File
@@ -53,7 +53,7 @@
"node-fetch": "npm:@achingbrain/node-fetch@2.6.7",
"nodemailer": "6.7.5",
"passport-local": "1.0.0",
"psffpp": "1.1.9",
"psffpp": "1.2.0",
"public-ip": "6.0.1",
"semver": "7.3.5",
"winston": "3.3.3",
@@ -19902,9 +19902,9 @@
"license": "MIT"
},
"node_modules/psf-multisig-approval": {
"version": "2.0.7",
"resolved": "http://94.130.170.209:4873/psf-multisig-approval/-/psf-multisig-approval-2.0.7.tgz",
"integrity": "sha512-Z00+ozZ3LqBL/5naimg9r15FfPZk01nzNe/YaKGNs4AE8szVK4PxwMTq1su3h4QFHWgDIBth+sbxgJCguy5qzw==",
"version": "2.1.0",
"resolved": "http://94.130.170.209:4873/psf-multisig-approval/-/psf-multisig-approval-2.1.0.tgz",
"integrity": "sha512-QLlOnYzeOx6OQXu5MGRlSP/IhS+X1Y7v3WxUse9riqYHOvvz3WeUPZUB8TVnSsUR6lH5W8Ckshl1lnaWXcPs7Q==",
"license": "MIT",
"dependencies": {
"@chris.troutner/bitcore-lib-cash": "8.25.26",
@@ -19923,13 +19923,13 @@
}
},
"node_modules/psffpp": {
"version": "1.1.9",
"resolved": "http://94.130.170.209:4873/psffpp/-/psffpp-1.1.9.tgz",
"integrity": "sha512-9DKlU+PbwcXLqoq18iOt8Wr6HDVAQqvrW71gNKmJ/1ZK7Tv0bgjxt6agRZAKuj0zDRUH7nPqvcqCs8OqEQ1biQ==",
"version": "1.2.0",
"resolved": "http://94.130.170.209:4873/psffpp/-/psffpp-1.2.0.tgz",
"integrity": "sha512-F3KDh6pzI5ZRxkek6KWFpwIcgx9tocnAKJie0HwNwTBuuS40/LsUnkmhVMghufF603Fp0vO6o3P4vgbTTzw6pg==",
"license": "MIT",
"dependencies": {
"axios": "1.3.5",
"psf-multisig-approval": "2.0.7"
"psf-multisig-approval": "2.1.0"
}
},
"node_modules/psffpp/node_modules/axios": {
+5 -4
View File
@@ -6,11 +6,12 @@
"type": "module",
"scripts": {
"start": "node index.js",
"test": "npm run test:all",
"test": "npm run test:unit",
"test:all": "export CONSUMER_ENV=test && c8 --reporter=text mocha --exit --timeout 15000 --recursive test/unit test/e2e/automated/",
"test:unit": "export CONSUMER_ENV=test && mocha --exit --timeout 15000 --recursive test/unit/",
"test:unit": "export CONSUMER_ENV=test && c8 --reporter=text mocha --exit --timeout 15000 --recursive test/unit/",
"test:e2e:auto": "export CONSUMER_ENV=test && mocha --exit --timeout 15000 test/e2e/automated/",
"test:temp": "export CONSUMER_ENV=test && mocha --exit --timeout 15000 -g '#rate-limit' test/unit/json-rpc/",
"start:e2e:server": "export CONSUMER_ENV=test && node index.js",
"test:temp": "export SVC_ENV=test && mocha --exit --timeout 15000 -g '#rate-limit' test/unit/json-rpc/",
"lint": "standard --env mocha --fix",
"docs": "./node_modules/.bin/apidoc -i src/ -o docs",
"coverage": "c8 report --reporter=text-lcov | coveralls",
@@ -68,7 +69,7 @@
"node-fetch": "npm:@achingbrain/node-fetch@2.6.7",
"nodemailer": "6.7.5",
"passport-local": "1.0.0",
"psffpp": "1.1.9",
"psffpp": "1.2.0",
"public-ip": "6.0.1",
"semver": "7.3.5",
"winston": "3.3.3",
+26 -58
View File
@@ -51,70 +51,37 @@ class Admin {
// used by the Listing Manager and test scripts, in order access private API
// functions.
async createSystemUser () {
// Create the system user.
try {
context.password = _this._randomString(20)
const options = {
method: 'POST',
url: `${LOCALHOST}/users`,
data: {
user: {
email: 'system@system.com',
password: context.password,
name: 'admin'
}
}
const context = {
email: 'system@system.com',
name: 'admin',
password: _this.config.adminPassword || _this._randomString(20),
type: 'admin'
}
const result = await _this.axios.request(options)
// console.log('admin.data: ', result.data)
// Check if the user already exists
let adminUser = await _this.User.findOne({ email: context.email })
context.email = result.data.user.email
context.id = result.data.user._id
context.token = result.data.token
if (adminUser) {
// Update the password
adminUser.password = context.password
} else {
// Create a new admin user
adminUser = new _this.User(context)
}
// Update context with the new user id and token
context.id = adminUser._id
context.token = await adminUser.generateToken()
// Get the mongoDB entry
const user = await _this.User.findById(context.id)
// Save the user
await adminUser.save()
// Change the user type to admin
user.type = 'admin'
// console.log(`user created: ${JSON.stringify(user, null, 2)}`)
// Save the user model.
await user.save()
// console.log(`admin user created: ${JSON.stringify(result.body, null, 2)}`)
// console.log(`with password: ${context.password}`)
// Write out the system user information to a JSON file that external
// applications like the Task Manager and the test scripts can access.
await jsonFiles.writeJSON(context, JSON_PATH)
// console.log('context: ', context)
// console.log('JSON_PATH: ', JSON_PATH)
// Write the user data to the JSON file
await _this.jsonFiles.writeJSON(context, JSON_PATH)
return context
} catch (err) {
// Handle existing system user.
if (err.response.status === 422) {
try {
// Delete the existing user
await _this.deleteExistingSystemUser()
// Call this function again.
return _this.createSystemUser()
} catch (err2) {
console.error(
'Error in admin.js/createSystemUser() while trying generate new system user.'
)
// process.end(1)
throw err2
}
} else {
console.log('Error in admin.js/createSystemUser: ')
// process.end(1)
throw err
}
} catch (error) {
console.log('Error in admin.js/createSystemUser()')
throw error
}
}
@@ -129,6 +96,7 @@ class Admin {
})
await _this.User.deleteOne({ email: 'system@system.com' })
return true
} catch (err) {
console.log('Error in admin.js/deleteExistingSystemUser()')
throw err
@@ -152,7 +120,7 @@ class Admin {
Accept: 'application/json'
},
data: {
email: 'system@system.com',
email: existingUser.email,
password: existingUser.password
}
}
+4 -1
View File
@@ -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
@@ -405,7 +408,7 @@ class BchAdapter {
// Wait for data to come back from the wallet service.
const data = await this.waitForRPCResponse(rpcId)
console.log(`data: ${JSON.stringify(data, null, 2)}`)
// console.log(`data: ${JSON.stringify(data, null, 2)}`)
return data
} catch (err) {
+65 -6
View File
@@ -48,6 +48,7 @@ class IpfsFilesAdapter {
this.getFileMetadata = this.getFileMetadata.bind(this)
this.getPins = this.getPins.bind(this)
this.waitForRPCResponse = this.waitForRPCResponse.bind(this)
this.pinClaim = this.pinClaim.bind(this)
}
// This handler is triggered when RPC data comes in over IPFS.
@@ -63,7 +64,7 @@ class IpfsFilesAdapter {
this.rpcDataQueue.push(data)
} catch (err) {
console.error('Error in files/rpcHandler(): ', err)
// Do not throw error. This is a top-level function.
// Do not throw error. This is a top-level function.
}
}
@@ -80,7 +81,7 @@ class IpfsFilesAdapter {
// Add names to the IPFS IDs for each provider.
const initialServiceProviders =
this.ipfs.ipfsCoordAdapter.state.ipfsFileProviders
this.ipfs.ipfsCoordAdapter.state.ipfsFileProviders
const serviceProviders = []
for (let i = 0; i < initialServiceProviders.length; i++) {
const thisProvider = initialServiceProviders[i]
@@ -118,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
@@ -142,7 +147,7 @@ class IpfsFilesAdapter {
// Throw an error if this IPFS node has not yet made a connection to a
// wallet service provider.
const selectedProvider =
this.ipfs.ipfsCoordAdapter.state.selectedIpfsFileProvider
this.ipfs.ipfsCoordAdapter.state.selectedIpfsFileProvider
if (!selectedProvider) {
throw new Error('No IPFS File Pin Service provider available yet.')
}
@@ -185,13 +190,16 @@ class IpfsFilesAdapter {
// Throw an error if this IPFS node has not yet made a connection to a
// wallet service provider.
const selectedProvider =
this.ipfs.ipfsCoordAdapter.state.selectedIpfsFileProvider
this.ipfs.ipfsCoordAdapter.state.selectedIpfsFileProvider
if (!selectedProvider) {
throw new Error('No IPFS File Pin Service provider available yet.')
}
const { page } = inObj
const rpcData = {
endpoint: 'getPins'
endpoint: 'getPins',
page
}
// Generate a UUID for the call.
@@ -224,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
@@ -261,7 +273,7 @@ class IpfsFilesAdapter {
cnt++
// Exit if data was returned, or the window for a response expires.
// Exit if data was returned, or the window for a response expires.
} while (!dataFound && cnt < 10)
// console.log(`dataFound: ${dataFound}, cnt: ${cnt}`)
@@ -271,6 +283,53 @@ class IpfsFilesAdapter {
throw err
}
}
async pinClaim (inObj = {}) {
try {
// Throw an error if this IPFS node has not yet made a connection to a
// ipfs service provider.
const selectedProvider =
this.ipfs.ipfsCoordAdapter.state.selectedIpfsFileProvider
if (!selectedProvider) {
throw new Error('No IPFS File Provider Service is available yet. Try again in a few seconds.')
}
const { proofOfBurnTxid, cid, claimTxid, filename, address } = inObj
const rpcData = {
endpoint: 'pinClaim',
proofOfBurnTxid,
cid,
claimTxid,
filename,
address
}
// Generate a UUID for the call.
const rpcId = this.uid()
// Generate a JSON RPC command.
const cmd = this.jsonrpc.request(rpcId, 'file-pin', rpcData)
const cmdStr = JSON.stringify(cmd)
// console.log('cmdStr: ', cmdStr)
// Send the RPC command to selected wallet service.
const thisNode = this.ipfs.ipfsCoordAdapter.ipfsCoord.thisNode
await this.ipfs.ipfsCoordAdapter.ipfsCoord.useCases.peer.sendPrivateMessage(
selectedProvider,
cmdStr,
thisNode
)
// Wait for data to come back from the wallet service.
const data = await this.waitForRPCResponse(rpcId)
return data
} catch (err) {
console.error('Error in pinClaim()')
throw err
}
}
}
// module.exports = P2wdbAdapter
+7 -3
View File
@@ -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
}
+2
View File
@@ -4,11 +4,13 @@
// Load Mongoose models.
import Users from './models/users.js'
import Usage from './models/usage.js'
class LocalDB {
constructor () {
// Encapsulate dependencies
this.Users = Users
this.Usage = Usage
}
}
+15
View File
@@ -0,0 +1,15 @@
/*
Usage data model
*/
// Global npm libraries
import mongoose from 'mongoose'
const Usage = new mongoose.Schema({
ip: { type: String },
url: { type: String },
method: { type: String },
timestamp: { type: Date }
})
export default mongoose.model('usage', Usage)
+7
View File
@@ -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
+7
View File
@@ -20,6 +20,13 @@ class Wallet {
// Bind 'this' object to all subfunctions
this.instanceWalletWithoutInitialization = this.instanceWalletWithoutInitialization.bind(this)
this._instanceWallet = this._instanceWallet.bind(this)
this.openWallet = this.openWallet.bind(this)
this.instanceWallet = this.instanceWallet.bind(this)
this.incrementNextAddress = this.incrementNextAddress.bind(this)
this.getKeyPair = this.getKeyPair.bind(this)
this.optimize = this.optimize.bind(this)
this.getBalance = this.getBalance.bind(this)
}
// This is used for initializing the wallet, without waiting to update the wallet
+1 -1
View File
@@ -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') {
+26 -2
View File
@@ -41,6 +41,7 @@ class IpfsRESTControllerLib {
this.getPins = this.getPins.bind(this)
this.cid2json = this.cid2json.bind(this)
this.downloadFile = this.downloadFile.bind(this)
this.pinClaim = this.pinClaim.bind(this)
}
/**
@@ -261,11 +262,11 @@ class IpfsRESTControllerLib {
*/
async getPins (ctx) {
try {
// const { cid } = ctx.params
const { page } = ctx.params
const ipfsFiles = this.adapters.ipfsFiles
const pinData = await ipfsFiles.getPins()
const pinData = await ipfsFiles.getPins({ page })
console.log('getPins() pinData: ', pinData)
ctx.body = pinData
@@ -300,6 +301,29 @@ class IpfsRESTControllerLib {
}
}
/**
* @api {post} /ipfs/pin-claim Process pin claim.
* @apiPermission public
* @apiName PinClaim
* @apiGroup REST IPFS
* @apiDescription Process pin claim.
*
* @apiExample Example usage:
* curl -H "Content-Type: application/json" -X POST -d '{ "proofOfBurnTxid": "be4b63156c93f58ed311d403d9f756deda9abbc81d0fef8fbe5d769538b4261c", "cid": "bafybeied3zdwdiro7fqytyha2yfband4lwcrtozmf6shynylt3kexh26dq", "claimTxid": "c71e2f2cdf8658d90c61ac6183b8ffeeb359779807b317386044705d8352f0f2", "filename": "mutable-67ccefcca67097473e78ca10.json", "address": "bitcoincash:qqs2wrahl6azn9qdyrmp9ygeejqvzr8ruv7e9m30fr" }' http://localhost:5001/ipfs/pin-claim *
*/
async pinClaim (ctx) {
try {
const body = ctx.request.body
const result = await this.useCases.ipfs.pinClaim(body)
ctx.body = result
} catch (err) {
console.log('Error in ipfs/controller.js/cid2json(): ', err.message)
this.handleError(ctx, err)
}
}
// DRY error handler
handleError (ctx, err) {
// console.log('handleError() err.status: ', err.status)
+2 -2
View File
@@ -60,9 +60,9 @@ class IpfsRouter {
this.router.get('/download/:cid', this.ipfsRESTController.downloadFile)
this.router.get('/service', this.ipfsRESTController.getService)
this.router.get('/file-info/:cid', this.ipfsRESTController.getFileInfo)
this.router.get('/pins', this.ipfsRESTController.getPins)
this.router.get('/pins/:page', this.ipfsRESTController.getPins)
this.router.get('/cid2json/:cid', this.ipfsRESTController.cid2json)
this.router.post('/pin-claim', this.ipfsRESTController.pinClaim)
// Attach the Controller routes to the Koa app.
app.use(this.router.routes())
app.use(this.router.allowedMethods())
+12 -1
View File
@@ -10,6 +10,8 @@ import UserRESTControllerLib from './controller.js'
import Validators from '../middleware/validators.js'
import config from '../../../../config/index.js'
let _this
class UserRouter {
@@ -34,6 +36,7 @@ class UserRouter {
}
// Encapsulate dependencies.
this.config = config
this.userRESTController = new UserRESTControllerLib(dependencies)
this.validators = new Validators()
@@ -52,7 +55,7 @@ class UserRouter {
}
// Define the routes and attach the controller.
this.router.post('/', this.userRESTController.createUser)
this.router.post('/', this.createUser)
this.router.get('/', this.getAll)
this.router.get('/:id', this.getById)
this.router.put('/:id', this.updateUser)
@@ -63,6 +66,14 @@ class UserRouter {
app.use(this.router.allowedMethods())
}
async createUser (ctx, next) {
if (process.env.DISABLE_NEW_ACCOUNTS) {
await _this.validators.ensureAdmin(ctx, next)
}
await _this.userRESTController.createUser(ctx, next)
return true
}
async getAll (ctx, next) {
await _this.validators.ensureUser(ctx, next)
await _this.userRESTController.getUsers(ctx, next)
+61 -24
View File
@@ -1,6 +1,6 @@
/*
This Controller library is concerned with timer-based functions that are
kicked off periodicially.
kicked off periodically.
*/
import config from '../../config/index.js'
@@ -23,60 +23,97 @@ class TimerControllers {
this.debugLevel = localConfig.debugLevel
// Constants
this.cleanUsageInterval = 60000 * 60 // 1 hour
this.backupUsageInterval = 60000 * 10 // 10 minutes
this.rebootInterval = 60000 * 60 * 12 // 12 hours
// Encapsulate dependencies
this.config = config
// Bind 'this' object to all subfunctions.
this.exampleTimerFunc = this.exampleTimerFunc.bind(this)
this.cleanUsage = this.cleanUsage.bind(this)
// this.startTimers()
this.backupUsage = this.backupUsage.bind(this)
this.autoReboot = this.autoReboot.bind(this)
}
// Start all the time-based controllers.
startTimers () {
// Any new timer control functions can be added here. They will be started
// when the server starts.
this.optimizeWalletHandle = setInterval(this.exampleTimerFunc, 60000 * 60)
this.cleanUsageHandle = setInterval(this.cleanUsage, 60000 * 60) // 1 hour
this.cleanUsageHandle = setInterval(this.cleanUsage, this.cleanUsageInterval)
this.backupUsageHandle = setInterval(this.backupUsage, this.backupUsageInterval)
// Periodically reboot this app. Helia has a slight memory leak, and occasionally
// runs into unrecoverable network issues. Rebooting is one way to overcome these
// black-box issues.
this.rebootHandle = setInterval(this.autoReboot, this.rebootInterval)
return true
}
stopTimers () {
clearInterval(this.optimizeWalletHandle)
clearInterval(this.cleanusageHandle)
}
// Replace this example function with your own timer handler.
exampleTimerFunc (negativeTest) {
try {
// console.log('Example timer controller executed.')
if (negativeTest) throw new Error('test error')
return true
} catch (err) {
console.error('Error in exampleTimerFunc(): ', err)
// Note: Do not throw an error. This is a top-level function.
return false
}
clearInterval(this.cleanUsageHandle)
clearInterval(this.backupUsageHandle)
}
// Clean the usage state so that stats reflect the last 24 hours.
cleanUsage () {
try {
clearInterval(this.cleanUsageHandle)
const now = new Date()
console.log(`cleanUsage() Timer Controller executing at ${now.toLocaleString()}`)
this.useCases.usage.cleanUsage()
this.cleanUsageHandle = setInterval(this.cleanUsage, this.cleanUsageInterval)
return true
} catch (err) {
console.error('Error in time-controller.js/cleanUsage(): ', err)
this.cleanUsageHandle = setInterval(this.cleanUsage, this.cleanUsageInterval)
// Note: Do not throw an error. This is a top-level function.
return false
}
}
// Backup the usage stats to the database
async backupUsage () {
try {
clearInterval(this.backupUsageHandle)
console.log('backupUsage() Timer Controller executing at ', new Date().toLocaleString())
// Clear the database of old usage data.
await this.useCases.usage.clearUsage()
// Save the current usage snapshot to the database.
await this.useCases.usage.saveUsage()
this.backupUsageHandle = setInterval(this.backupUsage, this.backupUsageInterval)
return true
} catch (err) {
console.error('Error in time-controller.js/backupUsage(): ', err)
this.backupUsageHandle = setInterval(this.backupUsage, this.backupUsageInterval)
// Note: Do not throw an error. This is a top-level function.
return false
}
}
// This timer is used to auto-reboot the processes. If this is being run in Docker container
// (the target for production), the Docker container should automatically restart the timer.
// This is a temporary fix to the IPFS node stalling occasionally until a better fix can be
// found.
autoReboot () {
console.log('Rebooting service.')
process.exit(1)
}
}
export default TimerControllers
+3
View File
@@ -32,6 +32,9 @@ class UseCases {
// Run any startup Use Cases at the start of the app.
async start () {
// Load the usage stats from the database
await this.usage.loadUsage()
console.log('Async Use Cases have been started.')
return true
+19 -1
View File
@@ -37,6 +37,7 @@ class IpfsUseCases {
this.getWritePrice = this.getWritePrice.bind(this)
this.getCidMetadata = this.getCidMetadata.bind(this)
this.cid2json = this.cid2json.bind(this)
this.pinClaim = this.pinClaim.bind(this)
// State
this.lastWritePriceUpdate = null // Used to periodically update write price.
@@ -154,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 })
@@ -225,9 +227,11 @@ class IpfsUseCases {
// Throw an error if this is not a JSON file
const filename = fileMetadata.filename
if (!filename.endsWith('.json')) {
throw new Error(`CID ${cid} does not resolve to a JSON file.`)
}
// Retrieve the CID content and store it in a Buffer.
const helia = this.adapters.ipfs.ipfs
const fileChunks = []
@@ -281,6 +285,20 @@ class IpfsUseCases {
}
}
async pinClaim (inObj = {}) {
try {
// Throw an error if ipfs-bch-wallet-consumer can not communicate with the ipfs-file-pin-service.
const metadata = await this.adapters.ipfsFiles.pinClaim(inObj)
return {
success: metadata.success,
message: metadata.message
}
} catch (err) {
console.error('Error in ipfs-use-cases.js/pinClaim(): ', err)
throw err
}
}
// async downloadCid2 (inObj = {}) {
// try {
// const { cid } = inObj
+72
View File
@@ -3,6 +3,10 @@
for tracking the usage of REST API and JSON RPC calls. This library is used
by admins to keep an eye on how many API calls were made in a 24-hour and
1-hour time period.
Usage stats are held in memory. But they are periodically backed up to the
Mongo database. On startup, the usage stats are loaded from the database.
This allows the usage stats to be persisted across restarts.
*/
// This global variable is used to share data between the REST middleware and
@@ -19,11 +23,17 @@ class UsageUseCases {
)
}
// Encapsulate dependencies
this.UsageModel = this.adapters.localdb.Usage
// Bind 'this' object to all subfunctions
this.cleanUsage = this.cleanUsage.bind(this)
this.getRestSummary = this.getRestSummary.bind(this)
this.getTopIps = this.getTopIps.bind(this)
this.getTopEndpoints = this.getTopEndpoints.bind(this)
this.clearUsage = this.clearUsage.bind(this)
this.saveUsage = this.saveUsage.bind(this)
this.loadUsage = this.loadUsage.bind(this)
// State
}
@@ -36,7 +46,13 @@ class UsageUseCases {
const now = new Date()
const twentyFourHoursAgo = now.getTime() - (60000 * 60 * 24)
console.log('cleanUsage() now: ', now)
console.log('cleanUsage() restCalls.length before filtering: ', restCalls.length)
restCalls = restCalls.filter(x => x.timestamp > twentyFourHoursAgo)
console.log('cleanUsage() restCalls.length after filtering: ', restCalls.length)
if (!restCalls) restCalls = []
return restCalls
} catch (err) {
console.error('Error in usage-use-cases.js/cleanUsage()')
@@ -104,6 +120,62 @@ class UsageUseCases {
throw err
}
}
// Clear the usage database data
async clearUsage () {
try {
await this.UsageModel.deleteMany({})
// Debugging: verify the database is empty
// Delete this code after debugging
const usage = await this.UsageModel.find({})
console.log('clearUsage() usage: ', usage)
return true
} catch (err) {
console.error('Error in usage-use-cases.js/clearUsage()')
throw err
}
}
// Save the usage data to the database
async saveUsage (inObj = {}) {
try {
for (let i = 0; i < restCalls.length; i++) {
const thisRestCall = restCalls[i]
const usageData = {
ip: thisRestCall.ip,
url: thisRestCall.url,
method: thisRestCall.method,
timestamp: thisRestCall.timestamp
}
const usage = new this.UsageModel(usageData)
await usage.save()
}
return true
} catch (err) {
console.error('Error in usage-use-cases.js/saveUsage()')
throw err
}
}
// Load usage data from the database
async loadUsage () {
try {
const usage = await this.UsageModel.find({})
// console.log('usage: ', usage)
restCalls = usage
if (!restCalls) restCalls = []
return usage
} catch (err) {
console.error('Error in usage-use-cases.js/loadUsage(): ', err)
return false
// throw err
}
}
}
// This Koa middleware is called any time there is a REST API. It logs the
@@ -0,0 +1,45 @@
/*
Liveness test runs before all other tests to ensure the server is running.
*/
// Public npm libraries
import { assert } from 'chai'
import axios from 'axios'
import testUtils from '../../utils/test-utils.js'
// const sinon = require('sinon')
// Local support libraries
import config from '../../../config/index.js'
// import Server from '../../../bin/server.js'
// import testUtils from '../../utils/test-utils.js'
// const adminLib = new AdminLib()
const LOCALHOST = `http://localhost:${config.port}`
describe('#Check Server Liveness', () => {
// before(async () => {
it('should confirm the server is running', async () => {
try {
const response = await axios.get(`${LOCALHOST}/`)
assert(response.status === 200, 'Server is running, continuing with E2E tests.')
} catch (err) {
console.log('\nServer is not running, exiting tests.')
console.log('Start the server with `npm run start:e2e:server` before running E2E tests.\n')
console.log('Ensure running npm run docs before running the test server')
process.exit(1)
}
})
it('should confirm the server is running over test enviroment', async () => {
try {
const res = await testUtils.loginAdminUser()
assert.property(res, 'user')
assert.property(res, 'token')
assert.property(res, 'id')
} catch (err) {
console.log('\nServer is not running over test enviroment, exiting tests.')
console.log('Start the server with `npm run start:e2e:server` before running E2E tests.\n')
process.exit(1)
}
})
})
+5 -8
View File
@@ -13,10 +13,8 @@ import axios from 'axios'
// Local support libraries
import config from '../../../config/index.js'
import Server from '../../../bin/server.js'
// import Server from '../../../bin/server.js'
import testUtils from '../../utils/test-utils.js'
import AdminLib from '../../../src/adapters/admin.js'
const adminLib = new AdminLib()
// const request = supertest.agent(app.listen())
const context = {}
@@ -26,10 +24,12 @@ const LOCALHOST = `http://localhost:${config.port}`
if (!config.noMongo) {
describe('Auth', () => {
before(async () => {
const app = new Server()
// const app = new Server()
// This should be the first instruction. It starts the REST API server.
await app.startServer()
// await app.startServer()
// TODO:
// Stop the IPFS node for the rest of the e2e tests.
// await app.controllers.adapters.ipfs.stop()
@@ -37,9 +37,6 @@ if (!config.noMongo) {
// Delete all previous users in the database.
await testUtils.deleteAllUsers()
// Create a new admin user.
await adminLib.createSystemUser()
const userObj = {
email: 'test@test.com',
password: 'pass',
-41
View File
@@ -5,22 +5,11 @@ import axios from 'axios'
import sinon from 'sinon'
import util from 'util'
import UserController from '../../../src/controllers/rest-api/users/controller.js'
import Adapters from '../../../src/adapters/index.js'
import UseCases from '../../../src/use-cases/index.js'
// import UserController from '../../../src/controllers/rest-api/users/controller.js'
// import Adapters from '../../../src/adapters/index.js'
import EventEmitter from 'events'
util.inspect.defaultOptions = { depth: 1 }
const LOCALHOST = `http://localhost:${config.port}`
const context = {}
const adapters = new Adapters({ eventEmitter: new EventEmitter() })
// import UseCases from '../../../src/use-cases/index.js'
let uut
let sandbox
// const mockContext = require('../../unit/mocks/ctx-mock').context
@@ -56,9 +45,6 @@ if (!config.noMongo) {
})
beforeEach(() => {
const useCases = new UseCases({ adapters })
uut = new UserController({ adapters, useCases })
sandbox = sinon.createSandbox()
})
@@ -274,33 +260,6 @@ if (!config.noMongo) {
assert.hasAnyKeys(users[0], ['type', '_id', 'email'])
assert.isNumber(users.length)
})
it('should return a 422 http status if biz-logic throws an error', async () => {
try {
const { token } = context
// Force an error
sandbox
.stub(uut.useCases.user, 'getAllUsers')
.rejects(new Error('test error'))
const options = {
method: 'GET',
url: `${LOCALHOST}/users`,
headers: {
Accept: 'application/json',
Authorization: `Bearer ${token}`
}
}
await axios(options)
assert.fail('Unexpected code path!')
} catch (err) {
// console.log(err)
assert.equal(err.response.status, 422)
assert.equal(err.response.data, 'test error')
}
})
})
describe('GET /users/:id', () => {
+15
View File
@@ -37,6 +37,21 @@ describe('#adapters', () => {
assert.equal(result, true)
})
it('should not start ipfs on test enviroment', async () => {
// Mock dependencies
uut.config.getJwtAtStartup = true
uut.config.useIpfs = true
uut.config.env = 'test'
sandbox.stub(uut.fullStackJwt, 'getJWT').resolves()
sandbox.stub(uut.fullStackJwt, 'instanceBchjs').resolves()
const ipfsSpy = sandbox.stub(uut.ipfs, 'start').resolves(null)
const result = await uut.start()
assert.isTrue(ipfsSpy.notCalled)
assert.equal(result, true)
})
it('should catch and throw an error', async () => {
try {
@@ -19,26 +19,13 @@ describe('Admin', () => {
if (!config.noMongo) {
describe('loginAdmin()', () => {
it('should logind admin', async () => {
it('should login admin', async () => {
try {
const error = new Error('test error')
error.response = {
status: 422
}
// sandbox.stub(uut.axios, 'request').onFirstCall().throws(error)
sandbox.stub(uut.jsonFiles, 'readJSON').resolves({ password: 'pass' })
sandbox.stub(uut.axios, 'request').resolves(true)
const result = await uut.loginAdmin()
const user = result.data.user
assert.property(user, '_id')
assert.property(user, 'email')
assert.property(user, 'type')
assert.isString(user._id)
assert.isString(user.email)
assert.isString(user.type)
assert.equal(user.type, 'admin')
assert.isTrue(result)
} catch (err) {
assert(false, 'Unexpected result')
}
@@ -48,13 +35,13 @@ describe('Admin', () => {
try {
// Returns an erroneous password to force
// an auth error
sandbox.stub(uut.axios, 'request').throws(new Error('test error'))
sandbox.stub(uut.jsonFiles, 'readJSON').resolves({ password: 'wrong' })
await uut.loginAdmin()
assert(false, 'Unexpected result')
} catch (err) {
assert.equal(err.response.status, 401)
assert.include(err.response.data, 'Unauthorized')
assert.include(err.message, 'test error')
}
})
})
@@ -62,6 +49,7 @@ describe('Admin', () => {
describe('createSystemUser()', () => {
it('should create admin', async () => {
try {
await uut.deleteExistingSystemUser()
const result = await uut.createSystemUser()
assert.property(result, 'email')
@@ -72,44 +60,59 @@ describe('Admin', () => {
assert(false, 'Unexpected result')
}
})
it('should handle axios error', async () => {
it('should update admin password', async () => {
try {
const error1 = new Error('test error')
error1.response = {
status: 422
}
const error2 = new Error('test error')
error1.response = {
status: 500
}
// The loginAdmin() function in some use cases is recursive
// after handling the 422 error, it gets called again
sandbox
.stub(uut.axios, 'request')
.onFirstCall()
.throws(error1)
.onSecondCall()
.throws(error2)
uut.config.adminPassword = 'newpassword'
await uut.createSystemUser()
const fakeUser = {
password: 'oldpassword',
save: () => { return 'token' },
generateToken: () => { return 'token' }
}
sandbox.stub(uut.User, 'findOne').resolves(fakeUser)
sandbox.stub(uut.jsonFiles, 'writeJSON').resolves(true)
const result = await uut.createSystemUser()
assert.property(result, 'email')
assert.property(result, 'password')
assert.property(result, 'id')
assert.property(result, 'token')
assert.equal(fakeUser.password, 'newpassword', 'password should be updated')
} catch (err) {
console.log(err)
assert(false, 'Unexpected result')
}
})
it('should handle error', async () => {
try {
sandbox.stub(uut.User, 'findOne').throws(new Error('test error'))
await uut.createSystemUser()
assert.fail('Unexpected result')
} catch (err) {
assert.include(err.message, 'test error')
}
})
})
it('should handle errors when remove user', async () => {
describe('deleteExistingSystemUser()', () => {
it('should delete admin', async () => {
try {
const error1 = new Error('test error')
error1.response = {
status: 422
}
sandbox.stub(uut.axios, 'request').throws(error1)
sandbox.stub(uut.User, 'deleteOne').throws(new Error('test error'))
await uut.createSystemUser()
sandbox.stub(uut.User, 'deleteOne').resolves(true)
const result = await uut.deleteExistingSystemUser()
assert.isTrue(result)
} catch (err) {
assert(false, 'Unexpected result')
}
})
it('should handle error when deleting admin', async () => {
try {
sandbox.stub(uut.User, 'deleteOne').throws(new Error('test error'))
await uut.deleteExistingSystemUser()
assert.fail('Unexpected result')
} catch (err) {
assert.include(err.message, 'test error')
}
+186
View File
@@ -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.')
}
})
})
})
+191 -29
View File
@@ -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.')
})
})
})
@@ -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.')
}
})
})
})
+223
View File
@@ -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)
})
})
})
@@ -41,5 +41,17 @@ describe('#passport.js', () => {
assert.include(err.message, 'cant auth user')
}
})
it('should authenticate user', async () => {
const ctx = {}
const errMock = null
const userMock = {
_id: '123',
email: 'test@test.com'
}
sandbox.stub(uut.passport, 'authenticate').yields(errMock, userMock)
const user = await uut.authUser(ctx)
assert.equal(user._id, userMock._id)
assert.equal(user.email, userMock.email)
})
})
})
+7
View File
@@ -54,6 +54,13 @@ describe('#User-Adapter', () => {
assert.notEqual(testuser.password, 'password')
})
it('should ignore password encrption if password property is not provided', async () => {
const lastPassword = testuser.password
await testuser.save()
// console.log('testuser: ', testuser)
assert.equal(testuser.password, lastPassword)
})
})
describe('#validatePassword', () => {
+21
View File
@@ -38,4 +38,25 @@ describe('#Controllers', () => {
await uut.attachControllers(app)
})
})
describe('#attachRESTControllers', () => {
it('should attach the controllers', async () => {
const app = {
use: () => {}
}
await uut.attachRESTControllers(app)
})
})
describe('#initAdapters', () => {
it('should attach the controllers', async () => {
sandbox.stub(uut.adapters, 'start').resolves({})
await uut.initAdapters()
})
})
describe('#initUseCases', () => {
it('should attach the controllers', async () => {
sandbox.stub(uut.useCases, 'start').resolves({})
await uut.initUseCases()
})
})
})
@@ -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()
@@ -79,11 +79,22 @@ describe('#Auth-REST-Router', () => {
await uut.authUser(ctx)
})
it('should catch and throw an error', async () => {
it('should catch and throw passport error', async () => {
try {
// Force an error
sandbox.stub(uut.passport, 'authUser').rejects('test error')
await uut.authUser(ctx)
} catch (err) {
// console.log('err: ', err)
assert.include(err.message, 'Unauthorized')
}
})
it('should handle error if user is not found!', async () => {
try {
// Force an error
sandbox.stub(uut.passport, 'authUser').resolves(null)
await uut.authUser(ctx)
} catch (err) {
// console.log('err: ', err)
@@ -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)
})
})
})
@@ -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)
})
})
})
@@ -248,4 +248,247 @@ describe('#IPFS REST API', () => {
assert.property(ctx.body, 'thisNode')
})
})
describe('#viewFile', () => {
it('should return 422 status on biz logic error', async () => {
try {
// Force an error
ctx.params = {
cid: 'bafybeib2rphswe7clvclverw7xi7nejkpkh4mdfcurdmcgw7wcup7za6wy'
}
sandbox.stub(uut.useCases.ipfs, 'downloadCid').throws(new Error('test error'))
uut.adapters.ipfs.ipfsCoordAdapter = {}
ctx.request.body = {}
await uut.viewFile(ctx)
assert.fail('Unexpected result')
} catch (err) {
// console.log('err: ', err)
assert.equal(err.status, 422)
assert.include(err.message, 'test error')
}
})
it('should return 200 status on success', async () => {
ctx.params = {
cid: 'bafybeib2rphswe7clvclverw7xi7nejkpkh4mdfcurdmcgw7wcup7za6wy'
}
// Mock dependencies
sandbox.stub(uut.useCases.ipfs, 'downloadCid').resolves({ success: true, readStream: 'readStream' })
uut.adapters.ipfs.ipfsCoordAdapter = {
ipfsCoord: {
thisNode: {}
}
}
ctx.request.body = {}
await uut.viewFile(ctx)
assert.exists(ctx.body)
})
})
describe('#downloadFile', () => {
it('should return 422 status on biz logic error', async () => {
try {
ctx.params = {
cid: 'bafybeib2rphswe7clvclverw7xi7nejkpkh4mdfcurdmcgw7wcup7za6wy'
}
// Force an error
sandbox.stub(uut.useCases.ipfs, 'downloadCid').throws(new Error('test error'))
uut.adapters.ipfs.ipfsCoordAdapter = {}
ctx.request.body = {}
await uut.downloadFile(ctx)
assert.fail('Unexpected result')
} catch (err) {
// console.log('err: ', err)
assert.equal(err.status, 422)
assert.include(err.message, 'test error')
}
})
it('should return 200 status on success', async () => {
ctx.params = {
cid: 'bafybeib2rphswe7clvclverw7xi7nejkpkh4mdfcurdmcgw7wcup7za6wy'
}
// Mock dependencies
sandbox.stub(uut.useCases.ipfs, 'downloadCid').resolves({ success: true, readStream: 'readStream' })
uut.adapters.ipfs.ipfsCoordAdapter = {
ipfsCoord: {
thisNode: {}
}
}
ctx.request.body = {}
await uut.downloadFile(ctx)
assert.exists(ctx.body)
})
})
describe('#getService', () => {
it('should return 422 status on biz logic error', async () => {
try {
// Force an error
uut.adapters.ipfs.ipfsCoordAdapter = {}
ctx.request.body = {}
await uut.getService(ctx)
assert.fail('Unexpected result')
} catch (err) {
// console.log('err: ', err)
assert.equal(err.status, 422)
assert.include(err.message, 'Cannot read properties of undefined')
}
})
it('should return 200 status on success', async () => {
// Mock dependencies
uut.adapters.ipfs.ipfsCoordAdapter = {
state: {
selectedIpfsFileProvider: 'provider'
}
}
ctx.request.body = {}
await uut.getService(ctx)
assert.isTrue(ctx.body.success)
})
})
describe('#getFileInfo', () => {
it('should return 422 status on biz logic error', async () => {
try {
ctx.params = {
cid: 'bafybeib2rphswe7clvclverw7xi7nejkpkh4mdfcurdmcgw7wcup7za6wy'
}
sandbox.stub(uut.adapters.ipfsFiles, 'getFileMetadata').throws(new Error('test error'))
ctx.request.body = {}
await uut.getFileInfo(ctx)
assert.fail('Unexpected result')
} catch (err) {
// console.log('err: ', err)
assert.equal(err.status, 422)
assert.include(err.message, 'test error')
}
})
it('should return 200 status on success', async () => {
ctx.params = {
cid: 'bafybeib2rphswe7clvclverw7xi7nejkpkh4mdfcurdmcgw7wcup7za6wy'
}
// Mock dependencies
sandbox.stub(uut.adapters.ipfsFiles, 'getFileMetadata').resolves({ success: true, filename: 'filename' })
ctx.request.body = {}
await uut.getFileInfo(ctx)
assert.isTrue(ctx.body.success)
})
})
describe('#getPins', () => {
it('should return 422 status on biz logic error', async () => {
try {
sandbox.stub(uut.adapters.ipfsFiles, 'getPins').throws(new Error('test error'))
ctx.request.body = {}
ctx.params = { page: 1 }
await uut.getPins(ctx)
assert.fail('Unexpected result')
} catch (err) {
assert.equal(err.status, 422)
assert.include(err.message, 'test error')
}
})
it('should return 200 status on success', async () => {
// Mock dependencies
sandbox.stub(uut.adapters.ipfsFiles, 'getPins').resolves({ success: true })
ctx.request.body = {}
ctx.params = { page: 1 }
await uut.getPins(ctx)
assert.isTrue(ctx.body.success)
})
})
describe('#cid2json', () => {
it('should return 422 status on biz logic error', async () => {
try {
ctx.params = {
cid: 'bafybeib2rphswe7clvclverw7xi7nejkpkh4mdfcurdmcgw7wcup7za6wy'
}
sandbox.stub(uut.useCases.ipfs, 'cid2json').throws(new Error('test error'))
ctx.request.body = {}
await uut.cid2json(ctx)
assert.fail('Unexpected result')
} catch (err) {
assert.equal(err.status, 422)
assert.include(err.message, 'test error')
}
})
it('should return 200 status on success', async () => {
ctx.params = {
cid: 'bafybeib2rphswe7clvclverw7xi7nejkpkh4mdfcurdmcgw7wcup7za6wy'
}
// Mock dependencies
sandbox.stub(uut.useCases.ipfs, 'cid2json').resolves({ success: true })
ctx.request.body = {}
await uut.cid2json(ctx)
assert.isTrue(ctx.body.success)
})
})
describe('#pinClaim', () => {
it('should return 422 status on biz logic error', async () => {
try {
sandbox.stub(uut.useCases.ipfs, 'pinClaim').throws(new Error('test error'))
ctx.request.body = {}
await uut.pinClaim(ctx)
assert.fail('Unexpected result')
} catch (err) {
assert.equal(err.status, 422)
assert.include(err.message, 'test error')
}
})
it('should return 200 status on success', async () => {
// Mock dependencies
sandbox.stub(uut.useCases.ipfs, 'pinClaim').resolves({ success: true })
ctx.request.body = {}
await uut.pinClaim(ctx)
assert.isTrue(ctx.body.success)
})
})
})
@@ -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)
})
})
})
@@ -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)
})
})
})
@@ -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')
}
})
})
})
@@ -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)
})
})
})
@@ -20,7 +20,7 @@ describe('#RESTControllers', () => {
let sandbox
// let ctx
before(async () => {})
before(async () => { })
beforeEach(() => {
const useCases = new UseCasesMock()
@@ -64,4 +64,19 @@ describe('#RESTControllers', () => {
}
})
})
describe('#attachRESTControllers', () => {
it('should attach controllers without mongo service', () => {
uut.config.noMongo = true
const app = { use: () => {} }
uut.attachRESTControllers(app)
})
it('should attach controllers with mongo service', () => {
uut.config.noMongo = false
const app = { use: () => {} }
uut.attachRESTControllers(app)
})
})
})
@@ -153,6 +153,20 @@ describe('#Users-REST-Controller', () => {
// Assert that expected properties exist in the returned data.
assert.property(ctx.response.body, 'user')
})
it('should run next function if it exists', async () => {
// Mock dependencies
const nextSpy = sandbox.spy()
sandbox.stub(uut.useCases.user, 'getUser').resolves({ _id: '123' })
await uut.getUser(ctx, nextSpy)
// Assert the expected HTTP response
assert.equal(ctx.status, 200)
// Assert that expected properties exist in the returned data.
assert.property(ctx.response.body, 'user')
assert.isTrue(nextSpy.calledOnce)
})
it('should return other error status passed by biz logic', async () => {
try {
@@ -79,4 +79,70 @@ describe('#Users-REST-Router', () => {
}
})
})
describe('#createUser', () => {
it('should ignore admin validator when DISABLE_NEW_ACCOUNTS is not defined', async () => {
// Stub functions
const validationSpy = sandbox.stub(uut.validators, 'ensureAdmin').resolves(true)
sandbox.stub(uut.userRESTController, 'createUser').resolves(true)
// Call function
await uut.createUser()
// Assertions
assert.isTrue(validationSpy.notCalled, 'Admin validator should not be called')
})
it('should ensure admin when DISABLE_NEW_ACCOUNTS is defined', async () => {
// Set environment variable
process.env.DISABLE_NEW_ACCOUNTS = true
// Stub functions
const validationSpy = sandbox.stub(uut.validators, 'ensureAdmin').resolves(true)
sandbox.stub(uut.userRESTController, 'createUser').resolves(true)
// Call function
await uut.createUser()
// Assertions
assert.isTrue(validationSpy.calledOnce, 'Admin validator should be called')
})
})
describe('#getAll', () => {
it('should route to controller', async () => {
sandbox.stub(uut.validators, 'ensureUser').resolves(true)
const spy = sandbox.stub(uut.userRESTController, 'getUsers').resolves(true)
await uut.getAll()
assert.isTrue(spy.calledOnce)
})
})
describe('#getById', () => {
it('should route to controller', async () => {
sandbox.stub(uut.validators, 'ensureUser').resolves(true)
const spy = sandbox.stub(uut.userRESTController, 'getUser').resolves(true)
await uut.getById()
assert.isTrue(spy.calledOnce)
})
})
describe('#updateUser', () => {
it('should route to controller', async () => {
sandbox.stub(uut.validators, 'ensureTargetUserOrAdmin').resolves(true)
sandbox.stub(uut.userRESTController, 'getUser').resolves(true)
const spy = sandbox.stub(uut.userRESTController, 'updateUser').resolves(true)
await uut.updateUser()
assert.isTrue(spy.calledOnce)
})
})
describe('#deleteUser', () => {
it('should route to controller', async () => {
sandbox.stub(uut.validators, 'ensureTargetUserOrAdmin').resolves(true)
sandbox.stub(uut.userRESTController, 'getUser').resolves(true)
const spy = sandbox.stub(uut.userRESTController, 'deleteUser').resolves(true)
await uut.deleteUser()
assert.isTrue(spy.calledOnce)
})
})
})
+27 -10
View File
@@ -66,19 +66,19 @@ describe('#Timer-Controllers', () => {
})
})
describe('#exampleTimerFunc', () => {
it('should kick off the Use Case', async () => {
const result = await uut.exampleTimerFunc()
// describe('#exampleTimerFunc', () => {
// it('should kick off the Use Case', async () => {
// const result = await uut.exampleTimerFunc()
assert.equal(result, true)
})
// assert.equal(result, true)
// })
it('should return false on error', async () => {
const result = await uut.exampleTimerFunc(true)
// it('should return false on error', async () => {
// const result = await uut.exampleTimerFunc(true)
assert.equal(result, false)
})
})
// assert.equal(result, false)
// })
// })
describe('#cleanUsage', () => {
it('should kick off the Use Case', async () => {
@@ -94,4 +94,21 @@ describe('#Timer-Controllers', () => {
assert.equal(result, false)
})
})
describe('#backupUsage', () => {
it('should kick off the Use Case', async () => {
const result = await uut.backupUsage()
assert.equal(result, true)
})
it('should return false on error', async () => {
sandbox.stub(uut.useCases.usage, 'clearUsage').throws(new Error('test error'))
// sandbox.stub(uut.useCases.usage, 'saveUsage').throws(new Error('test error'))
const result = await uut.backupUsage()
assert.equal(result, false)
})
})
})
+4 -4
View File
@@ -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/'
+15
View File
@@ -34,6 +34,12 @@ describe('#passport', () => {
passportCallback(id, 'password', done)
})
it('should handle not found user', () => {
// Mock Users model.
sandbox.stub(User, 'findOne').resolves(null)
passportCallback(id, 'password', done)
})
it('should return if password is validated', () => {
// Mock Users model.
@@ -41,6 +47,15 @@ describe('#passport', () => {
passportCallback(id, 'password', done)
})
it('should handle error on password validation', () => {
// Mock Users model.
const userMock = {
validatePassword: () => false
}
sandbox.stub(User, 'findOne').resolves(userMock)
passportCallback(id, 'password', done)
})
it('should catch a high-level error', () => {
// Force an error
+1 -1
View File
@@ -30,7 +30,7 @@ describe('#server', () => {
sandbox.stub(uut.adminLib, 'createSystemUser').resolves(true)
sandbox.stub(uut.controllers, 'attachControllers').resolves()
uut.config.env = 'dev'
uut.config.port = 5040
const result = await uut.startServer()
// console.log('result: ', result)
+94 -4
View File
@@ -1,10 +1,26 @@
/*
Mocks for the Adapter library.
*/
class BlockstoreMock {
constructor () {
this.get = async () => {}
}
}
class FsMock {
constructor () {
this.ls = async function* () {
yield { path: 'test.txt', cid: 'QmS4ghgMgfFvqPjB4WKXHaN15ZyT4K4JYZxY5X5x5x5x5' }
}
this.cat = async function* () {
yield Buffer.from('test data')
}
}
}
class IpfsAdapter {
constructor () {
this.ipfs = {
blockstore: new BlockstoreMock(),
fs: new FsMock(),
files: {
stat: () => {}
}
@@ -14,7 +30,7 @@ class IpfsAdapter {
class IpfsCoordAdapter {
constructor () {
this.config = {}
this.ipfsCoord = {
adapters: {
ipfs: {
@@ -34,7 +50,9 @@ class IpfsCoordAdapter {
this.peerInputHandler = () => {}
this.state = {
serviceProviders: []
serviceProviders: [],
serviceProvidersByType: '',
selectedIpfsFileProvider:''
}
}
}
@@ -79,6 +97,39 @@ const localdb = {
}
},
Usage: class Usage {
static findById () {}
static find () {}
static findOne () {
return {
validatePassword: localdb.validatePassword
}
}
async save () {
return {}
}
generateToken () {
return '123'
}
toJSON () {
return {}
}
async remove () {
return true
}
async validatePassword () {
return true
}
static async deleteMany(){
return true
}
},
validatePassword: () => {
return true
}
@@ -136,7 +187,46 @@ class BchUseCaseMock {
async waitForRPCResponse () {
return {}
}
async selectProvider (){
return true
}
}
const bch = new BchUseCaseMock()
const wallet = {
bchWallet: bch
export default { ipfs, localdb, bch }
}
const ipfsFiles = {
pinClaim: () => {
return {
success: true,
message: 'Pin claimed'
}
},
getFileMetadata: () => {
return {
success: true,
message: 'Files metadata'
}
},
getPins: () => {
return {
success: true,
message: 'Pins'
}
},
rpcHandler: () => {
return {
success: true,
message: 'RPC handler'
}
}
}
const p2wdb = {
getStatus: async () => {},
selectProvider: async () => {},
getEntryByHash: async () => {},
writeEntry: async () => {}
}
export default { ipfs, localdb, bch, wallet, ipfsFiles, p2wdb }
+110 -2
View File
@@ -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 = {
+35
View File
@@ -65,6 +65,40 @@ class UsageUseCaseMock {
async getTopEndpoints(existingUser, newData) {
return true
}
async clearUsage() {
return true
}
async saveUsage() {
return true
}
}
class IpfsUseCaseMock {
async downloadCid() {
return {}
}
async cid2json() {
return {}
}
async getCidMetadata() {
return {}
}
async getService() {
return {}
}
async pinClaim() {
return {}
}
async getWritePrice() {
return 1.00
}
}
class UseCasesMock {
@@ -77,6 +111,7 @@ class UseCasesMock {
user = new UserUseCaseMock()
bch = new BchUseCaseMock()
usage = new UsageUseCaseMock()
ipfs = new IpfsUseCaseMock()
}
export default UseCasesMock;
+282
View File
@@ -0,0 +1,282 @@
/*
Unit tests for the ipfs-use-cases.js file.
*/
// Public npm libraries
import { assert } from 'chai'
import sinon from 'sinon'
// Local support libraries
// const testUtils = require('../../utils/test-utils')
// Unit under test (uut)
import UseCases from '../../../src/use-cases/ipfs-use-cases.js'
import adapters from '../mocks/adapters/index.js'
describe('#ipfs-use-cases', () => {
let uut
let sandbox
before(async () => {
// Delete all previous users in the database.
// await testUtils.deleteAllUsers()
})
beforeEach(() => {
sandbox = sinon.createSandbox()
uut = new UseCases({ adapters })
uut.adapters.ipfs.ipfsCoordAdapter = {
state: {
selectedIpfsFileProvider: 'provider'
}
}
})
afterEach(() => sandbox.restore())
describe('#constructor', () => {
it('should throw an error if adapters are not passed in', () => {
try {
uut = new UseCases()
assert.fail('Unexpected code path')
// This is here to prevent the linter from complaining.
assert.isOk(uut)
} catch (err) {
assert.include(
err.message,
'Instance of adapters must be passed in when instantiating IPFS Use Cases library.'
)
}
})
})
describe('#downloadCid', () => {
it('should download a file from IPFS', async () => {
sandbox.stub(uut, 'getCidMetadata').resolves('file.txt')
const result = await uut.downloadCid({ cid: 'bafybeib2rphswe7clvclverw7xi7nejkpkh4mdfcurdmcgw7wcup7za6wy' })
assert.property(result, 'filename')
assert.property(result, 'readStream')
})
it('should handle directory-based CIDs', async () => {
sandbox.stub(uut, 'getCidMetadata').resolves('file.txt')
sandbox.stub(uut.adapters.ipfs.ipfs.fs, 'ls').callsFake(async function * () {
yield { path: 'dir1/test.txt', cid: 'QmS4ghgMgfFvqPjB4WKXHaN15ZyT4K4JYZxY5X5x5x5x5' }
})
const result = await uut.downloadCid({ cid: 'bafybeib2rphswe7clvclverw7xi7nejkpkh4mdfcurdmcgw7wcup7za6wy' })
assert.property(result, 'filename')
assert.property(result, 'readStream')
})
it('should throw an error if cid is not provided ', async () => {
try {
await uut.downloadCid({})
} catch (error) {
assert.include(error.message, 'CID is undefined')
}
})
})
describe('#getWritePrice', () => {
it('should get price', async () => {
const result = await uut.getWritePrice()
assert.isNumber(result)
})
it('should handle undefined psffpp instance', async () => {
uut.psffpp = null
const result = await uut.getWritePrice()
assert.isNumber(result)
})
it('should 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')
}
})
})
describe('#getCidMetadata', () => {
it('should get cid metadata', async () => {
sandbox.stub(uut.adapters.ipfsFiles, 'getFileMetadata').resolves({ success: true, fileMetadata: { dataPinned: true, filename: 'test.json' } })
const result = await uut.getCidMetadata({ cid: 'bafybeib2rphswe7clvclverw7xi7nejkpkh4mdfcurdmcgw7wcup7za6wy' })
assert.isString(result)
assert.equal(result, 'test.json')
})
it('should handle error ', async () => {
try {
sandbox.stub(uut.adapters.ipfsFiles, 'getFileMetadata').throws(new Error('test error'))
await uut.getCidMetadata({ cid: 'bafybeib2rphswe7clvclverw7xi7nejkpkh4mdfcurdmcgw7wcup7za6wy' })
} catch (error) {
assert.include(error.message, 'test error')
}
})
it('should handle non-success response from getFileMetadata', async () => {
try {
sandbox.stub(uut.adapters.ipfsFiles, 'getFileMetadata').resolves({ success: false, message: 'test error' })
await uut.getCidMetadata({ cid: 'bafybeib2rphswe7clvclverw7xi7nejkpkh4mdfcurdmcgw7wcup7za6wy' })
} catch (error) {
assert.include(error.message, 'test error')
}
})
})
describe('#cid2json', () => {
it('should get cid2json', async () => {
uut.adapters.ipfs.ipfsCoordAdapter = {
state: {
selectedIpfsFileProvider: 'provider'
}
}
sandbox.stub(uut.adapters.ipfsFiles, 'getFileMetadata').resolves({ fileMetadata: { dataPinned: true, filename: 'test.json' } })
sandbox.stub(uut.adapters.ipfs.ipfs.fs, 'ls').callsFake(function * () {
yield { name: 'test.json' }
})
sandbox.stub(JSON, 'parse').returns(true)
const result = await uut.cid2json({ cid: 'bafybeib2rphswe7clvclverw7xi7nejkpkh4mdfcurdmcgw7wcup7za6wy' })
assert.isObject(result)
assert.property(result, 'success')
assert.property(result, 'json')
})
it('should throw error if selectedIpfsFileProvider is not defined', async () => {
try {
uut.adapters.ipfs.ipfsCoordAdapter = {
state: {
selectedIpfsFileProvider: null
}
}
await uut.cid2json({ cid: 'bafybeib2rphswe7clvclverw7xi7nejkpkh4mdfcurdmcgw7wcup7za6wy' })
assert.fail('Unexpected code path')
} catch (error) {
assert.include(error.message, 'No IPFS File Provider Service is available yet. Try again in a few seconds')
}
})
it('should throw error if file metadata cant be fetched', async () => {
try {
sandbox.stub(uut.adapters.ipfsFiles, 'getFileMetadata').resolves({ success: false })
await uut.cid2json({ cid: 'bafybeib2rphswe7clvclverw7xi7nejkpkh4mdfcurdmcgw7wcup7za6wy' })
assert.fail('Unexpected code path')
} catch (error) {
assert.include(error.message, 'Could not communicate with instance of ipfs-file-pin-service')
}
})
it('should throw error if cid is not pinned', async () => {
try {
sandbox.stub(uut.adapters.ipfsFiles, 'getFileMetadata').resolves({ success: true, fileMetadata: { dataPinned: false } })
await uut.cid2json({ cid: 'bafybeib2rphswe7clvclverw7xi7nejkpkh4mdfcurdmcgw7wcup7za6wy' })
assert.fail('Unexpected code path')
} catch (error) {
assert.include(error.message, 'has not been pinned by ipfs-file-pin-service instance')
}
})
it('should throw error if filename has no .json extension', async () => {
try {
sandbox.stub(uut.adapters.ipfsFiles, 'getFileMetadata').resolves({ success: true, fileMetadata: { dataPinned: true, filename: 'test' } })
await uut.cid2json({ cid: 'bafybeib2rphswe7clvclverw7xi7nejkpkh4mdfcurdmcgw7wcup7za6wy' })
assert.fail('Unexpected code path')
} catch (error) {
assert.include(error.message, 'does not resolve to a JSON file')
}
})
it('should handle error if file cant be retrieved', async () => {
try {
sandbox.stub(uut.adapters.ipfsFiles, 'getFileMetadata').resolves({ fileMetadata: { dataPinned: true, filename: 'test.json' } })
sandbox.stub(uut.adapters.ipfs.ipfs.fs, 'cat').throws(new Error('test error'))
sandbox.stub(uut.adapters.ipfs.ipfs.fs, 'ls').callsFake(function * () {
yield { name: 'test.json' }
})
await uut.cid2json({ cid: 'bafybeib2rphswe7clvclverw7xi7nejkpkh4mdfcurdmcgw7wcup7za6wy' })
assert.fail('Unexpected code path')
} catch (error) {
assert.include(error.message, 'test error')
}
})
it('should handle error if cid is a directory', async () => {
try {
sandbox.stub(uut.adapters.ipfsFiles, 'getFileMetadata').resolves({ fileMetadata: { dataPinned: true, filename: 'test.json' } })
sandbox.stub(uut.adapters.ipfs.ipfs.fs, 'cat')
.onCall(0).throws(new Error('test error'))
.onCall(1).callsFake(function * () {
yield { name: 'test.json' }
})
sandbox.stub(uut.adapters.ipfs.ipfs.fs, 'ls').callsFake(function * () {
yield { name: 'test/directory/' }
})
await uut.cid2json({ cid: 'bafybeib2rphswe7clvclverw7xi7nejkpkh4mdfcurdmcgw7wcup7za6wy' })
} catch (error) {
assert.include(error.message, 'file does not resolve to a valid JSON file')
}
})
it('should get content from cid/name.json format', async () => {
sandbox.stub(uut.adapters.ipfsFiles, 'getFileMetadata').resolves({ fileMetadata: { dataPinned: true, filename: 'test.json' } })
sandbox.stub(uut.adapters.ipfs.ipfs.fs, 'cat')
.onCall(0).throws(new Error('test error'))
.onCall(1).callsFake(function * () {
yield Buffer.from('{"test": "test"}')
})
sandbox.stub(uut.adapters.ipfs.ipfs.fs, 'ls').callsFake(function * () {
yield { name: 'test/directory/test.json' }
})
const result = await uut.cid2json({ cid: 'bafybeib2rphswe7clvclverw7xi7nejkpkh4mdfcurdmcgw7wcup7za6wy' })
assert.isObject(result)
assert.property(result, 'success')
assert.property(result, 'json')
})
it('should handle if content cant be parsed', async () => {
try {
sandbox.stub(uut.adapters.ipfsFiles, 'getFileMetadata').resolves({ fileMetadata: { dataPinned: true, filename: 'test.json' } })
sandbox.stub(uut.adapters.ipfs.ipfs.fs, 'ls').callsFake(function * () {
yield { name: 'test.json' }
})
sandbox.stub(JSON, 'parse').throws(new Error())
await uut.cid2json({ cid: 'bafybeib2rphswe7clvclverw7xi7nejkpkh4mdfcurdmcgw7wcup7za6wy' })
} catch (error) {
assert.include(error.message, 'not resolve to a valid JSON object')
}
})
})
describe('#pinClaim', () => {
it('should process pinClaim', async () => {
const result = await uut.pinClaim()
assert.isObject(result)
assert.property(result, 'success')
assert.property(result, 'message')
})
it('should handle error ', async () => {
try {
sandbox.stub(uut.adapters.ipfsFiles, 'pinClaim').throws(new Error('test error'))
await uut.pinClaim()
} catch (error) {
assert.include(error.message, 'test error')
}
})
})
})
+70 -3
View File
@@ -1,6 +1,5 @@
/*
Unit tests for the use-cases/usage-use-cases.js business logic library.
Unit tests for the use-cases/usage-use-cases.js business logic library.
*/
// Public npm libraries
@@ -29,6 +28,7 @@ describe('#usage-use-case', () => {
sandbox = sinon.createSandbox()
uut = new UsageUseCases({ adapters })
console.log('restCalls: ', restCalls)
// Set as empty array
restCalls.splice(0, restCalls.length)
@@ -58,7 +58,7 @@ describe('#usage-use-case', () => {
// set older mock data
restCalls.push({
timestamp: now.getTime() - (60000 * 60 * 24),
timestamp: now.getTime() - (60000 * 60 * 48), // 48 hours ago
ip: '127.0.0.1'
})
@@ -88,6 +88,7 @@ describe('#usage-use-case', () => {
}
})
})
describe('#getRestSummary', () => {
it('should get the number of rest calls', () => {
// Set mock data
@@ -137,6 +138,7 @@ describe('#usage-use-case', () => {
assert.equal(result[0].ip, 'localhost')
assert.equal(result[0].cnt, '2')
})
it('should return a maximum of 20 values', () => {
// Fill Array with 21 values
for (let i = 0; i < 21; i++) {
@@ -154,6 +156,7 @@ describe('#usage-use-case', () => {
assert.equal(result.length, 20)
})
it('should handle error', () => {
try {
// Set mock data
@@ -194,6 +197,7 @@ describe('#usage-use-case', () => {
assert.equal(result[0].endpoint, 'GET /api/v1/users')
assert.equal(result[0].cnt, '2')
})
it('should return a maximum of 20 values', () => {
// Fill Array with 21 values
for (let i = 0; i < 21; i++) {
@@ -252,4 +256,67 @@ describe('#usage-use-case', () => {
}
})
})
describe('#clearUsage', () => {
it('should clear the usage database data', async () => {
const res = await uut.clearUsage()
assert.isTrue(res)
})
it('should handle error', async () => {
try {
sandbox.stub(uut.UsageModel, 'deleteMany').throws(new Error('uut error'))
await uut.clearUsage()
assert.fail('Unexpected code path')
} catch (error) {
assert.equal(error.message, 'uut error')
}
})
})
describe('#saveUsage', () => {
it('should save usage', async () => {
// Set mock data
restCalls.push({
timestamp: new Date().getTime(),
ip: 'localhost',
url: 'fakeUrl',
method: 'unit test'
})
const res = await uut.saveUsage()
assert.isTrue(res)
})
it('should handle error', async () => {
try {
restCalls.push(null)
await uut.saveUsage()
assert.fail('Unexpected code path')
} catch (error) {
console.log(error)
assert.include(error.message, 'Cannot read properties')
}
})
})
describe('#loadUsage', () => {
it('should load usage', async () => {
// Set mock data
const mockObj = {
timestamp: new Date().getTime(),
ip: 'localhost',
url: 'fakeUrl',
method: 'unit test'
}
sandbox.stub(uut.UsageModel, 'find').returns(new Array(10).fill(null).map((_, i) => (mockObj)))
const res = await uut.loadUsage()
assert.equal(restCalls.length, 10)
assert.equal(res.length, 10)
})
it('should skip error', async () => {
sandbox.stub(uut.UsageModel, 'find').throws(new Error('uut error'))
const res = await uut.loadUsage()
assert.isFalse(res)
})
})
})
+45 -20
View File
@@ -8,7 +8,6 @@ import axios from 'axios'
// Local libraries
import config from '../../config/index.js'
import User from '../../src/adapters/localdb/models/users.js'
import JsonFiles from '../../src/adapters/json-files.js'
// Hack to get __dirname back.
@@ -33,24 +32,6 @@ async function cleanDb () {
}
}
// Delete all users in the database. This ensures there is no previous state
// to confuse tests.
async function deleteAllUsers () {
try {
// Get all the users in the DB.
const users = await User.find({}, '-password')
// console.log(`users: ${JSON.stringify(users, null, 2)}`)
// Delete each user.
for (let i = 0; i < users.length; i++) {
const thisUser = users[i]
await thisUser.remove()
}
} catch (err) {
console.error('Error in test-utils.js/deleteAllUsers()')
}
}
// This function is used to create new users.
// userObj = {
// username,
@@ -170,12 +151,56 @@ async function getAdminJWT () {
throw err
}
}
// Fetches all users from the database.
async function getAllUsers () {
try {
const adminJWT = await getAdminJWT()
const options = {
method: 'GET',
url: `${LOCALHOST}/users`,
headers: {
Authorization: `Bearer ${adminJWT}`
}
}
const result = await axios(options)
return result.data.users
} catch (err) {
console.error('Error in test/utils.js/getAllUsers()', err)
throw err
}
}
// Deletes all users from the database.
async function deleteAllUsers () {
try {
const allUsers = await getAllUsers()
const adminJWT = await getAdminJWT()
for (let i = 0; i < allUsers.length; i++) {
const user = allUsers[i]
// Skip the admin user.
if (user.type === 'admin') {
continue
}
const options = {
method: 'DELETE',
url: `${LOCALHOST}/users/${user._id}`,
headers: {
Authorization: `Bearer ${adminJWT}`
}
}
await axios(options)
}
} catch (err) {
console.error('Error in test/utils.js/deleteAllUsers()', err)
throw err
}
}
export default {
cleanDb,
createUser,
loginTestUser,
loginAdminUser,
getAdminJWT,
deleteAllUsers
deleteAllUsers,
getAllUsers
}
+3 -4
View File
@@ -1,8 +1,9 @@
import mongoose from 'mongoose'
import config from '../../config/index.js'
import User from '../../src/adapters/localdb/models/users.js'
const EMAIL = 'test@test.com'
const PASSWORD = 'pass'
const EMAIL = process.env.EMAIL || 'test@test3.com'
const PASSWORD = process.env.PASSWORD || 'pass'
async function addUser () {
// Connect to the Mongo Database.
@@ -13,8 +14,6 @@ async function addUser () {
{ useNewUrlParser: true, useUnifiedTopology: true }
)
const User = require('../../src/models/users')
const userData = {
email: EMAIL,
password: PASSWORD