Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

#1207- Implement settlementModels management - GET and PUT operations #588

Merged
Show file tree
Hide file tree
Changes from 3 commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8,820 changes: 7,946 additions & 874 deletions package-lock.json

Large diffs are not rendered by default.

2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -83,7 +83,7 @@
"@mojaloop/central-services-health": "8.3.0",
"@mojaloop/central-services-logger": "8.6.0",
"@mojaloop/central-services-metrics": "8.8.0",
"@mojaloop/central-services-shared": "9.1.1",
"@mojaloop/central-services-shared": "9.1.2",
"@mojaloop/central-services-stream": "8.7.2",
"@mojaloop/event-sdk": "8.7.0",
"@mojaloop/forensic-logging-client": "8.3.0",
Expand Down
71 changes: 69 additions & 2 deletions src/api/settlementModels/handler.js
Original file line number Diff line number Diff line change
Expand Up @@ -28,9 +28,73 @@
const SettlementService = require('../../domain/settlement')
const Sidecar = require('../../lib/sidecar')
const ErrorHandler = require('@mojaloop/central-services-error-handling')

const Cache = require('../../lib/cache')
const Util = require('@mojaloop/central-services-shared').Util
const Enum = require('@mojaloop/central-services-shared').Enum.Settlements
const Logger = require('@mojaloop/central-services-logger')

const entityItem = ({ settlementModelId, name, isActive, settlementGranularityId, settlementInterchangeId, settlementDelayId, currencyId, requireLiquidityCheck, ledgerAccountTypeId, autoPositionReset }, ledgerAccountIds, settlementGranularityIds, settlementInterchangeIds, settlementDelayIds) => {
return {
settlementModelId,
name,
isActive: Enum.booleanType[isActive],
settlementGranularity: settlementGranularityIds[settlementGranularityId],
settlementInterchange: settlementInterchangeIds[settlementInterchangeId],
settlementDelay: settlementDelayIds[settlementDelayId],
currency: currencyId,
requireLiquidityCheck: Enum.booleanType[requireLiquidityCheck],
ledgerAccountTypeId: ledgerAccountIds[ledgerAccountTypeId],
autoPositionReset: Enum.booleanType[autoPositionReset]

}
}
const handleMissingRecord = (entity) => {
if (!entity) {
throw ErrorHandler.Factory.createFSPIOPError(ErrorHandler.Enums.FSPIOPErrorCodes.ID_NOT_FOUND, 'The requested resource could not be found.')
}
return entity
}

const getByName = async function (request) {
const entity = await SettlementService.getByName(request.params.name)
handleMissingRecord(entity)
const ledgerAccountTypes = await Cache.getEnums('ledgerAccountType')
const ledgerAccountIds = Util.transpose(ledgerAccountTypes)
const settlementGranularityIds = Util.transpose(Enum.SettlementGranularity)
const settlementInterchangeIds = Util.transpose(Enum.SettlementInterchange)
const settlementDelayIds = Util.transpose(Enum.SettlementDelay)

return entityItem(entity, ledgerAccountIds, settlementGranularityIds, settlementInterchangeIds, settlementDelayIds)
}
const getAll = async function () {
const results = await SettlementService.getAll()
const ledgerAccountTypes = await Cache.getEnums('ledgerAccountType')
const ledgerAccountIds = Util.transpose(ledgerAccountTypes)
const settlementGranularityIds = Util.transpose(Enum.SettlementGranularity)
const settlementInterchangeIds = Util.transpose(Enum.SettlementInterchange)
const settlementDelayIds = Util.transpose(Enum.SettlementDelay)
return results.map(record => entityItem(record, ledgerAccountIds, settlementGranularityIds, settlementInterchangeIds, settlementDelayIds))
}

