fix(tests): Increased coverage of REST API validators

This commit is contained in:
Chris Troutner
2022-09-17 14:18:16 -07:00
parent d266bcc54b
commit d9377ea78b
15 changed files with 534 additions and 259 deletions
+8
View File
@@ -0,0 +1,8 @@
[
{
"srcDir": "",
"destDir": "",
"files": "**/*.js",
"command": "npm run lint"
}
]
+1 -1
View File
@@ -27,7 +27,7 @@ import AdminLib from '../src/adapters/admin.js'
import errorMiddleware from '../src/controllers/rest-api/middleware/error.js'
import wlogger from '../src/adapters/wlogger.js'
import Controllers from '../src/controllers/index.js'
import applyPassportMods from '../config/passport.js'
import { applyPassportMods } from '../config/passport.js'
class Server {
constructor () {
+6 -1
View File
@@ -5,9 +5,14 @@
/* eslint no-unneeded-ternary:0 */
// Hack to get __dirname back.
// https://blog.logrocket.com/alternatives-dirname-node-js-es-modules/
import * as url from 'url'
// Get the version from the package.json file.
import { readFileSync } from 'fs'
const pkgInfo = JSON.parse(readFileSync('package.json'))
const __dirname = url.fileURLToPath(new URL('.', import.meta.url))
const pkgInfo = JSON.parse(readFileSync(`${__dirname.toString()}/../../package.json`))
const version = pkgInfo.version
+2 -1
View File
@@ -5,11 +5,12 @@ import production from './env/production.js'
import test from './env/test.js'
const env = process.env.SVC_ENV || 'development'
console.log(`Loading config for this environment: ${env}`)
let config = development
if (env === 'test') {
config = test
} else if (env === 'production') {
} else if (env === 'prod') {
config = production
}
+4 -1
View File
@@ -49,8 +49,11 @@ function applyPassportMods (passport) {
passportCallback
)
)
return true
}
// For testing
// export default { passport, passportCallback };
export default applyPassportMods
// export default applyPassportMods
export { applyPassportMods, passportCallback }
+39 -151
View File
@@ -12,7 +12,7 @@
"axios": "0.27.2",
"bcryptjs": "2.4.3",
"glob": "7.1.6",
"ipfs-coord-esm": "9.1.2",
"ipfs-coord-esm": "../ipfs-coord-esm/",
"ipfs-http-client": "58.0.0",
"jsonrpc-lite": "2.2.0",
"jsonwebtoken": "8.5.1",
@@ -51,6 +51,28 @@
"uuid": "8.3.2"
}
},
"../ipfs-coord-esm": {
"version": "9.0.0",
"license": "MIT",
"dependencies": {
"bch-encrypt-lib": "2.0.0",
"uuid": "9.0.0"
},
"devDependencies": {
"@istanbuljs/esm-loader-hook": "0.2.0",
"chai": "4.3.6",
"cross-env": "7.0.3",
"lodash.clonedeep": "4.5.0",
"minimal-slp-wallet": "5.0.3",
"mocha": "10.0.0",
"nyc": "15.1.0",
"sinon": "14.0.0",
"standard": "17.0.0"
},
"peerDependencies": {
"ipfs-http-client": ">= 58.0.0"
}
},
"node_modules/@babel/code-frame": {
"version": "7.18.6",
"resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.18.6.tgz",
@@ -1489,11 +1511,6 @@
"acorn": "^6.0.0 || ^7.0.0 || ^8.0.0"
}
},
"node_modules/aes-js": {
"version": "3.1.2",
"resolved": "https://registry.npmjs.org/aes-js/-/aes-js-3.1.2.tgz",
"integrity": "sha512-e5pEa2kBnBOgR4Y/p20pskXI74UEz7de8ZGVo58asOtvSVG5YAbJeELPZxOmt+Bnz3rX753YKhfIn4X4l1PPRQ=="
},
"node_modules/agent-base": {
"version": "6.0.2",
"resolved": "https://registry.npmjs.org/agent-base/-/agent-base-6.0.2.tgz",
@@ -2053,15 +2070,6 @@
"url": "https://github.com/sponsors/isaacs"
}
},
"node_modules/bch-encrypt-lib": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/bch-encrypt-lib/-/bch-encrypt-lib-2.0.0.tgz",
"integrity": "sha512-EAfcw2vzcKMkdO004tfLmQEEpGZA9QeIgzG9sbxDweFiGr2BP6/06G5SIJdvsJhcAd9OS9Rw5bLlPAy25kX8HQ==",
"dependencies": {
"eccrypto-js": "^5.3.0",
"wif": "^2.0.6"
}
},
"node_modules/bchaddrjs-slp": {
"version": "0.2.5",
"resolved": "https://registry.npmjs.org/bchaddrjs-slp/-/bchaddrjs-slp-0.2.5.tgz",
@@ -3550,28 +3558,6 @@
"integrity": "sha512-UVU9dibq2JcFWxQPA6KCqj5O42VOmAY3zQUfEKxU0KpTGXwNoCjkX1e13eHNvw/xPynt6pU0rZ1htjWTNTSXsg==",
"dev": true
},
"node_modules/eccrypto-js": {
"version": "5.4.0",
"resolved": "https://registry.npmjs.org/eccrypto-js/-/eccrypto-js-5.4.0.tgz",
"integrity": "sha512-W4xBr0UANgpkSIu2FBHG5wjhvOR4L19HwAUm9xiA4c3bzB9gGbH8+E9hSMTNF+QbmEi+TDvt373JHKA6NnW38A==",
"dependencies": {
"aes-js": "3.1.2",
"enc-utils": "2.1.0",
"hash.js": "1.1.7",
"js-sha3": "0.8.0",
"pbkdf2": "^3.0.17",
"randombytes": "2.1.0",
"secp256k1": "3.8.0"
}
},
"node_modules/eccrypto-js/node_modules/randombytes": {
"version": "2.1.0",
"resolved": "https://registry.npmjs.org/randombytes/-/randombytes-2.1.0.tgz",
"integrity": "sha512-vYl3iOX+4CKUWuxGi9Ukhie6fsqXqS9FE2Zaic4tNFD2N2QQaXOMFbuKK4QmDHC0JO6B1Zp41J0LpT0oR68amQ==",
"dependencies": {
"safe-buffer": "^5.1.0"
}
},
"node_modules/ecdsa-sig-formatter": {
"version": "1.0.11",
"resolved": "https://registry.npmjs.org/ecdsa-sig-formatter/-/ecdsa-sig-formatter-1.0.11.tgz",
@@ -3648,16 +3634,6 @@
"resolved": "https://registry.npmjs.org/enabled/-/enabled-2.0.0.tgz",
"integrity": "sha512-AKrN98kuwOzMIdAizXGI86UFBoo26CL21UM763y1h/GMSJ4/OHU9k2YlsmBpyScFo/wbLzWQJBMCW4+IO3/+OQ=="
},
"node_modules/enc-utils": {
"version": "2.1.0",
"resolved": "https://registry.npmjs.org/enc-utils/-/enc-utils-2.1.0.tgz",
"integrity": "sha512-VD0eunGDyzhojePzkORWDnW88gi6tIeGb5Z6QVHugux6mMAPiXyw94fb/7WdDQEWhKMSoYRyzFFUebCqeH20PA==",
"dependencies": {
"bn.js": "4.11.8",
"is-typedarray": "1.0.0",
"typedarray-to-buffer": "3.1.5"
}
},
"node_modules/encodeurl": {
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-1.0.2.tgz",
@@ -6072,24 +6048,8 @@
}
},
"node_modules/ipfs-coord-esm": {
"version": "9.1.2",
"resolved": "https://registry.npmjs.org/ipfs-coord-esm/-/ipfs-coord-esm-9.1.2.tgz",
"integrity": "sha512-tZZMtBS7TTatDJD2gWdu9EOPt5E2kxwSRr5GMeRKRPnP4z1Z4QTpP94nmDhcDDABpdD4IseVQjcwUED5fd+IgQ==",
"dependencies": {
"bch-encrypt-lib": "2.0.0",
"uuid": "9.0.0"
},
"peerDependencies": {
"ipfs-http-client": ">= 58.0.0"
}
},
"node_modules/ipfs-coord-esm/node_modules/uuid": {
"version": "9.0.0",
"resolved": "https://registry.npmjs.org/uuid/-/uuid-9.0.0.tgz",
"integrity": "sha512-MXcSTerfPa4uqyzStbRoTgt5XIe3x5+42+q1sDuy3R5MDk66URdLMOZe5aPX/SQd+kuYAh0FdP/pO28IkQyTeg==",
"bin": {
"uuid": "dist/bin/uuid"
}
"resolved": "../ipfs-coord-esm",
"link": true
},
"node_modules/ipfs-http-client": {
"version": "58.0.0",
@@ -6645,7 +6605,8 @@
"node_modules/is-typedarray": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/is-typedarray/-/is-typedarray-1.0.0.tgz",
"integrity": "sha512-cyA56iCMHAh5CdzjJIa4aohJyeO1YbwLi3Jc35MmRU6poroFjIGZzUzupGiRPOjgHg9TLu43xbpwXk523fMxKA=="
"integrity": "sha512-cyA56iCMHAh5CdzjJIa4aohJyeO1YbwLi3Jc35MmRU6poroFjIGZzUzupGiRPOjgHg9TLu43xbpwXk523fMxKA==",
"dev": true
},
"node_modules/is-unicode-supported": {
"version": "0.1.0",
@@ -6880,11 +6841,6 @@
"resolved": "https://registry.npmjs.org/js-sha256/-/js-sha256-0.9.0.tgz",
"integrity": "sha512-sga3MHh9sgQN2+pJ9VYZ+1LPwXOxuBJBA5nrR5/ofPfuiJBE2hnjsaN8se8JznOmGLN2p49Pe5U/ttafcs/apA=="
},
"node_modules/js-sha3": {
"version": "0.8.0",
"resolved": "https://registry.npmjs.org/js-sha3/-/js-sha3-0.8.0.tgz",
"integrity": "sha512-gF1cRrHhIzNfToc802P800N8PpXS+evLLXfsVpowqmAFR9uwbi89WvXg2QspOmXL8QL86J4T1EpFu+yUkwJY3Q=="
},
"node_modules/js-tokens": {
"version": "4.0.0",
"resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz",
@@ -14153,14 +14109,6 @@
"node": ">= 0.6"
}
},
"node_modules/typedarray-to-buffer": {
"version": "3.1.5",
"resolved": "https://registry.npmjs.org/typedarray-to-buffer/-/typedarray-to-buffer-3.1.5.tgz",
"integrity": "sha512-zdu8XMNEDepKKR+XYOXAVPtWui0ly0NtohUscw+UmaHiAWT8hrV1rr//H6V+0DvJ3OQ19S979M0laLfX8rm82Q==",
"dependencies": {
"is-typedarray": "^1.0.0"
}
},
"node_modules/typeforce": {
"version": "1.18.0",
"resolved": "https://registry.npmjs.org/typeforce/-/typeforce-1.18.0.tgz",
@@ -16068,11 +16016,6 @@
"dev": true,
"requires": {}
},
"aes-js": {
"version": "3.1.2",
"resolved": "https://registry.npmjs.org/aes-js/-/aes-js-3.1.2.tgz",
"integrity": "sha512-e5pEa2kBnBOgR4Y/p20pskXI74UEz7de8ZGVo58asOtvSVG5YAbJeELPZxOmt+Bnz3rX753YKhfIn4X4l1PPRQ=="
},
"agent-base": {
"version": "6.0.2",
"resolved": "https://registry.npmjs.org/agent-base/-/agent-base-6.0.2.tgz",
@@ -16485,15 +16428,6 @@
}
}
},
"bch-encrypt-lib": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/bch-encrypt-lib/-/bch-encrypt-lib-2.0.0.tgz",
"integrity": "sha512-EAfcw2vzcKMkdO004tfLmQEEpGZA9QeIgzG9sbxDweFiGr2BP6/06G5SIJdvsJhcAd9OS9Rw5bLlPAy25kX8HQ==",
"requires": {
"eccrypto-js": "^5.3.0",
"wif": "^2.0.6"
}
},
"bchaddrjs-slp": {
"version": "0.2.5",
"resolved": "https://registry.npmjs.org/bchaddrjs-slp/-/bchaddrjs-slp-0.2.5.tgz",
@@ -17669,30 +17603,6 @@
}
}
},
"eccrypto-js": {
"version": "5.4.0",
"resolved": "https://registry.npmjs.org/eccrypto-js/-/eccrypto-js-5.4.0.tgz",
"integrity": "sha512-W4xBr0UANgpkSIu2FBHG5wjhvOR4L19HwAUm9xiA4c3bzB9gGbH8+E9hSMTNF+QbmEi+TDvt373JHKA6NnW38A==",
"requires": {
"aes-js": "3.1.2",
"enc-utils": "2.1.0",
"hash.js": "1.1.7",
"js-sha3": "0.8.0",
"pbkdf2": "^3.0.17",
"randombytes": "2.1.0",
"secp256k1": "3.8.0"
},
"dependencies": {
"randombytes": {
"version": "2.1.0",
"resolved": "https://registry.npmjs.org/randombytes/-/randombytes-2.1.0.tgz",
"integrity": "sha512-vYl3iOX+4CKUWuxGi9Ukhie6fsqXqS9FE2Zaic4tNFD2N2QQaXOMFbuKK4QmDHC0JO6B1Zp41J0LpT0oR68amQ==",
"requires": {
"safe-buffer": "^5.1.0"
}
}
}
},
"ecdsa-sig-formatter": {
"version": "1.0.11",
"resolved": "https://registry.npmjs.org/ecdsa-sig-formatter/-/ecdsa-sig-formatter-1.0.11.tgz",
@@ -17765,16 +17675,6 @@
"resolved": "https://registry.npmjs.org/enabled/-/enabled-2.0.0.tgz",
"integrity": "sha512-AKrN98kuwOzMIdAizXGI86UFBoo26CL21UM763y1h/GMSJ4/OHU9k2YlsmBpyScFo/wbLzWQJBMCW4+IO3/+OQ=="
},
"enc-utils": {
"version": "2.1.0",
"resolved": "https://registry.npmjs.org/enc-utils/-/enc-utils-2.1.0.tgz",
"integrity": "sha512-VD0eunGDyzhojePzkORWDnW88gi6tIeGb5Z6QVHugux6mMAPiXyw94fb/7WdDQEWhKMSoYRyzFFUebCqeH20PA==",
"requires": {
"bn.js": "4.11.8",
"is-typedarray": "1.0.0",
"typedarray-to-buffer": "3.1.5"
}
},
"encodeurl": {
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-1.0.2.tgz",
@@ -19425,19 +19325,19 @@
"integrity": "sha512-B9ZWJxHHOHUhUjCPrMpLD4xEq35bUTClHM1S6CBU5ixQnkZmwipwgc96vAd7AAGM9TGHvJR+Uss+/Ak6UphK+Q=="
},
"ipfs-coord-esm": {
"version": "9.1.2",
"resolved": "https://registry.npmjs.org/ipfs-coord-esm/-/ipfs-coord-esm-9.1.2.tgz",
"integrity": "sha512-tZZMtBS7TTatDJD2gWdu9EOPt5E2kxwSRr5GMeRKRPnP4z1Z4QTpP94nmDhcDDABpdD4IseVQjcwUED5fd+IgQ==",
"version": "file:../ipfs-coord-esm",
"requires": {
"@istanbuljs/esm-loader-hook": "0.2.0",
"bch-encrypt-lib": "2.0.0",
"chai": "4.3.6",
"cross-env": "7.0.3",
"lodash.clonedeep": "4.5.0",
"minimal-slp-wallet": "5.0.3",
"mocha": "10.0.0",
"nyc": "15.1.0",
"sinon": "14.0.0",
"standard": "17.0.0",
"uuid": "9.0.0"
},
"dependencies": {
"uuid": {
"version": "9.0.0",
"resolved": "https://registry.npmjs.org/uuid/-/uuid-9.0.0.tgz",
"integrity": "sha512-MXcSTerfPa4uqyzStbRoTgt5XIe3x5+42+q1sDuy3R5MDk66URdLMOZe5aPX/SQd+kuYAh0FdP/pO28IkQyTeg=="
}
}
},
"ipfs-http-client": {
@@ -19832,7 +19732,8 @@
"is-typedarray": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/is-typedarray/-/is-typedarray-1.0.0.tgz",
"integrity": "sha512-cyA56iCMHAh5CdzjJIa4aohJyeO1YbwLi3Jc35MmRU6poroFjIGZzUzupGiRPOjgHg9TLu43xbpwXk523fMxKA=="
"integrity": "sha512-cyA56iCMHAh5CdzjJIa4aohJyeO1YbwLi3Jc35MmRU6poroFjIGZzUzupGiRPOjgHg9TLu43xbpwXk523fMxKA==",
"dev": true
},
"is-unicode-supported": {
"version": "0.1.0",
@@ -20021,11 +19922,6 @@
"resolved": "https://registry.npmjs.org/js-sha256/-/js-sha256-0.9.0.tgz",
"integrity": "sha512-sga3MHh9sgQN2+pJ9VYZ+1LPwXOxuBJBA5nrR5/ofPfuiJBE2hnjsaN8se8JznOmGLN2p49Pe5U/ttafcs/apA=="
},
"js-sha3": {
"version": "0.8.0",
"resolved": "https://registry.npmjs.org/js-sha3/-/js-sha3-0.8.0.tgz",
"integrity": "sha512-gF1cRrHhIzNfToc802P800N8PpXS+evLLXfsVpowqmAFR9uwbi89WvXg2QspOmXL8QL86J4T1EpFu+yUkwJY3Q=="
},
"js-tokens": {
"version": "4.0.0",
"resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz",
@@ -25451,14 +25347,6 @@
"mime-types": "~2.1.24"
}
},
"typedarray-to-buffer": {
"version": "3.1.5",
"resolved": "https://registry.npmjs.org/typedarray-to-buffer/-/typedarray-to-buffer-3.1.5.tgz",
"integrity": "sha512-zdu8XMNEDepKKR+XYOXAVPtWui0ly0NtohUscw+UmaHiAWT8hrV1rr//H6V+0DvJ3OQ19S979M0laLfX8rm82Q==",
"requires": {
"is-typedarray": "^1.0.0"
}
},
"typeforce": {
"version": "1.18.0",
"resolved": "https://registry.npmjs.org/typeforce/-/typeforce-1.18.0.tgz",
+1 -1
View File
@@ -27,7 +27,7 @@
"axios": "0.27.2",
"bcryptjs": "2.4.3",
"glob": "7.1.6",
"ipfs-coord-esm": "9.1.2",
"ipfs-coord-esm": "../ipfs-coord-esm/",
"ipfs-http-client": "58.0.0",
"jsonrpc-lite": "2.2.0",
"jsonwebtoken": "8.5.1",
+1 -1
View File
@@ -25,9 +25,9 @@ import JsonFiles from '../adapters/json-files.js'
// Hack to get __dirname back.
// https://blog.logrocket.com/alternatives-dirname-node-js-es-modules/
import * as url from 'url'
const __dirname = url.fileURLToPath(new URL('.', import.meta.url))
const jsonFiles = new JsonFiles()
const __dirname = url.fileURLToPath(new URL('.', import.meta.url))
const JSON_FILE = `system-user-${config.env}.json`
const JSON_PATH = `${__dirname.toString()}/../../config/${JSON_FILE}`
@@ -1,5 +1,17 @@
/*
REST API validator middleware.
These are a series of functions that ensure the user making a REST API
matches a user in the database (or not). In can do fine-grain user control
such as telling the difference between an admin, a normal user, and an
anonymous user.
This middleware is used to gatekeep access to different REST API resources.
CT 9/17/22:
This library was lightly refactored to make it work with the new unit tests.
This not and the commented code below can be deleted once it is verified that
this refactor did not result in any breaking changes.
*/
import User from '../../../adapters/localdb/models/users.js'
@@ -21,12 +33,10 @@ class Validators {
async ensureUser (ctx, next) {
try {
// console.log(`getToken: ${typeof (getToken)}`)
const token = _this.getToken(ctx)
if (!token) {
// console.log(`Err: Token not provided.`)
ctx.throw(401)
throw new Error('Token could not be retrieved from header')
}
let decoded = null
@@ -35,20 +45,23 @@ class Validators {
// console.log(`config: ${JSON.stringify(config, null, 2)}`)
decoded = _this.jwt.verify(token, config.token)
} catch (err) {
// console.log(`Err: Token could not be decoded: ${err}`)
ctx.throw(401)
throw new Error('Could not verify JWT')
}
ctx.state.user = await _this.User.findById(decoded.id, '-password')
if (!ctx.state.user) {
// console.log(`Err: Could not find user.`)
ctx.throw(401)
// console.log('Err: Could not find user.')
throw new Error('Could not find user')
}
// return next()
return true
} catch (error) {
ctx.throw(401)
// console.log('Ensure user error: ', error)
// console.log('ctx: ', ctx)
ctx.status = 401
ctx.throw(401, error.message)
}
}
@@ -61,7 +74,8 @@ class Validators {
if (!token) {
// console.log(`Err: Token not provided.`)
ctx.throw(401)
// ctx.throw(401)
throw new Error('Token could not be retrieved from header')
}
let decoded = null
@@ -71,22 +85,26 @@ class Validators {
decoded = _this.jwt.verify(token, config.token)
} catch (err) {
// console.log(`Err: Token could not be decoded: ${err}`)
ctx.throw(401)
// ctx.throw(401)
throw new Error('Could not verify JWT')
}
ctx.state.user = await _this.User.findById(decoded.id, '-password')
if (!ctx.state.user) {
// console.log(`Err: Could not find user.`)
ctx.throw(401)
// ctx.throw(401)
throw new Error('Could not find user')
}
if (ctx.state.user.type !== 'admin') {
ctx.throw(401, 'not admin')
// ctx.throw(401, 'not admin')
throw new Error('User is not an admin')
}
// return next()
return true
} catch (error) {
ctx.status = 401
ctx.throw(401, error.message)
}
}
@@ -102,7 +120,8 @@ class Validators {
if (!token) {
// console.log(`Err: Token not provided.`)
ctx.throw(401)
// ctx.throw(401)
throw new Error('Token could not be retrieved from header')
}
// The user ID targeted in this API call.
@@ -115,14 +134,16 @@ class Validators {
// console.log(`config: ${JSON.stringify(config, null, 2)}`)
decoded = _this.jwt.verify(token, config.token)
} catch (err) {
// console.log(`Err: Token could not be decoded: ${err}`)
ctx.throw(401)
console.log(`Err: Token could not be decoded: ${err}`)
// ctx.throw(401)
throw new Error('Could not verify JWT')
}
ctx.state.user = await _this.User.findById(decoded.id, '-password')
if (!ctx.state.user) {
// console.log(`Err: Could not find user.`)
ctx.throw(401)
// ctx.throw(401)
throw new Error('Could not find user')
}
// console.log('ctx.state.user: ', ctx.state.user)
@@ -136,7 +157,8 @@ class Validators {
// If they don't match, then the calling user better be an admin.
if (ctx.state.user.type !== 'admin') {
ctx.throw(401, 'not admin')
// ctx.throw(401, 'not admin')
throw new Error('User is not an admin')
} else {
wlogger.verbose("It's ok. The user is an admin.")
}
@@ -145,6 +167,8 @@ class Validators {
// return next()
return true
} catch (error) {
// console.log('Error in ensureTargetUserOrAdmin(): ', error)
ctx.status = 401
ctx.throw(401, error.message)
}
}
+2 -2
View File
@@ -36,7 +36,7 @@ class UserRESTControllerLib {
* @apiGroup REST Users
*
* @apiExample Example usage:
* curl -H "Content-Type: application/json" -X POST -d '{ "user": { "email": "email@format.com", "name": "my name", "password": "secretpasas" } }' localhost:5001/users
* curl -H "Content-Type: application/json" -X POST -d '{ "user": { "email": "email@format.com", "name": "my name", "password": "secretpasas" } }' localhost:5010/users
*
* @apiParam {Object} user User object (required)
* @apiParam {String} user.email Email
@@ -136,7 +136,7 @@ class UserRESTControllerLib {
* @apiGroup REST Users
*
* @apiExample Example usage:
* curl -H "Content-Type: application/json" -X GET localhost:5000/users/56bd1da600a526986cf65c80
* curl -H "Content-Type: application/json" -X GET localhost:5010/users/56bd1da600a526986cf65c80
*
* @apiSuccess {Object} users User object
* @apiSuccess {ObjectId} users._id User id
@@ -0,0 +1,306 @@
/*
Unit tests for the REST API middleware that validates users.
*/
// Public npm libraries
import { assert } from 'chai'
import sinon from 'sinon'
// Local libraries
import Validators from '../../../../../src/controllers/rest-api/middleware/validators.js'
import { context as mockContext } from '../../../../unit/mocks/ctx-mock.js'
describe('#Validators', () => {
let uut
let ctx
let sandbox
beforeEach(() => {
uut = new Validators()
// Mock the context object.
ctx = mockContext()
sandbox = sinon.createSandbox()
})
afterEach(() => sandbox.restore())
describe('#getToken', () => {
it('should return null if no header is provided', () => {
const result = uut.getToken(ctx)
assert.equal(result, null)
})
it('should return null if header is not in two parts', () => {
ctx.request.header.authorization = 'Bearer'
const result = uut.getToken(ctx)
assert.equal(result, null)
})
it('should return null if first part of header does not container the word bearer', () => {
ctx.request.header.authorization = 'some thing'
const result = uut.getToken(ctx)
assert.equal(result, null)
})
it('should return the JWT token from the header', () => {
const jwt = 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpZCI6IjYzMjNiNTUwNzgxYWYzNTc4YzI0ZmU5YiIsImlhdCI6MTY2MzQ0NDczNCwiZXhwIjoxNjYzNTMxMTM0fQ.BY5sOfXc4z5axS98CdTfyqnO9y2wijOlwnv52rcvxHA'
ctx.request.header.authorization = `Bearer ${jwt}`
const result = uut.getToken(ctx)
assert.equal(result, jwt)
})
})
describe('#ensureUser', () => {
it('should throw error if token is not provided', async () => {
try {
// Mock dependencies and force desired code path
sandbox.stub(uut, 'getToken').returns()
await uut.ensureUser(ctx)
assert.fail('Unexpected result')
} catch (err) {
assert.equal(ctx.status, 401)
}
})
it('should throw error if token can not be verified', async () => {
try {
// Mock dependencies and force desired code path
sandbox.stub(uut, 'getToken').returns('fake-jwt')
sandbox.stub(uut.jwt, 'verify').throws(new Error('test error'))
await uut.ensureUser(ctx)
assert.fail('Unexpected result')
} catch (err) {
assert.equal(ctx.status, 401)
}
})
it('should throw error if user can not be found in database', async () => {
try {
// Mock dependencies and force desired code path
sandbox.stub(uut, 'getToken').returns('fake-jwt')
sandbox.stub(uut.jwt, 'verify').returns({})
sandbox.stub(uut.User, 'findById').resolves(false)
await uut.ensureUser(ctx)
assert.fail('Unexpected result')
} catch (err) {
assert.equal(ctx.status, 401)
}
})
it('should return true if the user is verified', async () => {
// Mock dependencies and force desired code path
sandbox.stub(uut, 'getToken').returns('fake-jwt')
sandbox.stub(uut.jwt, 'verify').returns({})
sandbox.stub(uut.User, 'findById').resolves({ user: 'alice' })
const result = await uut.ensureUser(ctx)
assert.equal(result, true)
})
})
describe('#ensureUser', () => {
it('should throw error if token is not provided', async () => {
try {
// Mock dependencies and force desired code path
sandbox.stub(uut, 'getToken').returns()
await uut.ensureAdmin(ctx)
assert.fail('Unexpected result')
} catch (err) {
assert.equal(ctx.status, 401)
}
})
it('should throw error if token can not be verified', async () => {
try {
// Mock dependencies and force desired code path
sandbox.stub(uut, 'getToken').returns('fake-jwt')
sandbox.stub(uut.jwt, 'verify').throws(new Error('test error'))
await uut.ensureAdmin(ctx)
assert.fail('Unexpected result')
} catch (err) {
assert.equal(ctx.status, 401)
}
})
it('should throw error if user can not be found in database', async () => {
try {
// Mock dependencies and force desired code path
sandbox.stub(uut, 'getToken').returns('fake-jwt')
sandbox.stub(uut.jwt, 'verify').returns({})
sandbox.stub(uut.User, 'findById').resolves(false)
await uut.ensureAdmin(ctx)
assert.fail('Unexpected result')
} catch (err) {
assert.equal(ctx.status, 401)
}
})
it('should throw error if user is not an admin', async () => {
try {
// Mock dependencies and force desired code path
sandbox.stub(uut, 'getToken').returns('fake-jwt')
sandbox.stub(uut.jwt, 'verify').returns({})
sandbox.stub(uut.User, 'findById').resolves({ type: 'user' })
await uut.ensureAdmin(ctx)
assert.fail('Unexpected result')
} catch (err) {
assert.equal(ctx.status, 401)
}
})
it('should return true if the user is an admin', async () => {
// Mock dependencies and force desired code path
sandbox.stub(uut, 'getToken').returns('fake-jwt')
sandbox.stub(uut.jwt, 'verify').returns({})
sandbox.stub(uut.User, 'findById').resolves({ type: 'admin' })
const result = await uut.ensureAdmin(ctx)
assert.equal(result, true)
})
})
describe('#ensureTargetUserOrAdmin', () => {
it('should throw error if token is not provided', async () => {
try {
// Mock dependencies and force desired code path
sandbox.stub(uut, 'getToken').returns()
await uut.ensureTargetUserOrAdmin(ctx)
assert.fail('Unexpected result')
} catch (err) {
assert.equal(ctx.status, 401)
}
})
it('should throw error if token can not be verified', async () => {
try {
// Mock dependencies and force desired code path
sandbox.stub(uut, 'getToken').returns('fake-jwt')
sandbox.stub(uut.jwt, 'verify').throws(new Error('test error'))
ctx.params = {
id: '456'
}
await uut.ensureTargetUserOrAdmin(ctx)
assert.fail('Unexpected result')
} catch (err) {
assert.equal(ctx.status, 401)
}
})
it('should throw error if user can not be found in database', async () => {
try {
// Mock dependencies and force desired code path
sandbox.stub(uut, 'getToken').returns('fake-jwt')
sandbox.stub(uut.jwt, 'verify').returns({})
sandbox.stub(uut.User, 'findById').resolves(false)
ctx.params = {
id: '456'
}
await uut.ensureTargetUserOrAdmin(ctx)
assert.fail('Unexpected result')
} catch (err) {
assert.equal(ctx.status, 401)
}
})
it('should throw error if user is not an admin', async () => {
try {
// Mock dependencies and force desired code path
sandbox.stub(uut, 'getToken').returns('fake-jwt')
sandbox.stub(uut.jwt, 'verify').returns({})
sandbox.stub(uut.User, 'findById').resolves({ type: 'user' })
ctx.params = {
id: '456'
}
await uut.ensureTargetUserOrAdmin(ctx)
assert.fail('Unexpected result')
} catch (err) {
assert.equal(ctx.status, 401)
}
})
it('should throw error is user is not admin or target user', async () => {
try {
// Mock dependencies and force desired code path
sandbox.stub(uut, 'getToken').returns('fake-jwt')
sandbox.stub(uut.jwt, 'verify').returns({})
sandbox.stub(uut.User, 'findById').resolves({ type: 'user', _id: '123' })
ctx.params = {
id: '456'
}
await uut.ensureTargetUserOrAdmin(ctx)
assert.fail('Unexpected result')
} catch (err) {
assert.equal(ctx.status, 401)
}
})
it('should return true if the user is an admin', async () => {
// Mock dependencies and force desired code path
sandbox.stub(uut, 'getToken').returns('fake-jwt')
sandbox.stub(uut.jwt, 'verify').returns({})
sandbox.stub(uut.User, 'findById').resolves({ type: 'admin', _id: '123' })
ctx.params = {
id: '456'
}
const result = await uut.ensureTargetUserOrAdmin(ctx)
assert.equal(result, true)
})
it('should return true if the user is the target user', async () => {
// Mock dependencies and force desired code path
sandbox.stub(uut, 'getToken').returns('fake-jwt')
sandbox.stub(uut.jwt, 'verify').returns({})
sandbox.stub(uut.User, 'findById').resolves({ type: 'user', _id: '123' })
ctx.params = {
id: '123'
}
const result = await uut.ensureTargetUserOrAdmin(ctx)
assert.equal(result, true)
})
})
})
@@ -4,12 +4,10 @@
// 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 UserController from '../../../../../src/controllers/rest-api/users/controller.js'
+30 -2
View File
@@ -2,6 +2,8 @@
Unit tests for the config directory
*/
import { assert } from 'chai'
let currentEnv
describe('#config', () => {
@@ -15,8 +17,34 @@ describe('#config', () => {
process.env.SVC_ENV = currentEnv
})
it('Should return development environment config', () => {
// import config from '../../../config/index.js'
it('Should return development environment config by default', async () => {
const importedConfig = await import('../../../config/index.js')
const config = importedConfig.default
// console.log('config: ', config)
assert.equal(config.env, 'dev')
})
it('Should return test environment config', async () => {
// Hack to dynamically import a library multiple times:
// https://github.com/denoland/deno/issues/6946
process.env.SVC_ENV = 'test'
const importedConfig2 = await import('../../../config/index.js?foo=bar1')
const config = importedConfig2.default
// console.log('config: ', config)
assert.equal(config.env, 'test')
})
it('Should return test environment config', async () => {
process.env.SVC_ENV = 'prod'
const importedConfig3 = await import('../../../config/index.js?foo=bar2')
const config = importedConfig3.default
// console.log('config: ', config)
assert.equal(config.env, 'prod')
})
})
+93 -78
View File
@@ -1,78 +1,93 @@
// /*
// Unit tests for the passport library.
// */
//
// // Public npm libraries
// // const assert = require('chai').assert
// import sinon from 'sinon';
//
// // Local libraries
// import User from '../../../src/adapters/localdb/models/users.js';
//
// import { passport, passportCallback } from '../../../config/passport.js';
// import adaptersMock from '../mocks/adapters/index.js';
//
// describe('#passport', () => {
// let sandbox
// let id
// let done
//
// beforeEach(() => {
// sandbox = sinon.createSandbox()
//
// id = 'abc123'
// done = () => {}
// })
//
// afterEach(() => sandbox.restore())
//
// describe('#serializeUser', () => {
// it('should serialize a user', () => {
// const user = {
// id: 'abc123'
// }
// const done = () => {}
//
// passport.serializeUser(user, done)
// })
// })
//
// describe('#deserializeUser', () => {
// it('should deserialize a user', () => {
// // Mock Users model.
// sandbox.stub(User, 'findById').resolves({ id })
//
// passport.deserializeUser(id, done)
// })
//
// it('should catch and handle errors', () => {
// // Force an error
// sandbox.stub(User, 'findById').rejects(new Error('test error'))
//
// passport.deserializeUser(id, done)
// })
// })
//
// describe('#passportCallback', () => {
// it('should return if user is found', () => {
// // Mock Users model.
// sandbox.stub(User, 'findOne').resolves({ id })
//
// passportCallback(id, 'password', done)
// })
//
// it('should return if password is validated', () => {
// // Mock Users model.
// sandbox.stub(User, 'findOne').resolves(new adaptersMock.localdb.Users())
//
// passportCallback(id, 'password', done)
// })
//
// it('should catch a high-level error', () => {
// // Force an error
// sandbox.stub(User, 'findOne').rejects(new Error('test error'))
//
// passportCallback(id, 'password', done)
// })
// })
// })
/*
Unit tests for the passport library.
*/
// Public npm libraries
import { assert } from 'chai'
import sinon from 'sinon'
import passport from 'koa-passport'
// Local libraries
import User from '../../../src/adapters/localdb/models/users.js'
import { applyPassportMods, passportCallback } from '../../../config/passport.js'
import adaptersMock from '../mocks/adapters/index.js'
describe('#passport', () => {
let sandbox
let id
let done
beforeEach(() => {
sandbox = sinon.createSandbox()
id = 'abc123'
done = () => {}
})
afterEach(() => sandbox.restore())
describe('#passportCallback', () => {
it('should return if user is found', () => {
// Mock Users model.
sandbox.stub(User, 'findOne').resolves({ id })
passportCallback(id, 'password', done)
})
it('should return if password is validated', () => {
// Mock Users model.
sandbox.stub(User, 'findOne').resolves(new adaptersMock.localdb.Users())
passportCallback(id, 'password', done)
})
it('should catch a high-level error', () => {
// Force an error
sandbox.stub(User, 'findOne').rejects(new Error('test error'))
passportCallback(id, 'password', done)
})
})
describe('#applyPassportMods', () => {
it('should apply modifications to default passport behavior', () => {
const result = applyPassportMods(passport)
assert.equal(result, true)
})
})
describe('#serializeUser', () => {
it('should serialize a user', () => {
const user = {
id: 'abc123'
}
const done = () => {}
applyPassportMods(passport)
passport.serializeUser(user, done)
})
})
describe('#deserializeUser', () => {
it('should deserialize a user', () => {
// Mock Users model.
sandbox.stub(User, 'findById').resolves({ id })
applyPassportMods(passport)
passport.deserializeUser(id, done)
})
it('should catch and handle errors', () => {
// Force an error
sandbox.stub(User, 'findById').rejects(new Error('test error'))
applyPassportMods(passport)
passport.deserializeUser(id, done)
})
})
})
-1
View File
@@ -4,7 +4,6 @@
// Public npm libraries
import { assert } from 'chai'
import sinon from 'sinon'
// Local libraries