generated from UK-Export-Finance/nestjs-template
-
Notifications
You must be signed in to change notification settings - Fork 3
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
feat: add DnB behaviour ready for use in customers service
- Loading branch information
Nat Dean-Lewis
committed
Nov 27, 2024
1 parent
0075701
commit 4cf255c
Showing
18 changed files
with
358 additions
and
27 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,20 @@ | ||
import { registerAs } from '@nestjs/config'; | ||
import { DUN_AND_BRADSTREET } from '@ukef/constants'; | ||
import { getIntConfig } from '@ukef/helpers/get-int-config'; | ||
|
||
export interface DunAndBradstreetConfig { | ||
baseUrl: string; | ||
key: string; | ||
maxRedirects: number; | ||
timeout: number; | ||
} | ||
|
||
export default registerAs( | ||
DUN_AND_BRADSTREET.CONFIG.KEY, | ||
(): DunAndBradstreetConfig => ({ | ||
baseUrl: process.env.DUN_AND_BRADSTREET_URL, | ||
key: process.env.DUN_AND_BRADSTREET_KEY, | ||
maxRedirects: getIntConfig(process.env.DUN_AND_BRADSTREET_MAX_REDIRECTS, 5), | ||
timeout: getIntConfig(process.env.DUN_AND_BRADSTREET_TIMEOUT, 30000), | ||
}), | ||
); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,5 @@ | ||
export const DUN_AND_BRADSTREET = { | ||
CONFIG: { | ||
KEY: 'dunAndBradstreet', | ||
}, | ||
}; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
27 changes: 27 additions & 0 deletions
27
src/helper-modules/dun-and-bradstreet/dun-and-bradstreet.module.ts
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,27 @@ | ||
import { Module } from '@nestjs/common'; | ||
import { ConfigModule, ConfigService } from '@nestjs/config'; | ||
import { DunAndBradstreetConfig } from '@ukef/config/dun-and-bradstreet.config'; | ||
import { DUN_AND_BRADSTREET } from '@ukef/constants'; | ||
import { HttpModule } from '@ukef/modules/http/http.module'; | ||
|
||
import { DunAndBradstreetService } from './dun-and-bradstreet.service'; | ||
|
||
@Module({ | ||
imports: [ | ||
HttpModule.registerAsync({ | ||
imports: [ConfigModule], | ||
inject: [ConfigService], | ||
useFactory: (configService: ConfigService) => { | ||
const { baseUrl, maxRedirects, timeout } = configService.get<DunAndBradstreetConfig>(DUN_AND_BRADSTREET.CONFIG.KEY); | ||
return { | ||
baseURL: baseUrl, | ||
maxRedirects, | ||
timeout, | ||
}; | ||
}, | ||
}), | ||
], | ||
providers: [DunAndBradstreetService], | ||
exports: [DunAndBradstreetService], | ||
}) | ||
export class DunAndBradstreetModule {} |
112 changes: 112 additions & 0 deletions
112
src/helper-modules/dun-and-bradstreet/dun-and-bradstreet.service.test.ts
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,112 @@ | ||
import { HttpService } from '@nestjs/axios'; | ||
import { ConfigService } from '@nestjs/config'; | ||
import { resetAllWhenMocks, when } from 'jest-when'; | ||
import { of, throwError } from 'rxjs'; | ||
|
||
import { DunAndBradstreetService } from './dun-and-bradstreet.service'; | ||
import { DunAndBradstreetException } from './exception/dun-and-bradstreet.exception'; | ||
import { RandomValueGenerator } from '@ukef-test/support/generator/random-value-generator'; | ||
import { AxiosError } from 'axios'; | ||
|
||
describe('CompaniesHouseService', () => { | ||
let httpServiceGet: jest.Mock; | ||
let configServiceGet: jest.Mock; | ||
let service: DunAndBradstreetService; | ||
|
||
const valueGenerator = new RandomValueGenerator(); | ||
|
||
const testRegistrationNumber = '0' + valueGenerator.stringOfNumericCharacters({ length: 7 }); | ||
const expectedAccessToken = 'TEST_ACCESS_TOKEN'; | ||
const getAccessTokenMethodMock = jest | ||
.spyOn(DunAndBradstreetService.prototype as any, 'getAccessToken') | ||
.mockImplementation(() => Promise.resolve(expectedAccessToken)) | ||
|
||
const dunAndBradstreetpath = `/v1/match/cleanseMatch?countryISOAlpha2Code=GB®istrationNumber=${testRegistrationNumber}`; | ||
const expectedDunsNumber = "123456789" | ||
const getDunsNumberDunAndBradstreetResponse = { | ||
"matchCandidates": [ | ||
{ | ||
"organization": { | ||
"duns": expectedDunsNumber | ||
} | ||
} | ||
] | ||
} | ||
|
||
|
||
const expectedHttpServiceGetArguments: [string, object] = [ | ||
dunAndBradstreetpath, | ||
{ | ||
headers: { | ||
Authorization: `Bearer ${expectedAccessToken}`, | ||
}, | ||
}, | ||
]; | ||
|
||
const expectedHttpServiceGetResponse = of({ | ||
data: getDunsNumberDunAndBradstreetResponse, | ||
status: 200, | ||
statusText: 'OK', | ||
config: undefined, | ||
headers: undefined, | ||
}); | ||
|
||
beforeAll(() => { | ||
const httpService = new HttpService(); | ||
httpServiceGet = jest.fn(); | ||
httpService.get = httpServiceGet; | ||
|
||
const configService = new ConfigService(); | ||
configServiceGet = jest.fn().mockReturnValue({ key: "TEST API_KEY" }); | ||
configService.get = configServiceGet; | ||
|
||
service = new DunAndBradstreetService(httpService, configService); | ||
}); | ||
|
||
beforeEach(() => { | ||
resetAllWhenMocks(); | ||
}); | ||
|
||
afterEach(() => { | ||
jest.clearAllMocks(); | ||
}); | ||
|
||
describe('getDunAndBradstreetNumberByRegistrationNumber', () => { | ||
it('calls the Dun and Bradstreet API with the correct arguments', async () => { | ||
when(httpServiceGet) | ||
.calledWith(...expectedHttpServiceGetArguments) | ||
.mockReturnValueOnce(expectedHttpServiceGetResponse); | ||
|
||
await service.getDunAndBradstreetNumberByRegistrationNumber(testRegistrationNumber); | ||
|
||
expect(getAccessTokenMethodMock).toHaveBeenCalledTimes(1); | ||
expect(httpServiceGet).toHaveBeenCalledTimes(1); | ||
expect(httpServiceGet).toHaveBeenCalledWith(...expectedHttpServiceGetArguments); | ||
}); | ||
|
||
it('returns the results when the Dun and Bradstreet API returns a 200 response with results', async () => { | ||
when(httpServiceGet) | ||
.calledWith(...expectedHttpServiceGetArguments) | ||
.mockReturnValueOnce(expectedHttpServiceGetResponse); | ||
|
||
const response = await service.getDunAndBradstreetNumberByRegistrationNumber(testRegistrationNumber); | ||
|
||
expect(getAccessTokenMethodMock).toHaveBeenCalledTimes(1); | ||
expect(response).toBe(expectedDunsNumber); | ||
}); | ||
|
||
it('throws a DunAndBradstreetException if the Dun and Bradstreet API returns an unknown error response', async () => { | ||
const axiosError = new AxiosError(); | ||
when(httpServiceGet) | ||
.calledWith(...expectedHttpServiceGetArguments) | ||
.mockReturnValueOnce(throwError(() => axiosError)); | ||
|
||
const getDunsNumberPromise = service.getDunAndBradstreetNumberByRegistrationNumber(testRegistrationNumber); | ||
|
||
expect(getAccessTokenMethodMock).toHaveBeenCalledTimes(1); | ||
await expect(getDunsNumberPromise).rejects.toBeInstanceOf(DunAndBradstreetException); | ||
await expect(getDunsNumberPromise).rejects.toThrow('Failed to get response from Dun and Bradstreet API'); | ||
await expect(getDunsNumberPromise).rejects.toHaveProperty('innerError', axiosError); | ||
}); | ||
}); | ||
}); |
57 changes: 57 additions & 0 deletions
57
src/helper-modules/dun-and-bradstreet/dun-and-bradstreet.service.ts
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,57 @@ | ||
import { HttpService } from '@nestjs/axios'; | ||
import { Injectable } from '@nestjs/common'; | ||
import { ConfigService } from '@nestjs/config'; | ||
import { DUN_AND_BRADSTREET } from '@ukef/constants'; | ||
import { HttpClient } from '@ukef/modules/http/http.client'; | ||
|
||
import { DunAndBradstreetConfig } from '@ukef/config/dun-and-bradstreet.config'; | ||
import { createWrapDunAndBradstreetHttpGetErrorCallback } from './wrap-dun-and-bradstreet-http-error-callback'; | ||
|
||
@Injectable() | ||
export class DunAndBradstreetService { | ||
private readonly httpClient: HttpClient; | ||
private readonly encoded_key: string; | ||
|
||
|
||
constructor(httpService: HttpService, configService: ConfigService) { | ||
this.httpClient = new HttpClient(httpService); | ||
const { key } = configService.get<DunAndBradstreetConfig>(DUN_AND_BRADSTREET.CONFIG.KEY); | ||
this.encoded_key = key; | ||
} | ||
|
||
async getDunAndBradstreetNumberByRegistrationNumber(registrationNumber: string): Promise<string> { | ||
const path = `/v1/match/cleanseMatch?countryISOAlpha2Code=GB®istrationNumber=${registrationNumber}`; | ||
const access_token = await this.getAccessToken(); | ||
|
||
const { data } = await this.httpClient.get<any>({ | ||
path, | ||
headers: { | ||
'Authorization': 'Bearer ' + access_token, | ||
}, | ||
onError: createWrapDunAndBradstreetHttpGetErrorCallback({ | ||
messageForUnknownError: 'Failed to get response from Dun and Bradstreet API', | ||
knownErrors: [], | ||
}), | ||
}); | ||
return data?.matchCandidates[0]?.organization?.duns; | ||
} | ||
|
||
private async getAccessToken(): Promise<string> { | ||
const path = '/v3/token' | ||
const response = await this.httpClient.post<any, any>({ | ||
path, | ||
body: { | ||
'grant_type': 'client_credentials', | ||
}, | ||
headers: { | ||
'Authorization': 'Basic ' + this.encoded_key, | ||
'Content-Type': 'application/x-www-form-urlencoded', | ||
}, | ||
onError: createWrapDunAndBradstreetHttpGetErrorCallback({ | ||
messageForUnknownError: 'Failed to get access token', | ||
knownErrors: [], | ||
}), | ||
}) | ||
return response.data.access_token | ||
} | ||
} |
28 changes: 28 additions & 0 deletions
28
src/helper-modules/dun-and-bradstreet/exception/dun-and-bradstreet.exception.test.ts
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,28 @@ | ||
import { RandomValueGenerator } from '@ukef-test/support/generator/random-value-generator'; | ||
|
||
import { DunAndBradstreetException } from './dun-and-bradstreet.exception'; | ||
|
||
describe('CompaniesHouseException', () => { | ||
const valueGenerator = new RandomValueGenerator(); | ||
const message = valueGenerator.string(); | ||
|
||
it('exposes the message it was created with', () => { | ||
const exception = new DunAndBradstreetException(message); | ||
|
||
expect(exception.message).toBe(message); | ||
}); | ||
|
||
it('exposes the name of the exception', () => { | ||
const exception = new DunAndBradstreetException(message); | ||
|
||
expect(exception.name).toBe('DunAndBradstreetException'); | ||
}); | ||
|
||
it('exposes the inner error it was created with', () => { | ||
const innerError = new Error(); | ||
|
||
const exception = new DunAndBradstreetException(message, innerError); | ||
|
||
expect(exception.innerError).toBe(innerError); | ||
}); | ||
}); |
9 changes: 9 additions & 0 deletions
9
src/helper-modules/dun-and-bradstreet/exception/dun-and-bradstreet.exception.ts
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,9 @@ | ||
export class DunAndBradstreetException extends Error { | ||
constructor( | ||
message: string, | ||
public readonly innerError?: Error, | ||
) { | ||
super(message); | ||
this.name = this.constructor.name; | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,5 @@ | ||
import { AxiosError } from 'axios'; | ||
|
||
export type KnownErrors = KnownError[]; | ||
|
||
type KnownError = { checkHasError: (error: Error) => boolean; throwError: (error: AxiosError) => never }; |
17 changes: 17 additions & 0 deletions
17
src/helper-modules/dun-and-bradstreet/wrap-dun-and-bradstreet-http-error-callback.ts
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,17 @@ | ||
import { AxiosError } from 'axios'; | ||
import { ObservableInput, throwError } from 'rxjs'; | ||
|
||
import { DunAndBradstreetException } from './exception/dun-and-bradstreet.exception'; | ||
import { KnownErrors } from './known-errors'; | ||
|
||
type DunAndBradstreetHttpErrorCallback = (error: Error) => ObservableInput<never>; | ||
|
||
export const createWrapDunAndBradstreetHttpGetErrorCallback = | ||
({ messageForUnknownError, knownErrors }: { messageForUnknownError: string; knownErrors: KnownErrors }): DunAndBradstreetHttpErrorCallback => | ||
(error: Error) => { | ||
if (error instanceof AxiosError && error?.response) { | ||
knownErrors.forEach(({ checkHasError, throwError }) => checkHasError(error) && throwError(error)); | ||
} | ||
|
||
return throwError(() => new DunAndBradstreetException(messageForUnknownError, error)); | ||
}; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.