const update = async function (request) {
Sidecar.logRequest(request)
try {
const updatedEntity = await SettlementService.update(request.params.name, request.payload)
if (request.payload.isActive !== undefined) {
const isActiveText = request.payload.isActive ? Enum.isActiveText.activated : Enum.isActiveText.disabled
const changeLog = JSON.stringify(Object.assign({}, request.params, { isActive: request.payload.isActive }))
Logger.info(`Settlement Model has been ${isActiveText} :: ${changeLog}`)
}
const ledgerAccountTypes = await Cache.getEnums('ledgerAccountType')
const ledgerAccountIds = Util.transpose(ledgerAccountTypes)
const settlementGranularityIds = Util.transpose(Enum.SettlementGranularity)
const settlementInterchangeIds = Util.transpose(Enum.SettlementInterchange)
const settlementDelayIds = Util.transpose(Enum.SettlementDelay)
return entityItem(updatedEntity, ledgerAccountIds, settlementGranularityIds, settlementInterchangeIds, settlementDelayIds)
} catch (err) {
throw ErrorHandler.Factory.reformatFSPIOPError(err)
}
}
const create = async function (request, h) {
Sidecar.logRequest(request)
try {
Expand All @@ -54,5 +118,8 @@ const create = async function (request, h) {
}

module.exports = {
create
create,
getByName,
getAll,
update
}
41 changes: 41 additions & 0 deletions src/api/settlementModels/routes.js
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,27 @@ const ledgerAccountList = require('../../../seeds/ledgerAccountType.js').ledgerA
const tags = ['api', 'settlement']

module.exports = [
{
method: 'GET',
path: '/settlementModels',
handler: Handler.getAll,
options: {
tags
}
},
{
method: 'GET',
path: '/settlementModels/{name}',
handler: Handler.getByName,
options: {
tags,
validate: {
params: Joi.object({
name: Joi.string().required().description('SettlementModel name')
})
}
}
},
{
method: 'POST',
path: '/settlementModels',
Expand All @@ -58,5 +79,25 @@ module.exports = [
})
}
}
},
{
method: 'PUT',
path: '/settlementModels/{name}',
handler: Handler.update,
options: {
tags,
payload: {
allow: ['application/json'],
failAction: 'error'
},
validate: {
payload: Joi.object({
isActive: Joi.boolean().required().description('settlementModel isActive boolean')
}),
params: Joi.object({
name: Joi.string().required().description('settlementModel name')
})
}
}
}
]
30 changes: 28 additions & 2 deletions src/domain/settlement/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,13 @@ const getByName = async (name) => {
throw ErrorHandler.Factory.reformatFSPIOPError(err)
}
}

const getAll = async () => {
try {
return await SettlementModelModel.getAll()
} catch (err) {
throw ErrorHandler.Factory.reformatFSPIOPError(err)
}
}
const getLedgerAccountTypeName = async (name) => {
try {
return await LedgerAccountTypeModel.getLedgerAccountByName(name)
Expand All @@ -54,8 +60,28 @@ const getLedgerAccountTypeName = async (name) => {
}
}

const update = async (name, payload) => {
try {
const settlementModel = await SettlementModelModel.getByName(name)
settlementModeExists(settlementModel)
await SettlementModelModel.update(settlementModel, payload.isActive)
settlementModel.isActive = +payload.isActive
return settlementModel
} catch (err) {
throw ErrorHandler.Factory.reformatFSPIOPError(err)
}
}

const settlementModeExists = (settlementModel) => {
if (settlementModel) {
return settlementModel
}
throw ErrorHandler.Factory.createInternalServerFSPIOPError('Settlement Model does not exist')
}
module.exports = {
createSettlementModel,
getLedgerAccountTypeName,
getByName
getByName,
getAll,
update
}
14 changes: 14 additions & 0 deletions src/models/settlement/settlementModel.js
Original file line number Diff line number Diff line change
Expand Up @@ -53,3 +53,17 @@ exports.getByName = async (name) => {
throw ErrorHandler.Factory.reformatFSPIOPError(err)
}
}
exports.getAll = async () => {
try {
return await Db.settlementModel.find()
} catch (err) {
throw ErrorHandler.Factory.reformatFSPIOPError(err)
}
}
exports.update = async (settlementModel, isActive) => {
try {
return await Db.settlementModel.update({ settlementModelId: settlementModel.settlementModelId }, { isActive })
} catch (err) {
throw ErrorHandler.Factory.reformatFSPIOPError(err)
}
}
124 changes: 122 additions & 2 deletions test/unit/api/settlementModels/handler.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -27,11 +27,32 @@

const Test = require('tapes')(require('tape'))
const Sinon = require('sinon')

const Logger = require('@mojaloop/central-services-logger')
const Handler = require('../../../../src/api/settlementModels/handler')
const Sidecar = require('../../../../src/lib/sidecar')
const SettlementService = require('../../../../src/domain/settlement')
const Cache = require('../../../../src/lib/cache')
const FSPIOPError = require('@mojaloop/central-services-error-handling').Factory.FSPIOPError

const createRequest = ({ payload, params, query }) => {
const sandbox = Sinon.createSandbox()
const requestPayload = payload || {}
const requestParams = params || {}
const requestQuery = query || {}
const enums = sandbox.stub()
enums.withArgs('ledgerAccountType').returns({ POSITION: 1, SETTLEMENT: 2, HUB_RECONCILIATION: 3, HUB_MULTILATERAL_SETTLEMENT: 4, HUB_FEE: 5 })
return {
payload: requestPayload,
params: requestParams,
query: requestQuery,
server: {
log: () => { },
methods: {
enums
}
}
}
}

