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

feat(services): allow local config to be used instead of redirecting to a URL #62

Merged
merged 4 commits into from
Oct 6, 2021
Merged
Show file tree
Hide file tree
Changes from all 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
4 changes: 4 additions & 0 deletions config/default.json
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,10 @@
},
"ENDPOINT_SERVICE_URL": "http://central-ledger.local:3001",
"PARTICIPANT_LIST_SERVICE_URL": "http://ml-testing-toolkit:5000",
"PARTICIPANT_LIST_LOCAL": [
"dfspa",
"dfspb"
],
"ERROR_HANDLING": {
"includeCauseExtension": true,
"truncateExtensions": true
Expand Down
64 changes: 50 additions & 14 deletions src/server/handlers/services/{ServiceType}.ts
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@ import Logger from '@mojaloop/central-services-logger'
import { ReformatFSPIOPError } from '@mojaloop/central-services-error-handling'
import { Enum } from '@mojaloop/central-services-shared'
import { AuditEventAction } from '@mojaloop/event-sdk'
import Config from '~/shared/config'

import {
forwardGetServicesServiceTypeRequestToProviderService,
Expand All @@ -46,7 +47,7 @@ import { getSpanTags } from '~/shared/util'
* produces: application/json
* responses: 202, 400, 401, 403, 404, 405, 406, 501, 503
*/
const get = async (_context: unknown, request: Request, h: ResponseToolkit): Promise<ResponseObject> => {
const get = async (_context: unknown, request: Request, h: ResponseToolkit): Promise<ResponseObject> => {
const span = (request as any).span
const serviceType: string = request.params.ServiceType
try {
Expand All @@ -62,19 +63,54 @@ const get = async (_context: unknown, request: Request, h: ResponseToolkit): Pro
payload: request.payload
}, AuditEventAction.start)

// Note: calling async function without `await`
forwardGetServicesServiceTypeRequestToProviderService(
Enum.EndPoints.FspEndpointTemplates.TP_SERVICES_GET,
request.headers,
Enum.Http.RestMethods.GET,
serviceType,
span
)
.catch(err => {
// Do nothing with the error - forwardServicesServiceTypeRequest takes care of async errors
Logger.error('Services::get - forwardGetServicesServiceTypeRequestToProviderService async handler threw an unhandled error')
Logger.error(ReformatFSPIOPError(err))
})
// If PARTICIPANT_LIST_LOCAL is set, then we should use the local config to
// respond to this request instead of forwarding it to another service
// This is guaranteed to be mutually exclusive by Config
if (Config.PARTICIPANT_LIST_LOCAL) {
const payload: tpAPI.Schemas.ServicesServiceTypePutResponse = {
providers: Config.PARTICIPANT_LIST_LOCAL
}

// this is a reply: Source header must become Destination
// destination header should be Switch
const destinationDfspId = request.headers[Enum.Http.Headers.FSPIOP.SOURCE]
const headers = {
...request.headers,
}
headers[Enum.Http.Headers.FSPIOP.SOURCE] = Enum.Http.Headers.FSPIOP.SWITCH.value
headers[Enum.Http.Headers.FSPIOP.DESTINATION] = destinationDfspId

// Note: calling async function without `await`
forwardGetServicesServiceTypeRequestFromProviderService(
Enum.EndPoints.FspEndpointTemplates.TP_SERVICES_PUT,
Enum.EndPoints.FspEndpointTypes.TP_CB_URL_SERVICES_PUT,
headers,
Enum.Http.RestMethods.PUT,
serviceType,
payload,
span
)
.catch(err => {
// Do nothing with the error - forwardGetServicesServiceTypeRequestFromProviderService takes care of async errors
Logger.error('Services::put - forwardGetServicesServiceTypeRequestFromProviderService async handler threw an unhandled error')
Logger.error(ReformatFSPIOPError(err))
})

} else {
// Note: calling async function without `await`
forwardGetServicesServiceTypeRequestToProviderService(
Enum.EndPoints.FspEndpointTemplates.TP_SERVICES_GET,
request.headers,
Enum.Http.RestMethods.GET,
serviceType,
span
)
.catch(err => {
// Do nothing with the error - forwardServicesServiceTypeRequest takes care of async errors
Logger.error('Services::get - forwardGetServicesServiceTypeRequestToProviderService async handler threw an unhandled error')
Logger.error(ReformatFSPIOPError(err))
})
}

return h.response().code(Enum.Http.ReturnCodes.ACCEPTED.CODE)
} catch (err) {
Expand Down
18 changes: 17 additions & 1 deletion src/shared/config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,8 @@ import rc from 'rc'
import parse from 'parse-strings-in-object'
import Config from '../../config/default.json'
import Package from '../../package.json'
import logger from '@mojaloop/central-services-logger'

export interface ServiceConfig {
// package.json
PACKAGE: Record<string, unknown>;
Expand All @@ -47,7 +49,10 @@ export interface ServiceConfig {
generateTimeout: number;
};
ENDPOINT_SERVICE_URL: string;
PARTICIPANT_LIST_SERVICE_URL: string;
// if set, will use this url to lookup the /services/{ServiceType} call
PARTICIPANT_LIST_SERVICE_URL?: string;
// if set, will respond with this static list of participants
PARTICIPANT_LIST_LOCAL?: Array<string>;
ERROR_HANDLING: {
includeCauseExtension: boolean;
truncateExtensions: boolean;
Expand All @@ -73,6 +78,17 @@ export interface ServiceConfig {

const RC = parse(rc('THIRD_PARTY', Config)) as ServiceConfig

// Custom validation
if (RC.PARTICIPANT_LIST_LOCAL && RC.PARTICIPANT_LIST_SERVICE_URL) {
logger.warn('Both `PARTICIPANT_LIST_LOCAL` and `PARTICIPANT_LIST_SERVICE_URL` set. Defaulting to `PARTICIPANT_LIST_SERVICE_URL`')
RC.PARTICIPANT_LIST_LOCAL = undefined
}

if (!RC.PARTICIPANT_LIST_LOCAL && !RC.PARTICIPANT_LIST_SERVICE_URL) {
logger.error('Either `PARTICIPANT_LIST_LOCAL` or `PARTICIPANT_LIST_SERVICE_URL` are required configs')
throw new Error('Either `PARTICIPANT_LIST_LOCAL` or `PARTICIPANT_LIST_SERVICE_URL` are required configs')
}

export default {
...RC,
PACKAGE: Package
Expand Down
69 changes: 69 additions & 0 deletions test/unit/server/handlers/services/ServiceTypeLocal.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
/*****
License
--------------
Copyright © 2020 Mojaloop Foundation
The Mojaloop files are made available by the Mojaloop Foundation under the Apache License, Version 2.0 (the 'License') and you may not use these files except in compliance with the License. You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, the Mojaloop files are distributed on an 'AS IS' BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License.
Contributors
--------------
This is the official list of the Mojaloop project contributors for this file.
Names of the original copyright holders (individuals or organizations)
should be listed with a '*' in the first column. People who have
contributed from an organization can be listed under the organization
that actually holds the copyright for their contributions (see the
Gates Foundation organization for an example). Those individuals should have
their names indented and be marked with a '-'. Email address can be added
optionally within square brackets <email>.
* Gates Foundation
- Name Surname <name.surname@gatesfoundation.com>

- Lewis Daly <lewisd@crosslaketech.com>

--------------
******/
import { Request } from '@hapi/hapi'
import '~/shared/config'

jest.mock('~/shared/config', () => ({
PARTICIPANT_LIST_SERVICE_URL: undefined,
PARTICIPANT_LIST_LOCAL: ['dfspa', 'dfspb']
}));

import * as Services from '~/domain/services'
import { mockResponseToolkit } from 'test/unit/__mocks__/responseToolkit'
import ServicesServiceTypeHandler from '~/server/handlers/services/{ServiceType}'
import TestData from 'test/unit/data/mockData.json'
const mockData = JSON.parse(JSON.stringify(TestData))

const forwardGetServicesServiceTypeRequestFromProviderService = jest.spyOn(Services, 'forwardGetServicesServiceTypeRequestFromProviderService')
const getServicesByServiceTypeRequest = mockData.getServicesByServiceTypeRequest
const putServicesByServiceTypeRequest = mockData.putServicesByServiceTypeRequest


describe.only('GET /services/{{ServiceType}} with PARTICIPANT_LIST_LOCAL', () => {
it('handles a successful request', async () => {
// Arrange
forwardGetServicesServiceTypeRequestFromProviderService.mockResolvedValueOnce()
const expected = [
'/services/{{ServiceType}}',
'TP_CB_URL_SERVICES_PUT',
putServicesByServiceTypeRequest.headers,
'PUT',
getServicesByServiceTypeRequest.params.ServiceType,
putServicesByServiceTypeRequest.payload,
undefined
]

// Act
const response = await ServicesServiceTypeHandler.get(
null,
getServicesByServiceTypeRequest as unknown as Request,
mockResponseToolkit
)

// Assert
expect(response.statusCode).toBe(202)
expect(forwardGetServicesServiceTypeRequestFromProviderService).toHaveBeenCalledWith(...expected)
})
})
8 changes: 8 additions & 0 deletions test/unit/server/handlers/services/{ServiceType}.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,13 @@ optionally within square brackets <email>.
******/
import { Request } from '@hapi/hapi'
import Logger from '@mojaloop/central-services-logger'
import Config from '~/shared/config'

jest.mock('~/shared/config', () => ({
PARTICIPANT_LIST_SERVICE_URL: 'http://ml-testing-toolkit:5000',
PARTICIPANT_LIST_LOCAL: undefined
}));


import * as Services from '~/domain/services'
import { mockResponseToolkit } from 'test/unit/__mocks__/responseToolkit'
Expand Down Expand Up @@ -112,6 +119,7 @@ describe('ServicesServiceType handler', () => {
})
})


describe('PUT /services/{{ServiceType}}', () => {
beforeEach((): void => {
jest.clearAllMocks()
Expand Down
4 changes: 3 additions & 1 deletion test/unit/shared/logger.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,9 @@ import inspect from '~/shared/inspect'

// eslint-disable-next-line @typescript-eslint/explicit-function-return-type
jest.mock('@mojaloop/central-services-logger', () => ({
info: jest.fn()
info: jest.fn(),
warn: jest.fn(),
error: jest.fn(),
}))

describe('shared/logger', (): void => {
Expand Down