From 7dfcb85ab946b7d3c2cc9a940add8ecdcf22ae1c Mon Sep 17 00:00:00 2001 From: Daniel Gonzalez Date: Tue, 3 Aug 2021 19:40:21 -0400 Subject: [PATCH] feat(endpoint): Added controller to enable endpoint --- src/controllers/rest-api/entry/controller.js | 66 ++++++++++ src/controllers/rest-api/entry/index.js | 60 +++++++++ src/controllers/rest-api/index.js | 5 + src/use-cases/entry.js | 4 +- .../entry/entry.rest.controller.unit.js | 122 ++++++++++++++++++ .../rest-api/entry/entry.rest.router.unit.js | 78 +++++++++++ test/unit/mocks/use-cases/index.js | 7 + 7 files changed, 340 insertions(+), 2 deletions(-) create mode 100644 src/controllers/rest-api/entry/controller.js create mode 100644 src/controllers/rest-api/entry/index.js create mode 100644 test/unit/controllers/rest-api/entry/entry.rest.controller.unit.js create mode 100644 test/unit/controllers/rest-api/entry/entry.rest.router.unit.js diff --git a/src/controllers/rest-api/entry/controller.js b/src/controllers/rest-api/entry/controller.js new file mode 100644 index 0000000..758d8ec --- /dev/null +++ b/src/controllers/rest-api/entry/controller.js @@ -0,0 +1,66 @@ +/* + REST API Controller library for the /entry route +*/ + +// const { wlogger } = require('../../../adapters/wlogger') + +let _this + +class EntryRESTControllerLib { + constructor (localConfig = {}) { + // Dependency Injection. + this.adapters = localConfig.adapters + if (!this.adapters) { + throw new Error( + 'Instance of Adapters library required when instantiating /users REST Controller.' + ) + } + this.useCases = localConfig.useCases + if (!this.useCases) { + throw new Error( + 'Instance of Use Cases library required when instantiating /users REST Controller.' + ) + } + + // Encapsulate dependencies + this.EntryModel = this.adapters.localdb.Entry + // this.userUseCases = this.useCases.user + + _this = this + } + + // No Documentation because this wont be a public endpoint + async createEntry (ctx) { + try { + const entryObj = ctx.request.body.entry + + const entry = await _this.useCases.entry.createEntry(entryObj) + // console.log('userData: ', userData) + // console.log('token: ', token) + + ctx.body = { entry } + } catch (err) { + // console.log(`err.message: ${err.message}`) + // console.log('err: ', err) + // ctx.throw(422, err.message) + _this.handleError(ctx, err) + } + } + + // DRY error handler + handleError (ctx, err) { + console.log('err', err.message) + // If an HTTP status is specified by the buisiness logic, use that. + if (err.status) { + if (err.message) { + ctx.throw(err.status, err.message) + } else { + ctx.throw(err.status) + } + } else { + // By default use a 422 error if the HTTP status is not specified. + ctx.throw(422, err.message) + } + } +} +module.exports = EntryRESTControllerLib diff --git a/src/controllers/rest-api/entry/index.js b/src/controllers/rest-api/entry/index.js new file mode 100644 index 0000000..0a31b37 --- /dev/null +++ b/src/controllers/rest-api/entry/index.js @@ -0,0 +1,60 @@ +/* + REST API library for /user route. +*/ + +// Public npm libraries. +const Router = require('koa-router') + +// Local libraries. +const EntryRESTControllerLib = require('./controller') + +let _this + +class UserRouter { + constructor (localConfig = {}) { + // Dependency Injection. + this.adapters = localConfig.adapters + if (!this.adapters) { + throw new Error( + 'Instance of Adapters library required when instantiating PostEntry REST Controller.' + ) + } + this.useCases = localConfig.useCases + if (!this.useCases) { + throw new Error( + 'Instance of Use Cases library required when instantiating PostEntry REST Controller.' + ) + } + + const dependencies = { + adapters: this.adapters, + useCases: this.useCases + } + + // Encapsulate dependencies. + this.entryRESTController = new EntryRESTControllerLib(dependencies) + + // Instantiate the router and set the base route. + const baseUrl = '/entry' + this.router = new Router({ prefix: baseUrl }) + + _this = this + } + + attach (app) { + if (!app) { + throw new Error( + 'Must pass app object when attaching REST API controllers.' + ) + } + + // Define the routes and attach the controller. + this.router.post('/', _this.entryRESTController.createEntry) + + // Attach the Controller routes to the Koa app. + app.use(_this.router.routes()) + app.use(_this.router.allowedMethods()) + } +} + +module.exports = UserRouter diff --git a/src/controllers/rest-api/index.js b/src/controllers/rest-api/index.js index 90255d8..c48b46c 100644 --- a/src/controllers/rest-api/index.js +++ b/src/controllers/rest-api/index.js @@ -11,6 +11,7 @@ const AuthRESTController = require('./auth') const UserRouter = require('./users') const ContactRESTController = require('./contact') const LogsRESTController = require('./logs') +const EntryRouter = require('./entry') class RESTControllers { constructor (localConfig = {}) { @@ -52,6 +53,10 @@ class RESTControllers { // Attach the REST API Controllers associated with the /logs route const logsRESTController = new LogsRESTController(dependencies) logsRESTController.attach(app) + + // Attach the REST API Controllers associated with the /entry route + const entryRouter = new EntryRouter(dependencies) + entryRouter.attach(app) } } diff --git a/src/use-cases/entry.js b/src/use-cases/entry.js index 13c78a4..b3b0607 100644 --- a/src/use-cases/entry.js +++ b/src/use-cases/entry.js @@ -24,7 +24,7 @@ class EntryLib { const entryEntity = this.EntryEntity.validate(entryObj) // Verify that the entry was signed by a specific BCH address. - const isValidSignature = this.bchjs._verifySignature(entryEntity.slpAddress) + const isValidSignature = this.bchjs._verifySignature(entryEntity) if (!isValidSignature) { throw new Error('Invalid signature') } @@ -33,7 +33,7 @@ class EntryLib { const psfBalance = await this.bchjs.getPSFTokenBalance(entryEntity.slpAddress) - if (psfBalance < 10) { + if (psfBalance < 1) { throw new Error('Insufficient psf balance') } diff --git a/test/unit/controllers/rest-api/entry/entry.rest.controller.unit.js b/test/unit/controllers/rest-api/entry/entry.rest.controller.unit.js new file mode 100644 index 0000000..f02e99d --- /dev/null +++ b/test/unit/controllers/rest-api/entry/entry.rest.controller.unit.js @@ -0,0 +1,122 @@ +/* + Unit tests for the REST API handler for the /users endpoints. +*/ + +// Public npm libraries +const assert = require('chai').assert +const sinon = require('sinon') + +// Local support libraries +const adapters = require('../../../mocks/adapters') +const UseCasesMock = require('../../../mocks/use-cases') + +const EntryController = require('../../../../../src/controllers/rest-api/entry/controller') +let uut +let sandbox +let ctx + +const mockContext = require('../../../../unit/mocks/ctx-mock').context + +describe('#Entry-REST-Controller', () => { + // const testUser = {} + + beforeEach(() => { + const useCases = new UseCasesMock() + uut = new EntryController({ adapters, useCases }) + + sandbox = sinon.createSandbox() + + // Mock the context object. + ctx = mockContext() + }) + + afterEach(() => sandbox.restore()) + + describe('#constructor', () => { + it('should throw an error if adapters are not passed in', () => { + try { + uut = new EntryController() + + assert.fail('Unexpected code path') + } catch (err) { + assert.include( + err.message, + 'Instance of Adapters library required when instantiating /users REST Controller.' + ) + } + }) + + it('should throw an error if useCases are not passed in', () => { + try { + uut = new EntryController({ adapters }) + + assert.fail('Unexpected code path') + } catch (err) { + assert.include( + err.message, + 'Instance of Use Cases library required when instantiating /users REST Controller.' + ) + } + }) + }) + + describe('#POST /entry', () => { + it('should return 422 status on biz logic error', async () => { + try { + await uut.createEntry(ctx) + + assert.fail('Unexpected result') + } catch (err) { + // console.log(err) + assert.equal(err.status, 422) + assert.include(err.message, 'Cannot read property') + } + }) + + it('should return 200 status on success', async () => { + ctx.request.body = { + entry: { + entry: 'entry', + description: 'test', + slpAddress: 'simpleledger:qpnty9t0w93fez04h7yzevujpv8pun204qqp0jfafg', + signature: 'IFytRg6KpvTHCzcW0ZwVhPqdKtRGpoRDcuEb958yIgJFUJlb1F5qPzt/JnlYE7r012BSFj+UT67DZVTU8oNB5vw=', + category: 'test' + } + } + + await uut.createEntry(ctx) + + // Assert the expected HTTP response + assert.equal(ctx.status, 200) + + // Assert that expected properties exist in the returned data. + assert.property(ctx.response.body, 'entry') + }) + }) + + describe('#handleError', () => { + it('should still throw error if there is no message', () => { + try { + const err = { + status: 404 + } + + uut.handleError(ctx, err) + } catch (err) { + assert.include(err.message, 'Not Found') + } + }) + it('should catch error if message is provided', () => { + try { + const err = { + status: 422, + message: 'Unprocessable Entity' + } + + uut.handleError(ctx, err) + } catch (err) { + assert.include(err.message, 'Unprocessable Entity') + } + }) + }) +}) diff --git a/test/unit/controllers/rest-api/entry/entry.rest.router.unit.js b/test/unit/controllers/rest-api/entry/entry.rest.router.unit.js new file mode 100644 index 0000000..16ecd88 --- /dev/null +++ b/test/unit/controllers/rest-api/entry/entry.rest.router.unit.js @@ -0,0 +1,78 @@ +/* + Unit tests for the REST API handler for the /users endpoints. +*/ + +// Public npm libraries +const assert = require('chai').assert +const sinon = require('sinon') + +// Local support libraries +const adapters = require('../../../mocks/adapters') +const UseCasesMock = require('../../../mocks/use-cases') +// const app = require('../../../mocks/app-mock') + +const EntryRouter = require('../../../../../src/controllers/rest-api/entry') +let uut +let sandbox +// let ctx + +// const mockContext = require('../../../../unit/mocks/ctx-mock').context + +describe('#Entry-REST-Router', () => { + // const testUser = {} + + beforeEach(() => { + const useCases = new UseCasesMock() + uut = new EntryRouter({ adapters, useCases }) + + sandbox = sinon.createSandbox() + + // Mock the context object. + // ctx = mockContext() + }) + + afterEach(() => sandbox.restore()) + + describe('#constructor', () => { + it('should throw an error if adapters are not passed in', () => { + try { + uut = new EntryRouter() + + assert.fail('Unexpected code path') + } catch (err) { + assert.include( + err.message, + 'Instance of Adapters library required when instantiating PostEntry REST Controller.' + ) + } + }) + + it('should throw an error if useCases are not passed in', () => { + try { + uut = new EntryRouter({ adapters }) + + assert.fail('Unexpected code path') + } catch (err) { + assert.include( + err.message, + 'Instance of Use Cases library required when instantiating PostEntry REST Controller.' + ) + } + }) + }) + + describe('#attach', () => { + it('should throw an error if app is not passed in.', () => { + try { + uut.attach() + + assert.fail('Unexpected code path') + } catch (err) { + assert.include( + err.message, + 'Must pass app object when attaching REST API controllers.' + ) + } + }) + }) +}) diff --git a/test/unit/mocks/use-cases/index.js b/test/unit/mocks/use-cases/index.js index 36d0fd1..efc1315 100644 --- a/test/unit/mocks/use-cases/index.js +++ b/test/unit/mocks/use-cases/index.js @@ -31,12 +31,19 @@ class UserUseCaseMock { } } +class EntryUseCaseMock { + async createEntry(userObj) { + return {} + } +} + class UseCasesMock { constuctor(localConfig = {}) { // this.user = new UserUseCaseMock(localConfig) } user = new UserUseCaseMock() + entry = new EntryUseCaseMock() } module.exports = UseCasesMock