Test('SettlementModel', settlementModelHandlerTest => {
let sandbox
Expand All @@ -42,20 +63,50 @@ Test('SettlementModel', settlementModelHandlerTest => {
isActive: 1,
createdDate: '2018-07-17T16:04:24.185Z'
}
const settlementModelService = [

{
settlementModelId: 106,
name: 'DEFERRED_NET',
isActive: 1,
settlementGranularityId: 1,
settlementInterchangeId: 1,
settlementDelayId: 2,
currencyId: null,
requireLiquidityCheck: 1,
ledgerAccountTypeId: 2,
autoPositionReset: 1
}
]
const settlementModel = [

{
settlementModelId: 106,
name: 'DEFERRED_NET',
isActive: true,
settlementGranularity: 'GROSS',
settlementInterchange: 'BILATERAL',
settlementDelay: 'DEFERRED',
currency: null,
requireLiquidityCheck: true,
ledgerAccountTypeId: 'SETTLEMENT',
autoPositionReset: true
}
]
settlementModelHandlerTest.beforeEach(test => {
sandbox = Sinon.createSandbox()
sandbox.stub(Sidecar)
sandbox.stub(Logger)
sandbox.stub(SettlementService)
sandbox.stub(Cache)
Cache.getEnums.returns(Promise.resolve({ POSITION: 1, SETTLEMENT: 2, HUB_RECONCILIATION: 3, HUB_MULTILATERAL_SETTLEMENT: 4, HUB_FEE: 5 }))
test.end()
})

settlementModelHandlerTest.afterEach(test => {
sandbox.restore()
test.end()
})

settlementModelHandlerTest.test('Handler Test', async handlerTest => {
handlerTest.test('create should create a new settlement model and return a 201', async function (test) {
const payload = {
Expand Down Expand Up @@ -129,6 +180,75 @@ Test('SettlementModel', settlementModelHandlerTest => {
}
})

handlerTest.test('getAll should return all the settlement models', async function (test) {
SettlementService.getLedgerAccountTypeName.returns(Promise.resolve(ledgerAccountType))
SettlementService.getAll.returns(Promise.resolve(settlementModelService))
const result = await Handler.getAll()
test.deepEqual(result, settlementModel, 'The results match')
test.end()
})

handlerTest.test('getByName should return the settlement model', async function (test) {
SettlementService.getByName.withArgs(settlementModel[0].name).returns(Promise.resolve(settlementModelService[0]))
const result = await Handler.getByName(createRequest({ params: { name: settlementModel[0].name } }))
test.deepEqual(result, settlementModel[0], 'The results match')
test.end()
})

handlerTest.test('getByName should throw error', async function (test) {
SettlementService.getByName.withArgs(settlementModel[0].name).returns(Promise.resolve(null))
try {
await Handler.getByName(createRequest({ params: { name: settlementModel[0].name } }))
} catch (e) {
test.ok(e instanceof Error)
test.equal(e.message, 'The requested resource could not be found.')
test.end()
}
})

handlerTest.test('update should update, return settlement model and utilize logger', async function (test) {
SettlementService.update.withArgs(settlementModel[0].name, { isActive: 1 }).returns(Promise.resolve(settlementModelService[0]))
try {
const result = await Handler.update(createRequest({
params: { name: settlementModel[0].name },
payload: { isActive: 1 }
}))
test.deepEqual(result, settlementModel[0], 'The results match')
test.ok(Logger.info.withArgs('SettlementModel has been activated :: {"name":"DEFERRED_NET","isActive":1}'))
test.end()
} catch (err) {
test.fail('Error thrown')
test.end()
}
})

handlerTest.test('update should update, return settlement model if settlement model when inactive and utilize', async function (test) {
SettlementService.update.withArgs(settlementModel[0].name, { isActive: 0 }).returns(Promise.resolve(settlementModelService[0]))
try {
const result = await Handler.update(createRequest({
params: { name: settlementModel[0].name },
payload: { isActive: 0 }
}))
test.deepEqual(result, settlementModel[0], 'The results match')
test.ok(Logger.info.withArgs('SettlementModel has been disabled :: {"name":"DEFERRED_NET","isActive":0}'))
test.end()
} catch (err) {
test.fail('Error thrown')
test.end()
}
})

handlerTest.test('update should throw error', async function (test) {
SettlementService.update.withArgs(settlementModel[0].name, { isActive: 1 }).throws(new Error('Test error'))
try {
await Handler.update(createRequest({ params: { name: settlementModelService[0].name }, payload: { isActive: 1 } }))
} catch (e) {
test.ok(e instanceof FSPIOPError)
test.equal(e.message, 'Test error')
test.end()
}
})

handlerTest.end()
})

Expand Down
Loading