diff --git a/package.json b/package.json index aed451e..9bfee16 100644 --- a/package.json +++ b/package.json @@ -14,7 +14,7 @@ "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 test/unit/biz-logic/ test/unit/rest-api/ test/e2e/automated/" + "coverage:report": "export SVC_ENV=test && nyc --reporter=html mocha --exit --timeout 15000 test/unit/biz-logic/ test/unit/json-rpc/ test/unit/rest-api/ test/e2e/automated/" }, "keywords": [ "koa-api-boilerplate", diff --git a/src/lib/users.js b/src/lib/users.js index 98b3d79..2f10996 100644 --- a/src/lib/users.js +++ b/src/lib/users.js @@ -95,6 +95,9 @@ class UserLib { async updateUser (existingUser, newData) { try { + // console.log('existingUser: ', existingUser) + // console.log('newData: ', newData) + // Input Validation // Optional inputs, but they must be strings if included. if (newData.email && typeof newData.email !== 'string') { diff --git a/src/rpc/users/index.js b/src/rpc/users/index.js index f8e98a2..97686a4 100644 --- a/src/rpc/users/index.js +++ b/src/rpc/users/index.js @@ -21,10 +21,11 @@ class UserRPC { // a specific endpoint. This method routes incoming calls to one of those // methods. async userRouter (rpcData) { + let endpoint = 'unknown' try { // console.log('userRouter rpcData: ', rpcData) - const endpoint = rpcData.payload.params.endpoint + endpoint = rpcData.payload.params.endpoint let user // Route the call based on the value of the method property. @@ -40,13 +41,24 @@ class UserRPC { user = await this.validators.ensureUser(rpcData) return await this.getUser(rpcData, user) + case 'updateUser': + user = await this.validators.ensureTargetUserOrAdmin(rpcData) + return await this.updateUser(rpcData, user) + case 'deleteUser': user = await this.validators.ensureTargetUserOrAdmin(rpcData) return await this.deleteUser(rpcData, user) } } catch (err) { console.error('Error in UsersRPC/rpcRouter()') - throw err + // throw err + + return { + success: false, + status: 500, + message: err.message, + endpoint + } } } @@ -135,6 +147,34 @@ class UserRPC { } } + async updateUser (rpcData, userModel) { + try { + // console.log('updateUser rpcData: ', rpcData) + + const newData = rpcData.payload.params + + const user = await this.userLib.updateUser(userModel, newData) + + return { + user, + endpoint: 'updateUser', + success: true, + status: 200, + message: '' + } + } catch (err) { + // console.log('updateUser err: ', err) + + // Return an error response + return { + success: false, + status: 422, + message: err.message, + endpoint: 'updateUser' + } + } + } + async deleteUser (rpcData, userModel) { try { // console.log('deleteUser rpcData: ', rpcData) @@ -142,13 +182,24 @@ class UserRPC { await this.userLib.deleteUser(userModel) const retObj = { - endpoint: 'deleteUser' + endpoint: 'deleteUser', + success: true, + status: 200, + message: '' } return retObj } catch (err) { - console.error('Error in deleteUser()') - throw err + // console.error('Error in deleteUser()') + // throw err + + // Return an error response + return { + success: false, + status: 422, + message: err.message, + endpoint: 'deleteUser' + } } } diff --git a/test/unit/json-rpc/a13-users.unit.js b/test/unit/json-rpc/a13-users.unit.js index b07281c..6d04977 100644 --- a/test/unit/json-rpc/a13-users.unit.js +++ b/test/unit/json-rpc/a13-users.unit.js @@ -145,6 +145,46 @@ describe('#UserRPC', () => { assert.equal(result, true) }) + it('should route to the updateUser method', async () => { + // Mock dependencies + sandbox.stub(uut, 'updateUser').resolves(true) + + // Generate the parsed data that the main router would pass to this + // endpoint. + const id = uid() + const userCall = jsonrpc.request(id, 'users', { + endpoint: 'updateUser', + apiToken: testUser.token, + userId: testUser.userData._id + }) + const jsonStr = JSON.stringify(userCall, null, 2) + const rpcData = jsonrpc.parse(jsonStr) + + const result = await uut.userRouter(rpcData) + // console.log('result: ', result) + + assert.equal(result, true) + }) + + it('should route to the getUser method', async () => { + // Mock dependencies + sandbox.stub(uut, 'getUser').resolves(true) + + // Generate the parsed data that the main router would pass to this + // endpoint. + const id = uid() + const userCall = jsonrpc.request(id, 'users', { + endpoint: 'getUser', + apiToken: testUser.token + }) + const jsonStr = JSON.stringify(userCall, null, 2) + const rpcData = jsonrpc.parse(jsonStr) + + const result = await uut.userRouter(rpcData) + + assert.equal(result, true) + }) + it('should route to the deleteUsers method', async () => { // Mock dependencies sandbox.stub(uut, 'deleteUser').resolves(true) @@ -165,12 +205,33 @@ describe('#UserRPC', () => { assert.equal(result, true) }) + + it('should return 500 status on routing issue', async () => { + // Force an error + sandbox.stub(uut, 'createUser').rejects(new Error('test error')) + + // Generate the parsed data that the main router would pass to this + // endpoint. + const id = uid() + const userCall = jsonrpc.request(id, 'users', { endpoint: 'createUser' }) + const jsonStr = JSON.stringify(userCall, null, 2) + const rpcData = jsonrpc.parse(jsonStr) + + const result = await uut.userRouter(rpcData) + // console.log('result: ', result) + + // assert.equal(result, true) + assert.equal(result.success, false) + assert.equal(result.status, 500) + assert.equal(result.message, 'test error') + assert.equal(result.endpoint, 'createUser') + }) }) describe('#getAllUsers', () => { it('should return all users', async () => { const result = await uut.getAll() - // console.log('result: ', result) + // console.log('getAll result: ', result) // Endpoint specific properties assert.property(result, 'users') @@ -198,6 +259,55 @@ describe('#UserRPC', () => { }) }) + describe('#updateUser', () => { + it('should update a user', async () => { + // Get the user model for the test user. + const testUserModel = await UserModel.findById( + testUser.userData._id, + '-password' + ) + + // Generate the parsed data that the main router would pass to this + // endpoint. + const id = uid() + const userCall = jsonrpc.request(id, 'users', { + endpoint: 'updateUser', + userId: testUser.userData._id.toString(), + name: 'test777' + }) + const jsonStr = JSON.stringify(userCall, null, 2) + const rpcData = jsonrpc.parse(jsonStr) + + const result = await uut.updateUser(rpcData, testUserModel) + // console.log('updateUser result: ', result) + + // Endpoint specific properties + assert.property(result, 'user') + assert.property(result.user, 'type') + assert.property(result.user, '_id') + assert.property(result.user, 'email') + assert.property(result.user, 'name') + + // Generic JSON RPC return values + assert.equal(result.endpoint, 'updateUser') + assert.equal(result.success, true) + assert.equal(result.status, 200) + assert.equal(result.message, '') + }) + + it('should return error data if biz logic throws an error', async () => { + // Force an error by not specifying an user ID. + const result = await uut.updateUser() + // console.log('result: ', result) + + // Generic JSON RPC return values + assert.equal(result.endpoint, 'updateUser') + assert.equal(result.success, false) + assert.equal(result.status, 422) + assert.include(result.message, 'Cannot read property') + }) + }) + describe('#getUser', () => { it('should return a specific user', async () => { // Generate the parsed data that the main router would pass to this @@ -211,7 +321,7 @@ describe('#UserRPC', () => { const rpcData = jsonrpc.parse(jsonStr) const result = await uut.getUser(rpcData) - // console.log('result: ', result) + // console.log('getUser result: ', result) // Endpoint specific properties assert.property(result, 'user') @@ -253,5 +363,17 @@ describe('#UserRPC', () => { assert.isOk('Not throwing an error is a success') }) + + it('should return error data if biz logic throws an error', async () => { + // Force an error by not specifying an user ID. + const result = await uut.deleteUser() + // console.log('result: ', result) + + // Generic JSON RPC return values + assert.equal(result.endpoint, 'deleteUser') + assert.equal(result.success, false) + assert.equal(result.status, 422) + assert.include(result.message, 'Cannot read property') + }) }) })