From d4fe3b3fc3c7042216e12767bdb79b0f8a7f10dd Mon Sep 17 00:00:00 2001 From: Abdalhamid Alhamad Date: Mon, 26 May 2025 16:34:09 +0300 Subject: [PATCH] feat: finish working on mocking inquire application api --- src/app.module.ts | 2 + src/common/modules/neoleap/__mocks__/index.ts | 1 + .../__mocks__/inquire-application.mock.ts | 91 ++++++++++++ .../neoleap/controllers/neotest.controller.ts | 14 ++ .../modules/neoleap/dtos/response/index.ts | 1 + .../response/inquire-application.response.ts | 133 ++++++++++++++++++ .../modules/neoleap/interfaces/index.ts | 1 + .../inquire-application.request.interface.ts | 22 +++ src/common/modules/neoleap/neoleap.module.ts | 9 +- .../neoleap/services/neoleap.service.ts | 49 ++++++- 10 files changed, 318 insertions(+), 5 deletions(-) create mode 100644 src/common/modules/neoleap/__mocks__/index.ts create mode 100644 src/common/modules/neoleap/__mocks__/inquire-application.mock.ts create mode 100644 src/common/modules/neoleap/controllers/neotest.controller.ts create mode 100644 src/common/modules/neoleap/dtos/response/index.ts create mode 100644 src/common/modules/neoleap/dtos/response/inquire-application.response.ts create mode 100644 src/common/modules/neoleap/interfaces/inquire-application.request.interface.ts diff --git a/src/app.module.ts b/src/app.module.ts index 4c2b94b..8b9bc17 100644 --- a/src/app.module.ts +++ b/src/app.module.ts @@ -12,6 +12,7 @@ import { AllowanceModule } from './allowance/allowance.module'; import { AuthModule } from './auth/auth.module'; import { CacheModule } from './common/modules/cache/cache.module'; import { LookupModule } from './common/modules/lookup/lookup.module'; +import { NeoLeapModule } from './common/modules/neoleap/neoleap.module'; import { NotificationModule } from './common/modules/notification/notification.module'; import { OtpModule } from './common/modules/otp/otp.module'; import { AllExceptionsFilter, buildI18nValidationExceptionFilter } from './core/filters'; @@ -80,6 +81,7 @@ import { UserModule } from './user/user.module'; UserModule, CronModule, + NeoLeapModule, ], providers: [ // Global Pipes diff --git a/src/common/modules/neoleap/__mocks__/index.ts b/src/common/modules/neoleap/__mocks__/index.ts new file mode 100644 index 0000000..653007b --- /dev/null +++ b/src/common/modules/neoleap/__mocks__/index.ts @@ -0,0 +1 @@ +export * from './inquire-application.mock'; diff --git a/src/common/modules/neoleap/__mocks__/inquire-application.mock.ts b/src/common/modules/neoleap/__mocks__/inquire-application.mock.ts new file mode 100644 index 0000000..3a74ca7 --- /dev/null +++ b/src/common/modules/neoleap/__mocks__/inquire-application.mock.ts @@ -0,0 +1,91 @@ +export const INQUIRE_APPLICATION_MOCK = { + ResponseHeader: { + Version: '1.0.0', + MsgUid: 'stringstringstringstringstringstring', + Source: 'string', + ServiceId: 'string', + ReqDateTime: '2025-05-26T10:23:13.812Z', + RspDateTime: '2025-05-26T10:23:13.812Z', + ResponseCode: 'string', + ResponseType: 'Validation Error', + ProcessingTime: 0, + EncryptionKey: 'string', + ResponseDescription: 'string', + LocalizedResponseDescription: { + Locale: 'string', + LocalizedDescription: 'string', + }, + CustomerSpecificResponseDescriptionList: [ + { + Locale: 'string', + ResponseDescription: 'string', + }, + ], + HeaderUserDataList: [ + { + Tag: 'string', + Value: 'string', + }, + ], + }, + + InquireApplicationResponseDetails: { + InstitutionCode: 'stri', + ApplicationTypeDetails: { + TypeCode: 'st', + Description: 'string', + Additional: true, + Corporate: true, + UserData: 'string', + }, + ApplicationDetails: { + ApplicationNumber: 'string', + ExternalApplicationNumber: 'string', + ApplicationStatus: 'st', + Organization: 0, + Product: 'stri', + ApplicatonDate: '2025-05-26', + ApplicationSource: 's', + SalesSource: 'string', + DeliveryMethod: 's', + ProgramCode: 'string', + Campaign: 'string', + Plastic: 'string', + Design: 'string', + ProcessStage: 'st', + ProcessStageStatus: 's', + Score: 'string', + ExternalScore: 'string', + RequestedLimit: 0, + SuggestedLimit: 0, + AssignedLimit: 0, + AllowedLimitList: [ + { + CreditLimit: 0, + EvaluationCode: 'string', + }, + ], + EligibilityCheckResult: 'string', + EligibilityCheckDescription: 'string', + Title: 'string', + FirstName: 'string', + SecondName: 'string', + ThirdName: 'string', + LastName: 'string', + FullName: 'string', + EmbossName: 'string', + PlaceOfBirth: 'string', + DateOfBirth: '2025-05-26', + LocalizedDateOfBirth: '2025-05-26', + Age: 20, + Gender: 'M', + Married: 'S', + Nationality: 'str', + IdType: 'st', + IdNumber: 'string', + IdExpiryDate: '2025-05-26', + EducationLevel: 'stri', + ProfessionCode: 0, + }, + }, +}; diff --git a/src/common/modules/neoleap/controllers/neotest.controller.ts b/src/common/modules/neoleap/controllers/neotest.controller.ts new file mode 100644 index 0000000..9505f2f --- /dev/null +++ b/src/common/modules/neoleap/controllers/neotest.controller.ts @@ -0,0 +1,14 @@ +import { Controller, Get } from '@nestjs/common'; +import { ApiTags } from '@nestjs/swagger'; +import { NeoLeapService } from '../services/neoleap.service'; + +@Controller('neotest') +@ApiTags('NeoTest') +export class NeoTestController { + constructor(private readonly neoleapService: NeoLeapService) {} + + @Get('inquire-application') + async inquireApplication() { + return this.neoleapService.inquireApplication('1234567890'); + } +} diff --git a/src/common/modules/neoleap/dtos/response/index.ts b/src/common/modules/neoleap/dtos/response/index.ts new file mode 100644 index 0000000..6a8d1fe --- /dev/null +++ b/src/common/modules/neoleap/dtos/response/index.ts @@ -0,0 +1 @@ +export * from './inquire-application.response'; diff --git a/src/common/modules/neoleap/dtos/response/inquire-application.response.ts b/src/common/modules/neoleap/dtos/response/inquire-application.response.ts new file mode 100644 index 0000000..96ce358 --- /dev/null +++ b/src/common/modules/neoleap/dtos/response/inquire-application.response.ts @@ -0,0 +1,133 @@ +import { Expose, Transform } from 'class-transformer'; + +export class InquireApplicationResponse { + @Transform(({ obj }) => obj.ApplicationDetails?.ApplicationNumber) + @Expose() + applicationNumber!: string; + + @Transform(({ obj }) => obj.ApplicationDetails?.ExternalApplicationNumber) + @Expose() + externalApplicationNumber!: string; + + @Transform(({ obj }) => obj.ApplicationDetails?.ApplicationStatus) + @Expose() + applicationStatus!: string; + + @Transform(({ obj }) => obj.ApplicationDetails?.Organization) + @Expose() + organization!: number; + + @Transform(({ obj }) => obj.ApplicationDetails?.Product) + @Expose() + product!: string; + + @Transform(({ obj }) => obj.ApplicationDetails?.ApplicatonDate) + @Expose() + applicationDate!: string; + @Transform(({ obj }) => obj.ApplicationDetails?.ApplicationSource) + @Expose() + applicationSource!: string; + + @Transform(({ obj }) => obj.ApplicationDetails?.SalesSource) + @Expose() + salesSource!: string; + + @Transform(({ obj }) => obj.ApplicationDetails?.DeliveryMethod) + @Expose() + deliveryMethod!: string; + + @Transform(({ obj }) => obj.ApplicationDetails?.ProgramCode) + @Expose() + ProgramCode!: string; + + @Transform(({ obj }) => obj.ApplicationDetails?.Plastic) + @Expose() + plastic!: string; + + @Transform(({ obj }) => obj.ApplicationDetails?.Design) + @Expose() + design!: string; + + @Transform(({ obj }) => obj.ApplicationDetails?.ProcessStage) + @Expose() + processStage!: string; + + @Transform(({ obj }) => obj.ApplicationDetails?.ProcessStageStatus) + @Expose() + processStageStatus!: string; + + @Transform(({ obj }) => obj.ApplicationDetails?.EligibilityCheckResult) + @Expose() + eligibilityCheckResult!: string; + @Transform(({ obj }) => obj.ApplicationDetails?.EligibilityCheckDescription) + @Expose() + eligibilityCheckDescription!: string; + + @Transform(({ obj }) => obj.ApplicationDetails?.Title) + @Expose() + title!: string; + + @Transform(({ obj }) => obj.ApplicationDetails?.FirstName) + @Expose() + firstName!: string; + + @Transform(({ obj }) => obj.ApplicationDetails?.SecondName) + @Expose() + secondName!: string; + + @Transform(({ obj }) => obj.ApplicationDetails?.ThirdName) + @Expose() + thirdName!: string; + + @Transform(({ obj }) => obj.ApplicationDetails?.LastName) + @Expose() + lastName!: string; + + @Transform(({ obj }) => obj.ApplicationDetails?.FullName) + @Expose() + fullName!: string; + + @Transform(({ obj }) => obj.ApplicationDetails?.EmbossName) + @Expose() + embossName!: string; + + @Transform(({ obj }) => obj.ApplicationDetails?.PlaceOfBirth) + @Expose() + placeOfBirth!: string; + + @Transform(({ obj }) => obj.ApplicationDetails?.DateOfBirth) + @Expose() + dateOfBirth!: string; + + @Transform(({ obj }) => obj.ApplicationDetails?.LocalizedDateOfBirth) + @Expose() + localizedDateOfBirth!: string; + + @Transform(({ obj }) => obj.ApplicationDetails?.Age) + @Expose() + age!: number; + + @Transform(({ obj }) => obj.ApplicationDetails?.Gender) + @Expose() + gender!: string; + + @Transform(({ obj }) => obj.ApplicationDetails?.Married) + @Expose() + married!: string; + + @Transform(({ obj }) => obj.ApplicationDetails.Nationality) + @Expose() + nationality!: string; + + @Transform(({ obj }) => obj.ApplicationDetails.IdType) + @Expose() + idType!: string; + + @Transform(({ obj }) => obj.ApplicationDetails.IdNumber) + @Expose() + idNumber!: string; + + @Transform(({ obj }) => obj.ApplicationDetails.IdExpiryDate) + @Expose() + idExpiryDate!: string; +} diff --git a/src/common/modules/neoleap/interfaces/index.ts b/src/common/modules/neoleap/interfaces/index.ts index 97472ef..e6757c9 100644 --- a/src/common/modules/neoleap/interfaces/index.ts +++ b/src/common/modules/neoleap/interfaces/index.ts @@ -1,2 +1,3 @@ export * from './create-application.request.interface'; +export * from './inquire-application.request.interface'; export * from './neoleap-header.request.interface'; diff --git a/src/common/modules/neoleap/interfaces/inquire-application.request.interface.ts b/src/common/modules/neoleap/interfaces/inquire-application.request.interface.ts new file mode 100644 index 0000000..866c61b --- /dev/null +++ b/src/common/modules/neoleap/interfaces/inquire-application.request.interface.ts @@ -0,0 +1,22 @@ +import { INeoleapHeaderRequest } from './neoleap-header.request.interface'; + +export interface IInquireApplicationRequest extends INeoleapHeaderRequest { + InquireApplicationRequestDetails: { + ApplicationIdentifier: { + InstitutionCode: string; + ExternalApplicationNumber: string; + }; + AdditionalData?: { + ReturnApplicationType?: boolean; + ReturnApplicationStatus?: boolean; + ReturnAddresses?: boolean; + ReturnBranch?: boolean; + ReturnHistory?: boolean; + ReturnCard?: boolean; + ReturnCustomer?: boolean; + ReturnAccount?: boolean; + ReturnDirectDebitDetails?: boolean; + }; + HistoryTypeFilterList?: number[]; + }; +} diff --git a/src/common/modules/neoleap/neoleap.module.ts b/src/common/modules/neoleap/neoleap.module.ts index c68989f..e3563db 100644 --- a/src/common/modules/neoleap/neoleap.module.ts +++ b/src/common/modules/neoleap/neoleap.module.ts @@ -1,8 +1,11 @@ +import { HttpModule } from '@nestjs/axios'; import { Module } from '@nestjs/common'; +import { NeoTestController } from './controllers/neotest.controller'; +import { NeoLeapService } from './services/neoleap.service'; @Module({ - imports: [], - controllers: [], - providers: [], + imports: [HttpModule], + controllers: [NeoTestController], + providers: [NeoLeapService], }) export class NeoLeapModule {} diff --git a/src/common/modules/neoleap/services/neoleap.service.ts b/src/common/modules/neoleap/services/neoleap.service.ts index ac10e07..950d231 100644 --- a/src/common/modules/neoleap/services/neoleap.service.ts +++ b/src/common/modules/neoleap/services/neoleap.service.ts @@ -1,11 +1,14 @@ import { HttpService } from '@nestjs/axios'; import { Injectable } from '@nestjs/common'; import { ConfigService } from '@nestjs/config'; +import { plainToInstance } from 'class-transformer'; import moment from 'moment'; import { v4 as uuid } from 'uuid'; import { CountriesNumericISO } from '~/common/constants'; import { Customer } from '~/customer/entities'; import { Gender } from '~/customer/enums'; +import { INQUIRE_APPLICATION_MOCK } from '../__mocks__/'; +import { InquireApplicationResponse } from '../dtos/response'; import { ICreateApplicationRequest, INeoleapHeaderRequest } from '../interfaces'; @Injectable() export class NeoLeapService { @@ -55,7 +58,7 @@ export class NeoLeapService { Title: customer.gender === Gender.MALE ? 'Mr' : 'Ms', Gender: customer.gender === Gender.MALE ? 'M' : 'F', LocalizedDateOfBirth: moment(customer.dateOfBirth).format('YYYY-MM-DD'), - Nationality: '682', + Nationality: CountriesNumericISO[customer.countryOfResidence], }, ApplicationAddress: { City: customer.city, @@ -75,7 +78,7 @@ export class NeoLeapService { ...this.prepareHeaders('CreateNewApplication'), }; - const response = await this.httpService.axiosRef.post(`${this.baseUrl}/create-application`, payload, { + const response = await this.httpService.axiosRef.post(`${this.baseUrl}/application/CreateNewApplication`, payload, { headers: { 'Content-Type': 'application/json', Authorization: `${this.apiKey}`, @@ -85,6 +88,48 @@ export class NeoLeapService { //@TODO handle response } + async inquireApplication(externalApplicationNumber: string) { + const responseKey = 'InquireApplicationResponseDetails'; + if (this.useMock) { + return plainToInstance(InquireApplicationResponse, INQUIRE_APPLICATION_MOCK[responseKey], { + excludeExtraneousValues: true, + }); + } + + const payload = { + InquireApplicationRequestDetails: { + ApplicationIdentifier: { + InstitutionCode: this.institutionCode, + ExternalApplicationNumber: externalApplicationNumber, + }, + AdditionalData: { + ReturnApplicationType: true, + ReturnApplicationStatus: true, + ReturnCard: true, + ReturnCustomer: true, + }, + }, + ...this.prepareHeaders('InquireApplication'), + }; + + const response = await this.httpService.axiosRef.post(`${this.baseUrl}/application/InquireApplication`, payload, { + headers: { + 'Content-Type': 'application/json', + Authorization: `${this.apiKey}`, + }, + }); + + if (response.data?.[responseKey]) { + return plainToInstance(InquireApplicationResponse, response.data[responseKey], { + excludeExtraneousValues: true, + }); + } else { + throw new Error('Invalid response from NeoLeap API'); + } + + //@TODO handle response + } + private prepareHeaders(serviceName: string): INeoleapHeaderRequest { return { RequestHeader: {