Merge pull request #95 from Permissionless-Software-Foundation/ct-unstable

Syncing with upstream ipfs-service-provider
This commit is contained in:
Chris Troutner
2026-02-03 13:23:25 -07:00
committed by GitHub
38 changed files with 4855 additions and 5813 deletions
+7 -1
View File
@@ -153,7 +153,13 @@ export default {
// v2 Circuit Relay server (FullStack.cash) // v2 Circuit Relay server (FullStack.cash)
// '/ip4/78.46.129.7/tcp/4001/p2p/12D3KooWFQ11GQ5NubsJGhYZ4X3wrAGimLevxfm6HPExCrMYhpSL' // '/ip4/78.46.129.7/tcp/4001/p2p/12D3KooWFQ11GQ5NubsJGhYZ4X3wrAGimLevxfm6HPExCrMYhpSL'
] ],
// END IPFS CONFIGURATION // END IPFS CONFIGURATION
// Account Configuration
disableNewAccounts: process.env.DISABLE_NEW_ACCOUNTS ? true : false,
// Admin password
adminPassword: process.env.ADMIN_PASSWORD
} }
+4076 -5585
View File
File diff suppressed because it is too large Load Diff
+19 -7
View File
@@ -6,11 +6,11 @@
"type": "module", "type": "module",
"scripts": { "scripts": {
"start": "node index.js", "start": "node index.js",
"test": "npm run test:all", "test": "npm run test:unit",
"test:all": "export SVC_ENV=test && c8 --reporter=text mocha --exit --timeout 15000 --recursive test/unit test/e2e/automated/", "test:all": "export SVC_ENV=test && c8 --reporter=text mocha --exit --timeout 15000 --recursive test/unit test/e2e/automated/",
"test:unit": "export SVC_ENV=test && mocha --exit --timeout 15000 --recursive test/unit/", "test:unit": "export SVC_ENV=test && c8 --reporter=text mocha --exit --timeout 15000 --recursive test/unit/",
"test:e2e:auto": "export SVC_ENV=test && mocha --exit --timeout 30000 test/e2e/automated/", "test:e2e:auto": "export SVC_ENV=test && mocha --exit --timeout 15000 test/e2e/automated/",
"test:integration": "export SVC_ENV=test && mocha --timeout 30000 --recursive test/integration/", "start:e2e:server": "export SVC_ENV=test && node index.js",
"test:temp": "export SVC_ENV=test && mocha --exit --timeout 15000 -g '#rate-limit' test/unit/json-rpc/", "test:temp": "export SVC_ENV=test && mocha --exit --timeout 15000 -g '#rate-limit' test/unit/json-rpc/",
"lint": "standard --env mocha --fix", "lint": "standard --env mocha --fix",
"docs": "./node_modules/.bin/apidoc -i src/ -o docs", "docs": "./node_modules/.bin/apidoc -i src/ -o docs",
@@ -39,14 +39,14 @@
"@libp2p/tcp": "10.1.2", "@libp2p/tcp": "10.1.2",
"@libp2p/webrtc": "5.2.2", "@libp2p/webrtc": "5.2.2",
"@libp2p/websockets": "9.2.2", "@libp2p/websockets": "9.2.2",
"@multiformats/multiaddr": "12.3.5", "@multiformats/multiaddr": "12.5.1",
"axios": "0.27.2", "axios": "0.27.2",
"bcryptjs": "2.4.3", "bcryptjs": "2.4.3",
"blockstore-fs": "2.0.2", "blockstore-fs": "2.0.2",
"datastore-fs": "10.0.2", "datastore-fs": "10.0.2",
"glob": "7.1.6", "glob": "7.1.6",
"helia": "5.2.1", "helia": "5.2.1",
"helia-coord": "1.7.2", "helia-coord": "1.9.7",
"jsonrpc-lite": "2.2.0", "jsonrpc-lite": "2.2.0",
"jsonwebtoken": "8.5.1", "jsonwebtoken": "8.5.1",
"jwt-bch-lib": "1.3.0", "jwt-bch-lib": "1.3.0",
@@ -63,7 +63,7 @@
"koa2-ratelimit": "0.9.1", "koa2-ratelimit": "0.9.1",
"libp2p": "2.7.2", "libp2p": "2.7.2",
"line-reader": "0.4.0", "line-reader": "0.4.0",
"minimal-slp-wallet": "5.13.2", "minimal-slp-wallet": "7.1.4",
"mongoose": "5.13.14", "mongoose": "5.13.14",
"node-fetch": "npm:@achingbrain/node-fetch@2.6.7", "node-fetch": "npm:@achingbrain/node-fetch@2.6.7",
"nodemailer": "6.7.5", "nodemailer": "6.7.5",
@@ -102,5 +102,17 @@
"ignore": [ "ignore": [
"/test/unit/mocks/**/*.js" "/test/unit/mocks/**/*.js"
] ]
},
"overrides": {
"@multiformats/multiaddr-to-uri": "10.1.2",
"@multiformats/multiaddr-matcher": "1.6.0",
"@multiformats/multiaddr": "12.5.1",
"helia": {
"libp2p": "2.7.2",
"@libp2p/upnp-nat": "3.0.0"
},
"libp2p": {
"@multiformats/multiaddr": "12.5.1"
}
} }
} }
+26 -58
View File
@@ -51,70 +51,37 @@ class Admin {
// used by the Listing Manager and test scripts, in order access private API // used by the Listing Manager and test scripts, in order access private API
// functions. // functions.
async createSystemUser () { async createSystemUser () {
// Create the system user.
try { try {
context.password = _this._randomString(20) const context = {
email: 'system@system.com',
const options = { name: 'admin',
method: 'POST', password: _this.config.adminPassword || _this._randomString(20),
url: `${LOCALHOST}/users`, type: 'admin'
data: {
user: {
email: 'system@system.com',
password: context.password,
name: 'admin'
}
}
} }
const result = await _this.axios.request(options) // Check if the user already exists
// console.log('admin.data: ', result.data) let adminUser = await _this.User.findOne({ email: context.email })
context.email = result.data.user.email if (adminUser) {
context.id = result.data.user._id // Update the password
context.token = result.data.token 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 // Save the user
const user = await _this.User.findById(context.id) await adminUser.save()
// Change the user type to admin // Write the user data to the JSON file
user.type = 'admin' await _this.jsonFiles.writeJSON(context, JSON_PATH)
// 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)
return context return context
} catch (err) { } catch (error) {
// Handle existing system user. console.log('Error in admin.js/createSystemUser()')
if (err.response.status === 422) { throw error
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
}
} }
} }
@@ -129,6 +96,7 @@ class Admin {
}) })
await _this.User.deleteOne({ email: 'system@system.com' }) await _this.User.deleteOne({ email: 'system@system.com' })
return true
} catch (err) { } catch (err) {
console.log('Error in admin.js/deleteExistingSystemUser()') console.log('Error in admin.js/deleteExistingSystemUser()')
throw err throw err
@@ -152,7 +120,7 @@ class Admin {
Accept: 'application/json' Accept: 'application/json'
}, },
data: { data: {
email: 'system@system.com', email: existingUser.email,
password: existingUser.password password: existingUser.password
} }
} }
+2
View File
@@ -4,11 +4,13 @@
// Load Mongoose models. // Load Mongoose models.
import Users from './models/users.js' import Users from './models/users.js'
import Usage from './models/usage.js'
class LocalDB { class LocalDB {
constructor () { constructor () {
// Encapsulate dependencies // Encapsulate dependencies
this.Users = Users 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
@@ -20,6 +20,13 @@ class Wallet {
// Bind 'this' object to all subfunctions // Bind 'this' object to all subfunctions
this.instanceWalletWithoutInitialization = this.instanceWalletWithoutInitialization.bind(this) 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 // This is used for initializing the wallet, without waiting to update the wallet
+56 -1
View File
@@ -58,7 +58,26 @@ class IpfsRESTControllerLib {
} }
} }
// Return information on IPFS peers this node is connected to. /**
* @api {post} /ipfs/peers Get information on IPFS peers this node is connected to
* @apiPermission public
* @apiName GetIpfsPeers
* @apiGroup REST IPFS
*
* @apiParam {Boolean} [showAll=false] Whether to include detailed peer data
*
* @apiExample Example usage:
* curl -H "Content-Type: application/json" -X POST localhost:5001/ipfs/peers \
* -d '{"showAll": false}'
*
* @apiSuccess {Object[]} peers Array of peer objects
* @apiSuccess {String} peers[].peer Peer ID
* @apiSuccess {String} peers[].name Peer name
* @apiSuccess {String} peers[].protocol Protocol used by the peer
* @apiSuccess {String} peers[].version Peer version
* @apiSuccess {String} peers[].connectionAddr Connection address
* @apiSuccess {Object} [peers[].peerData] Detailed peer data (when showAll=true)
*/
async getPeers (ctx) { async getPeers (ctx) {
try { try {
const showAll = ctx.request.body.showAll const showAll = ctx.request.body.showAll
@@ -73,6 +92,24 @@ class IpfsRESTControllerLib {
} }
} }
/**
* @api {post} /ipfs/relays Get data about the known Circuit Relays
* @apiPermission public
* @apiName GetIpfsRelays
* @apiGroup REST IPFS
*
* @apiDescription Returns information about Circuit Relays, both v1 and v2, that this node knows about. V2 relays are hydrated with peer data from the connected peers list.
*
* @apiExample Example usage:
* curl -H "Content-Type: application/json" -X POST localhost:5001/ipfs/relays
*
* @apiSuccess {Object} relays Object containing relay information
* @apiSuccess {Object[]} relays.v2Relays Array of v2 Circuit Relay objects
* @apiSuccess {String} relays.v2Relays[].ipfsId IPFS ID of the relay
* @apiSuccess {String} relays.v2Relays[].name Name of the relay (hydrated from peer data)
* @apiSuccess {String} relays.v2Relays[].description Description of the relay (hydrated from peer data)
* @apiSuccess {Object[]} relays.v1Relays Array of v1 Circuit Relay configurations
*/
// Get data about the known Circuit Relays. Hydrate with data from peers list. // Get data about the known Circuit Relays. Hydrate with data from peers list.
async getRelays (ctx) { async getRelays (ctx) {
try { try {
@@ -86,6 +123,24 @@ class IpfsRESTControllerLib {
} }
} }
/**
* @api {post} /ipfs/connect Connect to a specific IPFS peer
* @apiPermission public
* @apiName ConnectToIpfsPeer
* @apiGroup REST IPFS
*
* @apiDescription Attempts to establish a connection to a specific IPFS peer using the provided multiaddr. Optionally returns detailed information about the connection.
*
* @apiParam {String} multiaddr Multiaddress of the peer to connect to (required)
* @apiParam {Boolean} [getDetails=false] Whether to return detailed connection information
*
* @apiExample Example usage:
* curl -H "Content-Type: application/json" -X POST localhost:5001/ipfs/connect \
* -d '{"multiaddr": "/ip4/161.35.99.207/tcp/4001/p2p/12D3KooWDtj9cfj1SKuLbDNKvKRKSsGN8qivq9M8CYpLPDpcD5pu", "getDetails": false}'
*
* @apiSuccess {Boolean} success Indicates whether the connection attempt was successful
* @apiSuccess {Object} [details] Additional connection details (when getDetails=true)
*/
async connect (ctx) { async connect (ctx) {
try { try {
const multiaddr = ctx.request.body.multiaddr const multiaddr = ctx.request.body.multiaddr
+12 -1
View File
@@ -10,6 +10,8 @@ import UserRESTControllerLib from './controller.js'
import Validators from '../middleware/validators.js' import Validators from '../middleware/validators.js'
import config from '../../../../config/index.js'
let _this let _this
class UserRouter { class UserRouter {
@@ -34,6 +36,7 @@ class UserRouter {
} }
// Encapsulate dependencies. // Encapsulate dependencies.
this.config = config
this.userRESTController = new UserRESTControllerLib(dependencies) this.userRESTController = new UserRESTControllerLib(dependencies)
this.validators = new Validators() this.validators = new Validators()
@@ -52,7 +55,7 @@ class UserRouter {
} }
// Define the routes and attach the controller. // 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('/', this.getAll)
this.router.get('/:id', this.getById) this.router.get('/:id', this.getById)
this.router.put('/:id', this.updateUser) this.router.put('/:id', this.updateUser)
@@ -63,6 +66,14 @@ class UserRouter {
app.use(this.router.allowedMethods()) 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) { async getAll (ctx, next) {
await _this.validators.ensureUser(ctx, next) await _this.validators.ensureUser(ctx, next)
await _this.userRESTController.getUsers(ctx, next) await _this.userRESTController.getUsers(ctx, next)
+38 -17
View File
@@ -1,6 +1,6 @@
/* /*
This Controller library is concerned with timer-based functions that are This Controller library is concerned with timer-based functions that are
kicked off periodicially. kicked off periodically.
*/ */
import config from '../../config/index.js' import config from '../../config/index.js'
@@ -23,55 +23,76 @@ class TimerControllers {
this.debugLevel = localConfig.debugLevel this.debugLevel = localConfig.debugLevel
// Constants
this.cleanUsageInterval = 60000 * 60 // 1 hour
this.backupUsageInterval = 60000 * 10 // 10 minutes
// Encapsulate dependencies // Encapsulate dependencies
this.config = config this.config = config
// Bind 'this' object to all subfunctions. // Bind 'this' object to all subfunctions.
this.exampleTimerFunc = this.exampleTimerFunc.bind(this)
this.cleanUsage = this.cleanUsage.bind(this) this.cleanUsage = this.cleanUsage.bind(this)
this.backupUsage = this.backupUsage.bind(this)
// this.startTimers()
} }
// Start all the time-based controllers. // Start all the time-based controllers.
startTimers () { startTimers () {
// Any new timer control functions can be added here. They will be started // Any new timer control functions can be added here. They will be started
// when the server starts. // when the server starts.
this.optimizeWalletHandle = setInterval(this.exampleTimerFunc, 60000 * 60) this.cleanUsageHandle = setInterval(this.cleanUsage, this.cleanUsageInterval)
this.cleanUsageHandle = setInterval(this.cleanUsage, 60000 * 60) // 1 hour this.backupUsageHandle = setInterval(this.backupUsage, this.backupUsageInterval)
return true return true
} }
stopTimers () { stopTimers () {
clearInterval(this.optimizeWalletHandle) clearInterval(this.cleanUsageHandle)
clearInterval(this.cleanusageHandle) clearInterval(this.backupUsageHandle)
} }
// Replace this example function with your own timer handler. // Clean the usage state so that stats reflect the last 24 hours.
exampleTimerFunc (negativeTest) { cleanUsage () {
try { try {
console.log('Example timer controller executed.') clearInterval(this.cleanUsageHandle)
if (negativeTest) throw new Error('test error') 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 return true
} catch (err) { } catch (err) {
console.error('Error in exampleTimerFunc(): ', 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. // Note: Do not throw an error. This is a top-level function.
return false return false
} }
} }
// Clean the usage state so that stats reflect the last 24 hours. // Backup the usage stats to the database
cleanUsage () { async backupUsage () {
try { try {
this.useCases.usage.cleanUsage() 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 return true
} catch (err) { } catch (err) {
console.error('Error in time-controller.js/cleanUsage(): ', 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. // Note: Do not throw an error. This is a top-level function.
return false return false
+3
View File
@@ -26,6 +26,9 @@ class UseCases {
// Run any startup Use Cases at the start of the app. // Run any startup Use Cases at the start of the app.
async start () { async start () {
// Load the usage stats from the database
await this.usage.loadUsage()
console.log('Async Use Cases have been started.') console.log('Async Use Cases have been started.')
return true return true
+78
View File
@@ -3,6 +3,10 @@
for tracking the usage of REST API and JSON RPC calls. This library is used 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 by admins to keep an eye on how many API calls were made in a 24-hour and
1-hour time period. 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 // 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 // Bind 'this' object to all subfunctions
this.cleanUsage = this.cleanUsage.bind(this) this.cleanUsage = this.cleanUsage.bind(this)
this.getRestSummary = this.getRestSummary.bind(this) this.getRestSummary = this.getRestSummary.bind(this)
this.getTopIps = this.getTopIps.bind(this) this.getTopIps = this.getTopIps.bind(this)
this.getTopEndpoints = this.getTopEndpoints.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 // State
} }
@@ -36,7 +46,11 @@ class UsageUseCases {
const now = new Date() const now = new Date()
const twentyFourHoursAgo = now.getTime() - (60000 * 60 * 24) 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) restCalls = restCalls.filter(x => x.timestamp > twentyFourHoursAgo)
console.log('cleanUsage() restCalls.length after filtering: ', restCalls.length)
return restCalls return restCalls
} catch (err) { } catch (err) {
console.error('Error in usage-use-cases.js/cleanUsage()') console.error('Error in usage-use-cases.js/cleanUsage()')
@@ -104,6 +118,70 @@ class UsageUseCases {
throw err 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]
// Debugging: delete this code after debugging
// if (i === 5) {
// console.log('saveUsage() thisRestCall: ', thisRestCall)
// }
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)
// Debugging: delete this code after debugging
if (usage[5]) {
console.log('loadUsage() usage[5]: ', usage[5])
}
restCalls = usage
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 // 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 // Local support libraries
import config from '../../../config/index.js' 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 testUtils from '../../utils/test-utils.js'
import AdminLib from '../../../src/adapters/admin.js'
const adminLib = new AdminLib()
// const request = supertest.agent(app.listen()) // const request = supertest.agent(app.listen())
const context = {} const context = {}
@@ -26,10 +24,12 @@ const LOCALHOST = `http://localhost:${config.port}`
if (!config.noMongo) { if (!config.noMongo) {
describe('Auth', () => { describe('Auth', () => {
before(async () => { before(async () => {
const app = new Server() // const app = new Server()
// This should be the first instruction. It starts the REST API 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. // Stop the IPFS node for the rest of the e2e tests.
// await app.controllers.adapters.ipfs.stop() // await app.controllers.adapters.ipfs.stop()
@@ -37,9 +37,6 @@ if (!config.noMongo) {
// Delete all previous users in the database. // Delete all previous users in the database.
await testUtils.deleteAllUsers() await testUtils.deleteAllUsers()
// Create a new admin user.
await adminLib.createSystemUser()
const userObj = { const userObj = {
email: 'test@test.com', email: 'test@test.com',
password: 'pass', password: 'pass',
-35
View File
@@ -5,16 +5,11 @@ import axios from 'axios'
import sinon from 'sinon' import sinon from 'sinon'
import util from 'util' 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'
util.inspect.defaultOptions = { depth: 1 } util.inspect.defaultOptions = { depth: 1 }
const LOCALHOST = `http://localhost:${config.port}` const LOCALHOST = `http://localhost:${config.port}`
const context = {} const context = {}
const adapters = new Adapters()
let uut
let sandbox let sandbox
// const mockContext = require('../../unit/mocks/ctx-mock').context // const mockContext = require('../../unit/mocks/ctx-mock').context
@@ -50,9 +45,6 @@ if (!config.noMongo) {
}) })
beforeEach(() => { beforeEach(() => {
const useCases = new UseCases({ adapters })
uut = new UserController({ adapters, useCases })
sandbox = sinon.createSandbox() sandbox = sinon.createSandbox()
}) })
@@ -268,33 +260,6 @@ if (!config.noMongo) {
assert.hasAnyKeys(users[0], ['type', '_id', 'email']) assert.hasAnyKeys(users[0], ['type', '_id', 'email'])
assert.isNumber(users.length) 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', () => { describe('GET /users/:id', () => {
+19 -3
View File
@@ -14,13 +14,14 @@ describe('#adapters', () => {
let uut, sandbox let uut, sandbox
beforeEach(() => { beforeEach(() => {
uut = new Adapters()
sandbox = sinon.createSandbox() sandbox = sinon.createSandbox()
uut = new Adapters()
}) })
afterEach(() => { afterEach(() => {
sandbox.restore() if (sandbox) {
sandbox.restore()
}
}) })
describe('#start', () => { describe('#start', () => {
@@ -37,6 +38,21 @@ describe('#adapters', () => {
assert.equal(result, true) 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 () => { it('should catch and throw an error', async () => {
try { try {
@@ -19,26 +19,13 @@ describe('Admin', () => {
if (!config.noMongo) { if (!config.noMongo) {
describe('loginAdmin()', () => { describe('loginAdmin()', () => {
it('should logind admin', async () => { it('should login admin', async () => {
try { try {
const error = new Error('test error') sandbox.stub(uut.jsonFiles, 'readJSON').resolves({ password: 'pass' })
error.response = { sandbox.stub(uut.axios, 'request').resolves(true)
status: 422
}
// sandbox.stub(uut.axios, 'request').onFirstCall().throws(error)
const result = await uut.loginAdmin() const result = await uut.loginAdmin()
const user = result.data.user assert.isTrue(result)
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')
} catch (err) { } catch (err) {
assert(false, 'Unexpected result') assert(false, 'Unexpected result')
} }
@@ -48,13 +35,13 @@ describe('Admin', () => {
try { try {
// Returns an erroneous password to force // Returns an erroneous password to force
// an auth error // an auth error
sandbox.stub(uut.axios, 'request').throws(new Error('test error'))
sandbox.stub(uut.jsonFiles, 'readJSON').resolves({ password: 'wrong' }) sandbox.stub(uut.jsonFiles, 'readJSON').resolves({ password: 'wrong' })
await uut.loginAdmin() await uut.loginAdmin()
assert(false, 'Unexpected result') assert(false, 'Unexpected result')
} catch (err) { } catch (err) {
assert.equal(err.response.status, 401) assert.include(err.message, 'test error')
assert.include(err.response.data, 'Unauthorized')
} }
}) })
}) })
@@ -62,6 +49,7 @@ describe('Admin', () => {
describe('createSystemUser()', () => { describe('createSystemUser()', () => {
it('should create admin', async () => { it('should create admin', async () => {
try { try {
await uut.deleteExistingSystemUser()
const result = await uut.createSystemUser() const result = await uut.createSystemUser()
assert.property(result, 'email') assert.property(result, 'email')
@@ -72,44 +60,59 @@ describe('Admin', () => {
assert(false, 'Unexpected result') assert(false, 'Unexpected result')
} }
}) })
it('should update admin password', async () => {
it('should handle axios error', async () => {
try { try {
const error1 = new Error('test error') uut.config.adminPassword = 'newpassword'
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)
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') 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) { } catch (err) {
assert.include(err.message, 'test error') assert.include(err.message, 'test error')
} }
}) })
})
it('should handle errors when remove user', async () => { describe('deleteExistingSystemUser()', () => {
it('should delete admin', async () => {
try { try {
const error1 = new Error('test error') sandbox.stub(uut.User, 'deleteOne').resolves(true)
error1.response = { const result = await uut.deleteExistingSystemUser()
status: 422 assert.isTrue(result)
} } catch (err) {
sandbox.stub(uut.axios, 'request').throws(error1)
sandbox.stub(uut.User, 'deleteOne').throws(new Error('test error'))
await uut.createSystemUser()
assert(false, 'Unexpected result') 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) { } catch (err) {
assert.include(err.message, 'test error') assert.include(err.message, 'test error')
} }
@@ -16,7 +16,7 @@ describe('#IPFS-adapter-index', () => {
let bchjs let bchjs
beforeEach(() => { beforeEach(() => {
bchjs = new BCHJS() bchjs = new BCHJS({ restURL: 'https://api.fullstack.cash/v5/' })
uut = new IPFSLib({ bchjs }) uut = new IPFSLib({ bchjs })
sandbox = sinon.createSandbox() sandbox = sinon.createSandbox()
@@ -41,5 +41,17 @@ describe('#passport.js', () => {
assert.include(err.message, 'cant auth user') 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') 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', () => { describe('#validatePassword', () => {
+5 -5
View File
@@ -107,7 +107,7 @@ describe('#wallet', () => {
describe('#instanceWalletWithoutInitialization', () => { describe('#instanceWalletWithoutInitialization', () => {
it('should create an instance of BchWallet', async () => { it('should create an instance of BchWallet', async () => {
// Create a mock wallet. // Create a mock wallet.
const mockWallet = new BchWallet() const mockWallet = new BchWallet(undefined, { restURL: 'https://api.fullstack.cash/v5/' })
await mockWallet.walletInfoPromise await mockWallet.walletInfoPromise
sandbox.stub(mockWallet, 'initialize').resolves() sandbox.stub(mockWallet, 'initialize').resolves()
@@ -143,7 +143,7 @@ describe('#wallet', () => {
it('should create an instance of BchWallet using web2 infra', async () => { it('should create an instance of BchWallet using web2 infra', async () => {
// Create a mock wallet. // Create a mock wallet.
const mockWallet = new BchWallet() const mockWallet = new BchWallet(undefined, { restURL: 'https://api.fullstack.cash/v5/' })
await mockWallet.walletInfoPromise await mockWallet.walletInfoPromise
sandbox.stub(mockWallet, 'initialize').resolves() sandbox.stub(mockWallet, 'initialize').resolves()
@@ -166,7 +166,7 @@ describe('#wallet', () => {
it('should generate wallet from mnemonic in config', async () => { it('should generate wallet from mnemonic in config', async () => {
// Create a mock wallet. // Create a mock wallet.
const mockWallet = new BchWallet() const mockWallet = new BchWallet(undefined, { restURL: 'https://api.fullstack.cash/v5/' })
await mockWallet.walletInfoPromise await mockWallet.walletInfoPromise
sandbox.stub(mockWallet, 'initialize').resolves() sandbox.stub(mockWallet, 'initialize').resolves()
@@ -194,7 +194,7 @@ describe('#wallet', () => {
describe('#instanceWallet', () => { describe('#instanceWallet', () => {
it('should create an instance of BchWallet', async () => { it('should create an instance of BchWallet', async () => {
// Create a mock wallet. // Create a mock wallet.
const mockWallet = new BchWallet() const mockWallet = new BchWallet(undefined, { restURL: 'https://api.fullstack.cash/v5/' })
await mockWallet.walletInfoPromise await mockWallet.walletInfoPromise
sandbox.stub(mockWallet, 'initialize').resolves() sandbox.stub(mockWallet, 'initialize').resolves()
@@ -227,7 +227,7 @@ describe('#wallet', () => {
it('should create an instance of BchWallet using web2 infra', async () => { it('should create an instance of BchWallet using web2 infra', async () => {
// Create a mock wallet. // Create a mock wallet.
const mockWallet = new BchWallet() const mockWallet = new BchWallet(undefined, { restURL: 'https://api.fullstack.cash/v5/' })
await mockWallet.walletInfoPromise await mockWallet.walletInfoPromise
sandbox.stub(mockWallet, 'initialize').resolves() sandbox.stub(mockWallet, 'initialize').resolves()
+21
View File
@@ -63,4 +63,25 @@ describe('#Controllers', () => {
} }
}) })
}) })
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()
})
})
}) })
@@ -79,11 +79,22 @@ describe('#Auth-REST-Router', () => {
await uut.authUser(ctx) await uut.authUser(ctx)
}) })
it('should catch and throw an error', async () => { it('should catch and throw passport error', async () => {
try { try {
// Force an error // Force an error
sandbox.stub(uut.passport, 'authUser').rejects('test 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) await uut.authUser(ctx)
} catch (err) { } catch (err) {
// console.log('err: ', err) // console.log('err: ', err)
@@ -20,7 +20,7 @@ describe('#RESTControllers', () => {
let sandbox let sandbox
// let ctx // let ctx
before(async () => {}) before(async () => { })
beforeEach(() => { beforeEach(() => {
const useCases = new UseCasesMock() 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 that expected properties exist in the returned data.
assert.property(ctx.response.body, 'user') 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 () => { it('should return other error status passed by biz logic', async () => {
try { 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', () => { // describe('#exampleTimerFunc', () => {
it('should kick off the Use Case', async () => { // it('should kick off the Use Case', async () => {
const result = await uut.exampleTimerFunc() // const result = await uut.exampleTimerFunc()
assert.equal(result, true) // assert.equal(result, true)
}) // })
it('should return false on error', async () => { // it('should return false on error', async () => {
const result = await uut.exampleTimerFunc(true) // const result = await uut.exampleTimerFunc(true)
assert.equal(result, false) // assert.equal(result, false)
}) // })
}) // })
describe('#cleanUsage', () => { describe('#cleanUsage', () => {
it('should kick off the Use Case', async () => { it('should kick off the Use Case', async () => {
@@ -94,4 +94,21 @@ describe('#Timer-Controllers', () => {
assert.equal(result, false) 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)
})
})
}) })
+10 -2
View File
@@ -10,15 +10,23 @@ describe('#config', () => {
before(() => { before(() => {
// Backup the current environment setting. // Backup the current environment setting.
currentEnv = process.env.SVC_ENV currentEnv = process.env.SVC_ENV
// Clear SVC_ENV for the first test to ensure default behavior
delete process.env.SVC_ENV
}) })
after(() => { after(() => {
// Restore the environment setting before starting these tests. // Restore the environment setting before starting these tests.
process.env.SVC_ENV = currentEnv if (currentEnv) {
process.env.SVC_ENV = currentEnv
} else {
delete process.env.SVC_ENV
}
}) })
it('Should return development environment config by default', async () => { it('Should return development environment config by default', async () => {
const importedConfig = await import('../../../config/index.js') // Ensure SVC_ENV is not set for this test
delete process.env.SVC_ENV
const importedConfig = await import('../../../config/index.js?foo=bar0')
const config = importedConfig.default const config = importedConfig.default
// console.log('config: ', config) // console.log('config: ', config)
+15
View File
@@ -34,6 +34,12 @@ describe('#passport', () => {
passportCallback(id, 'password', done) 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', () => { it('should return if password is validated', () => {
// Mock Users model. // Mock Users model.
@@ -41,6 +47,15 @@ describe('#passport', () => {
passportCallback(id, 'password', done) 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', () => { it('should catch a high-level error', () => {
// Force an error // Force an error
+1 -1
View File
@@ -30,7 +30,7 @@ describe('#server', () => {
sandbox.stub(uut.adminLib, 'createSystemUser').resolves(true) sandbox.stub(uut.adminLib, 'createSystemUser').resolves(true)
sandbox.stub(uut.controllers, 'attachControllers').resolves() sandbox.stub(uut.controllers, 'attachControllers').resolves()
uut.config.env = 'dev' uut.config.env = 'dev'
uut.config.port = 5040
const result = await uut.startServer() const result = await uut.startServer()
// console.log('result: ', result) // console.log('result: ', result)
+33
View File
@@ -70,6 +70,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: () => { validatePassword: () => {
return true return true
} }
+1 -1
View File
@@ -17,7 +17,7 @@ class MockBchWallet {
this.walletInfoPromise = true; this.walletInfoPromise = true;
this.walletInfo = mockWallet; this.walletInfo = mockWallet;
this.initialize = async () => {} this.initialize = async () => {}
this.bchjs = new BCHJS(); this.bchjs = new BCHJS({ restURL: 'https://api.fullstack.cash/v5/' });
this.burnTokens = async () => { this.burnTokens = async () => {
return { success: true, txid: 'txid' }; return { success: true, txid: 'txid' };
}; };
+8
View File
@@ -63,6 +63,14 @@ class UsageUseCaseMock {
async getTopEndpoints(existingUser, newData) { async getTopEndpoints(existingUser, newData) {
return true return true
} }
async clearUsage() {
return true
}
async saveUsage() {
return true
}
} }
class UseCasesMock { class UseCasesMock {
+69 -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 // Public npm libraries
@@ -58,7 +57,7 @@ describe('#usage-use-case', () => {
// set older mock data // set older mock data
restCalls.push({ restCalls.push({
timestamp: now.getTime() - (60000 * 60 * 24), timestamp: now.getTime() - (60000 * 60 * 48), // 48 hours ago
ip: '127.0.0.1' ip: '127.0.0.1'
}) })
@@ -88,6 +87,7 @@ describe('#usage-use-case', () => {
} }
}) })
}) })
describe('#getRestSummary', () => { describe('#getRestSummary', () => {
it('should get the number of rest calls', () => { it('should get the number of rest calls', () => {
// Set mock data // Set mock data
@@ -137,6 +137,7 @@ describe('#usage-use-case', () => {
assert.equal(result[0].ip, 'localhost') assert.equal(result[0].ip, 'localhost')
assert.equal(result[0].cnt, '2') assert.equal(result[0].cnt, '2')
}) })
it('should return a maximum of 20 values', () => { it('should return a maximum of 20 values', () => {
// Fill Array with 21 values // Fill Array with 21 values
for (let i = 0; i < 21; i++) { for (let i = 0; i < 21; i++) {
@@ -154,6 +155,7 @@ describe('#usage-use-case', () => {
assert.equal(result.length, 20) assert.equal(result.length, 20)
}) })
it('should handle error', () => { it('should handle error', () => {
try { try {
// Set mock data // Set mock data
@@ -194,6 +196,7 @@ describe('#usage-use-case', () => {
assert.equal(result[0].endpoint, 'GET /api/v1/users') assert.equal(result[0].endpoint, 'GET /api/v1/users')
assert.equal(result[0].cnt, '2') assert.equal(result[0].cnt, '2')
}) })
it('should return a maximum of 20 values', () => { it('should return a maximum of 20 values', () => {
// Fill Array with 21 values // Fill Array with 21 values
for (let i = 0; i < 21; i++) { for (let i = 0; i < 21; i++) {
@@ -252,4 +255,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 // Local libraries
import config from '../../config/index.js' import config from '../../config/index.js'
import User from '../../src/adapters/localdb/models/users.js'
import JsonFiles from '../../src/adapters/json-files.js' import JsonFiles from '../../src/adapters/json-files.js'
// Hack to get __dirname back. // 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. // This function is used to create new users.
// userObj = { // userObj = {
// username, // username,
@@ -170,12 +151,56 @@ async function getAdminJWT () {
throw err 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 { export default {
cleanDb, cleanDb,
createUser, createUser,
loginTestUser, loginTestUser,
loginAdminUser, loginAdminUser,
getAdminJWT, getAdminJWT,
deleteAllUsers deleteAllUsers,
getAllUsers
} }
+3 -4
View File
@@ -1,8 +1,9 @@
import mongoose from 'mongoose' import mongoose from 'mongoose'
import config from '../../config/index.js' import config from '../../config/index.js'
import User from '../../src/adapters/localdb/models/users.js'
const EMAIL = 'test@test.com' const EMAIL = process.env.EMAIL || 'test@test3.com'
const PASSWORD = 'pass' const PASSWORD = process.env.PASSWORD || 'pass'
async function addUser () { async function addUser () {
// Connect to the Mongo Database. // Connect to the Mongo Database.
@@ -13,8 +14,6 @@ async function addUser () {
{ useNewUrlParser: true, useUnifiedTopology: true } { useNewUrlParser: true, useUnifiedTopology: true }
) )
const User = require('../../src/models/users')
const userData = { const userData = {
email: EMAIL, email: EMAIL,
password: PASSWORD password: PASSWORD
+1 -1
View File
@@ -1,6 +1,6 @@
import mongoose from 'mongoose' import mongoose from 'mongoose'
import config from '../../config/index.js' import config from '../../config/index.js'
import User from '../../src/models/users.js' import User from '../../src/adapters/localdb/models/users.js'
async function getUsers () { async function getUsers () {
// Connect to the Mongo Database. // Connect to the Mongo Database.
+30
View File
@@ -0,0 +1,30 @@
import mongoose from 'mongoose'
import config from '../../config/index.js'
import User from '../../src/adapters/localdb/models/users.js'
async function getUsers () {
// Connect to the Mongo Database.
mongoose.Promise = global.Promise
mongoose.set('useCreateIndex', true) // Stop deprecation warning.
await mongoose.connect(
config.database,
{ useNewUrlParser: true, useUnifiedTopology: true }
)
// Find the user by email.
const user = await User.findOne({
email: 'test@test.com'
}, '-password')
// Update the users password
// user.password = 'newpassword'
// Change the user to an admin
// user.type = 'admin'
// Save the changes to the database.
await user.save()
mongoose.connection.close()
}
getUsers()