mirror of
https://github.com/Permissionless-Software-Foundation/x402-base-facilitator.git
synced 2026-09-21 16:52:01 -07:00
Merge pull request #101 from Permissionless-Software-Foundation/ct-unstable
Increased test coverage to 100%
This commit is contained in:
@@ -0,0 +1,8 @@
|
||||
[
|
||||
{
|
||||
"srcDir": "",
|
||||
"destDir": "",
|
||||
"files": "**/*.js",
|
||||
"command": "npm run lint"
|
||||
}
|
||||
]
|
||||
+1
-1
@@ -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 () {
|
||||
|
||||
Vendored
+7
-2
@@ -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
|
||||
|
||||
@@ -17,7 +22,7 @@ const ipfsCoordName = process.env.COORD_NAME
|
||||
|
||||
export default {
|
||||
// Configure TCP port.
|
||||
port: process.env.PORT || 5001,
|
||||
port: process.env.PORT || 5020,
|
||||
|
||||
// Password for HTML UI that displays logs.
|
||||
logPass: 'test',
|
||||
|
||||
+2
-1
@@ -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
@@ -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 }
|
||||
|
||||
Generated
+1730
-3420
File diff suppressed because it is too large
Load Diff
+6
-6
@@ -7,14 +7,14 @@
|
||||
"scripts": {
|
||||
"start": "node index.js",
|
||||
"test": "npm run test:all",
|
||||
"test:all": "export SVC_ENV=test && nyc --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:e2e:auto": "export SVC_ENV=test && mocha --exit --timeout 15000 test/e2e/automated/",
|
||||
"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": "nyc report --reporter=text-lcov | coveralls",
|
||||
"coverage:report": "export SVC_ENV=test && nyc --reporter=html mocha --exit --timeout 15000 --recursive test/unit/ test/e2e/automated/"
|
||||
"coverage": "c8 report --reporter=text-lcov | coveralls",
|
||||
"coverage:report": "export SVC_ENV=test && c8 --reporter=html mocha --exit --timeout 15000 --recursive test/unit/ test/e2e/automated/"
|
||||
},
|
||||
"author": "Chris Troutner <chris.troutner@gmail.com>",
|
||||
"license": "MIT",
|
||||
@@ -27,7 +27,7 @@
|
||||
"axios": "0.27.2",
|
||||
"bcryptjs": "2.4.3",
|
||||
"glob": "7.1.6",
|
||||
"ipfs-coord-esm": "9.1.1",
|
||||
"ipfs-coord-esm": "9.1.3",
|
||||
"ipfs-http-client": "58.0.0",
|
||||
"jsonrpc-lite": "2.2.0",
|
||||
"jsonwebtoken": "8.5.1",
|
||||
@@ -44,7 +44,7 @@
|
||||
"koa-static": "5.0.0",
|
||||
"koa2-ratelimit": "0.9.1",
|
||||
"line-reader": "0.4.0",
|
||||
"minimal-slp-wallet": "5.0.3",
|
||||
"minimal-slp-wallet": "5.1.0",
|
||||
"mongoose": "5.13.14",
|
||||
"node-fetch": "npm:@achingbrain/node-fetch@2.6.7",
|
||||
"nodemailer": "6.7.5",
|
||||
@@ -55,11 +55,11 @@
|
||||
},
|
||||
"devDependencies": {
|
||||
"apidoc": "0.51.1",
|
||||
"c8": "7.12.0",
|
||||
"chai": "4.3.0",
|
||||
"coveralls": "3.1.0",
|
||||
"husky": "4.3.8",
|
||||
"mocha": "10.0.0",
|
||||
"nyc": "15.1.0",
|
||||
"semantic-release": "19.0.3",
|
||||
"sinon": "9.2.4",
|
||||
"standard": "17.0.0",
|
||||
|
||||
@@ -25,12 +25,12 @@ 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 jsonFiles = new JsonFiles()
|
||||
const __dirname = url.fileURLToPath(new URL('.', import.meta.url))
|
||||
|
||||
const jsonFiles = new JsonFiles()
|
||||
|
||||
const JSON_FILE = `system-user-${config.env}.json`
|
||||
const JSON_PATH = `${__dirname.toString()}/../../config/${JSON_FILE}`
|
||||
const JSON_PATH = `${__dirname.toString()}../../config/${JSON_FILE}`
|
||||
|
||||
const LOCALHOST = `http://localhost:${config.port}`
|
||||
const context = {}
|
||||
@@ -90,6 +90,8 @@ class Admin {
|
||||
// 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
|
||||
} catch (err) {
|
||||
|
||||
@@ -51,6 +51,9 @@ class Adapters {
|
||||
// Do not start these adapters if this is an e2e test.
|
||||
if (this.config.env !== 'test') {
|
||||
await this.ipfs.start()
|
||||
} else {
|
||||
// These lines are here to ensure code coverage hits 100%.
|
||||
console.log('Not starting IPFS node since this is an e2e test.')
|
||||
}
|
||||
|
||||
console.log('Async Adapters have been started.')
|
||||
|
||||
@@ -76,5 +76,5 @@ logger.outputToConsole()
|
||||
|
||||
const wlogger = logger.wlogger
|
||||
|
||||
// export default { wlogger, Wlogger };
|
||||
export default wlogger
|
||||
export { wlogger as default, Wlogger }
|
||||
// export default wlogger
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -1,20 +1,19 @@
|
||||
/*
|
||||
import testUtils from '../../utils/test-utils.js';
|
||||
import { assert } from 'chai';
|
||||
import config from '../../../config/index.js';
|
||||
import axios from 'axios';
|
||||
import sinon from 'sinon';
|
||||
import util from 'util';
|
||||
import testUtils from '../../utils/test-utils.js'
|
||||
import { assert } from 'chai'
|
||||
import config from '../../../config/index.js'
|
||||
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'
|
||||
util.inspect.defaultOptions = { depth: 1 }
|
||||
|
||||
const LOCALHOST = `http://localhost:${config.port}`
|
||||
|
||||
const context = {}
|
||||
|
||||
import UserController from '../../../src/controllers/rest-api/users/controller.js';
|
||||
import Adapters from '../../../src/adapters/index.js';
|
||||
const adapters = new Adapters()
|
||||
import UseCases from '../../../src/use-cases/index.js';
|
||||
let uut
|
||||
let sandbox
|
||||
|
||||
@@ -39,7 +38,7 @@ describe('Users', () => {
|
||||
|
||||
// Get the JWT used to log in as the admin 'system' user.
|
||||
const adminJWT = await testUtils.getAdminJWT()
|
||||
// console.log(`adminJWT: ${admi nJWT}`)
|
||||
console.log(`adminJWT: ${adminJWT}`)
|
||||
context.adminJWT = adminJWT
|
||||
|
||||
// const admin = await testUtils.loginAdminUser()
|
||||
@@ -786,4 +785,3 @@ describe('Users', () => {
|
||||
})
|
||||
})
|
||||
})
|
||||
*/
|
||||
|
||||
@@ -52,7 +52,7 @@ describe('#IPFS', () => {
|
||||
it('should get the public IP address if this node is a Circuit Relay', async () => {
|
||||
// Mock dependencies.
|
||||
uut.IpfsCoord = IPFSCoordMock
|
||||
sandbox.stub(uut.publicIp, 'v4').returns('123')
|
||||
sandbox.stub(uut.publicIp, 'v4').resolves('123')
|
||||
|
||||
// Force Circuit Relay
|
||||
uut.config.isCircuitRelay = true
|
||||
@@ -62,6 +62,21 @@ describe('#IPFS', () => {
|
||||
|
||||
assert.equal(result, true)
|
||||
})
|
||||
|
||||
it('should exit quietly if this node is a Circuit Relay and there is an issue getting the IP address', async () => {
|
||||
// Mock dependencies.
|
||||
uut.IpfsCoord = IPFSCoordMock
|
||||
sandbox.stub(uut.publicIp, 'v4').rejects(new Error('test error'))
|
||||
|
||||
// Force Circuit Relay
|
||||
uut.config.isCircuitRelay = true
|
||||
|
||||
const result = await uut.start()
|
||||
// console.log('result: ', result)
|
||||
|
||||
assert.equal(result, true)
|
||||
})
|
||||
|
||||
it('should return a promise that resolves into an instance of IPFS in production mode', async () => {
|
||||
uut.config.isProduction = true
|
||||
// Mock dependencies.
|
||||
|
||||
@@ -1,39 +1,39 @@
|
||||
// import { assert } from 'chai';
|
||||
// import { Wlogger } from '../../../src/adapters/wlogger.js';
|
||||
// import sinon from 'sinon';
|
||||
//
|
||||
// let uut
|
||||
// let sandbox
|
||||
//
|
||||
// describe('#wlogger', () => {
|
||||
// beforeEach(() => {
|
||||
// sandbox = sinon.createSandbox()
|
||||
// })
|
||||
//
|
||||
// afterEach(() => {
|
||||
// sandbox.restore()
|
||||
//
|
||||
// uut = new Wlogger()
|
||||
// })
|
||||
//
|
||||
// describe('#constructor', () => {
|
||||
// it('should create a new wlogger instance', () => {
|
||||
// uut = new Wlogger()
|
||||
// // console.log('uut: ', uut)
|
||||
//
|
||||
// assert.property(uut, 'transport')
|
||||
// })
|
||||
// })
|
||||
//
|
||||
// describe('#notifyRotation', () => {
|
||||
// it('should notify of a log rotation', () => {
|
||||
// uut.notifyRotation()
|
||||
// })
|
||||
// })
|
||||
//
|
||||
// describe('#envronment', () => {
|
||||
// it('should write to console in non-test environment', () => {
|
||||
// uut.outputToConsole()
|
||||
// })
|
||||
// })
|
||||
// })
|
||||
import { assert } from 'chai'
|
||||
import { Wlogger } from '../../../src/adapters/wlogger.js'
|
||||
import sinon from 'sinon'
|
||||
|
||||
let uut
|
||||
let sandbox
|
||||
|
||||
describe('#wlogger', () => {
|
||||
beforeEach(() => {
|
||||
sandbox = sinon.createSandbox()
|
||||
})
|
||||
|
||||
afterEach(() => {
|
||||
sandbox.restore()
|
||||
|
||||
uut = new Wlogger()
|
||||
})
|
||||
|
||||
describe('#constructor', () => {
|
||||
it('should create a new wlogger instance', () => {
|
||||
uut = new Wlogger()
|
||||
// console.log('uut: ', uut)
|
||||
|
||||
assert.property(uut, 'transport')
|
||||
})
|
||||
})
|
||||
|
||||
describe('#notifyRotation', () => {
|
||||
it('should notify of a log rotation', () => {
|
||||
uut.notifyRotation()
|
||||
})
|
||||
})
|
||||
|
||||
describe('#envronment', () => {
|
||||
it('should write to console in non-test environment', () => {
|
||||
uut.outputToConsole()
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
@@ -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'
|
||||
|
||||
|
||||
@@ -0,0 +1,50 @@
|
||||
/*
|
||||
Unit tests for the config directory
|
||||
*/
|
||||
|
||||
import { assert } from 'chai'
|
||||
|
||||
let currentEnv
|
||||
|
||||
describe('#config', () => {
|
||||
before(() => {
|
||||
// Backup the current environment setting.
|
||||
currentEnv = process.env.SVC_ENV
|
||||
})
|
||||
|
||||
after(() => {
|
||||
// Restore the environment setting before starting these tests.
|
||||
process.env.SVC_ENV = currentEnv
|
||||
})
|
||||
|
||||
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')
|
||||
})
|
||||
})
|
||||
@@ -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)
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
@@ -4,7 +4,6 @@
|
||||
|
||||
// Public npm libraries
|
||||
import { assert } from 'chai'
|
||||
|
||||
import sinon from 'sinon'
|
||||
|
||||
// Local libraries
|
||||
|
||||
@@ -6,7 +6,6 @@
|
||||
|
||||
// Public npm libraries
|
||||
import { assert } from 'chai'
|
||||
|
||||
import sinon from 'sinon'
|
||||
|
||||
// Local support libraries
|
||||
@@ -331,22 +330,23 @@ describe('#users-use-case', () => {
|
||||
}
|
||||
})
|
||||
|
||||
// it('should update the user model', async () => {
|
||||
// const newData = {
|
||||
// email: 'test@test.com',
|
||||
// password: 'password',
|
||||
// name: 'testy tester'
|
||||
// }
|
||||
//
|
||||
// const result = await uut.updateUser(testUser, newData)
|
||||
//
|
||||
// // Assert that expected properties and values exist.
|
||||
// assert.property(result, '_id')
|
||||
// assert.property(result, 'email')
|
||||
// assert.equal(result.email, 'test@test.com')
|
||||
// assert.property(result, 'name')
|
||||
// assert.equal(result.name, 'testy tester')
|
||||
// })
|
||||
it('should update the user model', async () => {
|
||||
const newData = {
|
||||
email: 'test@test.com',
|
||||
password: 'password',
|
||||
name: 'testy tester'
|
||||
}
|
||||
testUser.save = async () => {}
|
||||
|
||||
const result = await uut.updateUser(testUser, newData)
|
||||
|
||||
// Assert that expected properties and values exist.
|
||||
assert.property(result, '_id')
|
||||
assert.property(result, 'email')
|
||||
assert.equal(result.email, 'test@test.com')
|
||||
assert.property(result, 'name')
|
||||
assert.equal(result.name, 'testy tester')
|
||||
})
|
||||
|
||||
// TODO: verify that an admin can change the type of a user
|
||||
})
|
||||
|
||||
@@ -4,15 +4,21 @@
|
||||
|
||||
// Public NPM libraries
|
||||
import mongoose from 'mongoose'
|
||||
|
||||
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.
|
||||
// https://blog.logrocket.com/alternatives-dirname-node-js-es-modules/
|
||||
import * as url from 'url'
|
||||
|
||||
const jsonFiles = new JsonFiles()
|
||||
|
||||
const LOCALHOST = `http://localhost:${config.port}`
|
||||
const __dirname = url.fileURLToPath(new URL('.', import.meta.url))
|
||||
|
||||
// Remove all collections from the DB.
|
||||
async function cleanDb () {
|
||||
@@ -112,9 +118,11 @@ async function loginTestUser () {
|
||||
|
||||
async function loginAdminUser () {
|
||||
try {
|
||||
const FILENAME = `../../config/system-user-${config.env}.json`
|
||||
const adminUserData = require(FILENAME)
|
||||
console.log(`adminUserData: ${JSON.stringify(adminUserData, null, 2)}`)
|
||||
const FILENAME = `${__dirname.toString()}../../config/system-user-${config.env}.json`
|
||||
// console.log('FILENAME: ', FILENAME)
|
||||
|
||||
const adminUserData = await jsonFiles.readJSON(FILENAME)
|
||||
// console.log(`adminUserData: ${JSON.stringify(adminUserData, null, 2)}`)
|
||||
|
||||
const options = {
|
||||
method: 'POST',
|
||||
@@ -151,8 +159,9 @@ async function getAdminJWT () {
|
||||
// process.env.KOA_ENV = process.env.KOA_ENV || 'dev'
|
||||
// console.log(`env: ${process.env.KOA_ENV}`)
|
||||
|
||||
const FILENAME = `../../config/system-user-${config.env}.json`
|
||||
const adminUserData = require(FILENAME)
|
||||
const FILENAME = `${__dirname.toString()}../../config/system-user-${config.env}.json`
|
||||
// console.log('FILENAME: ', FILENAME)
|
||||
const adminUserData = await jsonFiles.readJSON(FILENAME)
|
||||
// console.log(`adminUserData: ${JSON.stringify(adminUserData, null, 2)}`)
|
||||
|
||||
return adminUserData.token
|
||||
|
||||
Reference in New Issue
Block a user