From d62d4785001681434e6b1b95592b72f338ce0311 Mon Sep 17 00:00:00 2001 From: Len Bekker Date: Thu, 24 Oct 2024 11:46:43 +0200 Subject: [PATCH 1/6] feat(deps): use latest datacache from frms-coe-lib --- package-lock.json | 8 ++++---- package.json | 2 +- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/package-lock.json b/package-lock.json index 732776c..308e08e 100644 --- a/package-lock.json +++ b/package-lock.json @@ -14,7 +14,7 @@ "@fastify/swagger": "^8.8.0", "@fastify/swagger-ui": "^3.0.0", "@tazama-lf/auth-lib": "^0.0.9", - "@tazama-lf/frms-coe-lib": "^5.0.0-rc.3", + "@tazama-lf/frms-coe-lib": "^5.0.0-rc.4", "@tazama-lf/frms-coe-startup-lib": "^2.3.0-rc.4", "ajv": "^8.16.0", "arangojs": "^8.8.0", @@ -1820,9 +1820,9 @@ } }, "node_modules/@tazama-lf/frms-coe-lib": { - "version": "5.0.0-rc.3", - "resolved": "https://npm.pkg.github.com/download/@tazama-lf/frms-coe-lib/5.0.0-rc.3/fbd8578b5da559c6f2cbd95d8d1d8b65c63a181f", - "integrity": "sha512-xlG6RSXdt/WwM13LGwveFaulxl9sd4/zRSx7zbRTPpZxEcDRBP0sTEceVt4EQKb5Ct8PHqfuKu37t7R1XrWxdQ==", + "version": "5.0.0-rc.4", + "resolved": "https://npm.pkg.github.com/download/@tazama-lf/frms-coe-lib/5.0.0-rc.4/9df42df29b5f3039428872d49a9b47e056cd77de", + "integrity": "sha512-yvf5uI4UU+QVWQtC4LDwGCFuPKVkkLqk4F9ghmENeDS3ybDoV8GulnfBUMqw3BAB+iDkFS3mtz7hR6B4ZdpTVg==", "license": "Apache-2.0", "dependencies": { "@elastic/ecs-pino-format": "^1.5.0", diff --git a/package.json b/package.json index b9dae05..3d721a6 100644 --- a/package.json +++ b/package.json @@ -41,7 +41,7 @@ "@fastify/swagger": "^8.8.0", "@fastify/swagger-ui": "^3.0.0", "@tazama-lf/auth-lib": "^0.0.9", - "@tazama-lf/frms-coe-lib": "^5.0.0-rc.3", + "@tazama-lf/frms-coe-lib": "^5.0.0-rc.4", "@tazama-lf/frms-coe-startup-lib": "^2.3.0-rc.4", "ajv": "^8.16.0", "arangojs": "^8.8.0", From 0d7053572421ef0b6807501aa096f9f1a9f80a7f Mon Sep 17 00:00:00 2001 From: Len Bekker Date: Thu, 24 Oct 2024 11:56:43 +0200 Subject: [PATCH 2/6] feat: add xchgRate and use multi currency datacache --- src/index.ts | 2 +- src/logic.service.ts | 28 +++++++++++++++++++++------- src/schemas/pacs.008.json | 35 +++++------------------------------ src/schemas/pain.001.json | 3 +++ 4 files changed, 30 insertions(+), 38 deletions(-) diff --git a/src/index.ts b/src/index.ts index e2cc57f..8e31abc 100644 --- a/src/index.ts +++ b/src/index.ts @@ -82,7 +82,7 @@ if (cluster.isPrimary && configuration.maxCPU !== 1) { await runServer(); } } catch (err) { - loggerService.error(`Error while starting NATS server on Worker ${process.pid}`, err); + loggerService.error(`Error while starting ${configuration.functionName}`, err); process.exit(1); } })(); diff --git a/src/logic.service.ts b/src/logic.service.ts index 91e4a5a..368cd75 100644 --- a/src/logic.service.ts +++ b/src/logic.service.ts @@ -223,7 +223,11 @@ export const handlePacs008 = async (transaction: Pacs008, transactionType: strin const TxTp = transactionType; transaction.TxTp = TxTp; - const Amt = transaction.FIToFICstmrCdtTrf.CdtTrfTxInf.InstdAmt.Amt.Amt; + const InstdAmt = transaction.FIToFICstmrCdtTrf.CdtTrfTxInf.InstdAmt.Amt.Amt; + const InstdAmtCcy = transaction.FIToFICstmrCdtTrf.CdtTrfTxInf.InstdAmt.Amt.Ccy; + const IntrBkSttlmAmt = transaction.FIToFICstmrCdtTrf.CdtTrfTxInf.IntrBkSttlmAmt.Amt.Amt; + const IntrBkSttlmAmtCcy = transaction.FIToFICstmrCdtTrf.CdtTrfTxInf.IntrBkSttlmAmt.Amt.Ccy; + const XchgRate = transaction.FIToFICstmrCdtTrf.CdtTrfTxInf.XchgRate; const Ccy = transaction.FIToFICstmrCdtTrf.CdtTrfTxInf.InstdAmt.Amt.Ccy; const creDtTm = transaction.FIToFICstmrCdtTrf.GrpHdr.CreDtTm; const EndToEndId = transaction.FIToFICstmrCdtTrf.CdtTrfTxInf.PmtId.EndToEndId; @@ -246,7 +250,7 @@ export const handlePacs008 = async (transaction: Pacs008, transactionType: strin const transactionRelationship: TransactionRelationship = { from: `accounts/${debtorAcctId}`, to: `accounts/${creditorAcctId}`, - Amt, + Amt: InstdAmt, Ccy, CreDtTm: creDtTm, EndToEndId, @@ -263,17 +267,22 @@ export const handlePacs008 = async (transaction: Pacs008, transactionType: strin cdtrAcctId: creditorAcctId, dbtrAcctId: debtorAcctId, creDtTm, - amt: { - amt: parseFloat(Amt), - ccy: Ccy, + instdAmt: { + amt: parseFloat(InstdAmt), + ccy: InstdAmtCcy, + }, + intrBkSttlmAmt: { + amt: parseFloat(IntrBkSttlmAmt), + ccy: IntrBkSttlmAmtCcy, }, + xchgRate: XchgRate, }; transaction.DataCache = dataCache; const cacheBuffer = createMessageBuffer({ DataCache: { ...dataCache } }); if (cacheBuffer) { const redisTTL = configuration.redisConfig.distributedCacheTTL; - accountInserts.push(cacheDatabaseManager.set(EndToEndId, cacheBuffer, redisTTL ? Number(redisTTL) : 0)); + pendingPromises.push(cacheDatabaseManager.set(EndToEndId, cacheBuffer, redisTTL ? Number(redisTTL) : 0)); } else { // this is fatal throw new Error('[pacs008] data cache could not be serialised'); @@ -459,10 +468,15 @@ export const rebuildCache = async (endToEndId: string, writeToRedis: boolean, id cdtrAcctId: cdtTrfTxInf.CdtrAcct.Id.Othr[0].Id, dbtrAcctId: cdtTrfTxInf.DbtrAcct.Id.Othr[0].Id, creDtTm: pacs008.FIToFICstmrCdtTrf.GrpHdr.CreDtTm, - amt: { + instdAmt: { amt: parseFloat(cdtTrfTxInf.InstdAmt.Amt.Amt), ccy: cdtTrfTxInf.InstdAmt.Amt.Ccy, }, + intrBkSttlmAmt: { + amt: parseFloat(cdtTrfTxInf.IntrBkSttlmAmt.Amt.Amt), + ccy: cdtTrfTxInf.IntrBkSttlmAmt.Amt.Ccy, + }, + xchgRate: cdtTrfTxInf.XchgRate, }; if (writeToRedis) { diff --git a/src/schemas/pacs.008.json b/src/schemas/pacs.008.json index 77e872c..5b37c7c 100644 --- a/src/schemas/pacs.008.json +++ b/src/schemas/pacs.008.json @@ -79,6 +79,9 @@ }, "required": ["Amt"] }, + "XchgRate": { + "type": "string" + }, "ChrgBr": { "type": "string" }, @@ -501,36 +504,8 @@ "properties": { "Envlp": { "type": "object", - "properties": { - "Doc": { - "type": "object", - "properties": { - "InitgPty": { - "type": "object", - "properties": { - "Glctn": { - "type": "object", - "properties": { - "Lat": { - "type": "string" - }, - "Long": { - "type": "string" - } - }, - "required": ["Lat", "Long"] - } - }, - "required": ["Glctn"] - }, - "Xprtn": { - "type": "string" - } - }, - "required": ["InitgPty", "Xprtn"] - } - }, - "required": ["Doc"] + "properties": {}, + "required": [] } }, "required": ["Envlp"] diff --git a/src/schemas/pain.001.json b/src/schemas/pain.001.json index c79854b..73dfa6d 100644 --- a/src/schemas/pain.001.json +++ b/src/schemas/pain.001.json @@ -318,6 +318,9 @@ }, "CcyOfTrf": { "type": "string" + }, + "XchgRate": { + "type": "string" } }, "required": ["Amt", "CcyOfTrf"] From 03dd316d9aadd0846c2aadfa79ac3e80f49493bd Mon Sep 17 00:00:00 2001 From: Len Bekker Date: Thu, 24 Oct 2024 11:57:59 +0200 Subject: [PATCH 3/6] refactor(rename): use better naming for pending promises array --- src/logic.service.ts | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/src/logic.service.ts b/src/logic.service.ts index 368cd75..bdc476f 100644 --- a/src/logic.service.ts +++ b/src/logic.service.ts @@ -259,7 +259,7 @@ export const handlePacs008 = async (transaction: Pacs008, transactionType: strin TxTp, }; - const accountInserts = [cacheDatabaseManager.addAccount(debtorAcctId), cacheDatabaseManager.addAccount(creditorAcctId)]; + const pendingPromises = [cacheDatabaseManager.addAccount(debtorAcctId), cacheDatabaseManager.addAccount(creditorAcctId)]; const dataCache: DataCache = { cdtrId: creditorId, @@ -289,17 +289,17 @@ export const handlePacs008 = async (transaction: Pacs008, transactionType: strin } if (!configuration.QUOTING) { - accountInserts.push(cacheDatabaseManager.addEntity(creditorId, creDtTm)); - accountInserts.push(cacheDatabaseManager.addEntity(debtorId, creDtTm)); + pendingPromises.push(cacheDatabaseManager.addEntity(creditorId, creDtTm)); + pendingPromises.push(cacheDatabaseManager.addEntity(debtorId, creDtTm)); - await Promise.all(accountInserts); + await Promise.all(pendingPromises); await Promise.all([ cacheDatabaseManager.addAccountHolder(creditorId, creditorAcctId, creDtTm), cacheDatabaseManager.addAccountHolder(debtorId, debtorAcctId, creDtTm), ]); } else { - await Promise.all(accountInserts); + await Promise.all(pendingPromises); } cacheDatabaseManager.saveTransactionRelationship(transactionRelationship); From b3d8b4ae15cb1c89be95b1e1df87990034813991 Mon Sep 17 00:00:00 2001 From: Len Bekker Date: Thu, 24 Oct 2024 13:46:52 +0200 Subject: [PATCH 4/6] test: update datacache tests --- __tests__/unit/app.test.ts | 158 +++++++++++++++++++++++++++++++++++-- 1 file changed, 151 insertions(+), 7 deletions(-) diff --git a/__tests__/unit/app.test.ts b/__tests__/unit/app.test.ts index 9d2f544..3c05742 100644 --- a/__tests__/unit/app.test.ts +++ b/__tests__/unit/app.test.ts @@ -1,16 +1,18 @@ // SPDX-License-Identifier: Apache-2.0 /* eslint-disable @typescript-eslint/no-explicit-any */ +import * as protobuf from '@tazama-lf/frms-coe-lib/lib/helpers/protobuf'; import { Pacs002, Pacs008, Pain001, Pain013 } from '@tazama-lf/frms-coe-lib/lib/interfaces'; -import { cacheDatabaseManager, dbInit, runServer, server } from '../../src/index'; -import * as LogicService from '../../src/logic.service'; -import { configuration } from '../../src/'; -import { CacheDatabaseClientMocks, DatabaseManagerMocks } from '@tazama-lf/frms-coe-lib/lib/tests/mocks/mock-transactions'; import { Pacs002Sample, Pacs008Sample, Pain001Sample, Pain013Sample } from '@tazama-lf/frms-coe-lib/lib/tests/data'; +import { CacheDatabaseClientMocks, DatabaseManagerMocks } from '@tazama-lf/frms-coe-lib/lib/tests/mocks/mock-transactions'; +import { configuration } from '../../src/'; +import { cacheDatabaseManager, dbInit, loggerService, runServer, server } from '../../src/index'; +import * as LogicService from '../../src/logic.service'; jest.mock('@tazama-lf/frms-coe-lib/lib/config/processor.config', () => ({ validateProcessorConfig: jest.fn().mockReturnValue({ functionName: 'test-ed', nodeEnv: 'test', + maxCPU: 1, }), })); @@ -160,6 +162,24 @@ describe('App Controller & Logic Service', () => { expect(handleSpy).toHaveReturned(); }); + it('should pacs.008, createMessageBuffer undefined', async () => { + const request = Pacs008Sample as Pacs008; + + const handleSpy = jest.spyOn(LogicService, 'handlePacs008'); + + jest.spyOn(protobuf, 'createMessageBuffer').mockImplementationOnce(() => undefined); + + try { + await LogicService.handlePacs008(request, 'pacs.008.001.10'); + + expect(true).toStrictEqual(false); //unreachable + } catch (err) { + expect(handleSpy).toHaveBeenCalledTimes(1); + expect(handleSpy).toHaveReturned(); + expect(err).toStrictEqual(new Error('[pacs008] data cache could not be serialised')); + } + }); + it('should handle pacs.008, database error', async () => { jest .spyOn(cacheDatabaseManager, 'saveTransactionHistory') @@ -182,12 +202,13 @@ describe('App Controller & Logic Service', () => { describe('handlePacs.008, quoting enabled', () => { it('should handle pacs.008', async () => { - configuration.QUOTING = false; + configuration.QUOTING = true; const request = Pacs008Sample as Pacs008; const handleSpy = jest.spyOn(LogicService, 'handlePacs008'); await LogicService.handlePacs008(request, 'pacs.008.001.10'); + expect(configuration.QUOTING).toStrictEqual(true); expect(handleSpy).toHaveBeenCalledTimes(1); expect(handleSpy).toHaveReturned(); configuration.QUOTING = false; @@ -199,21 +220,56 @@ describe('App Controller & Logic Service', () => { jest.spyOn(cacheDatabaseManager, 'getTransactionPacs008').mockImplementation((EndToEndId: string) => { return Promise.resolve( JSON.parse( - '[[{"TxTp":"pacs.008.001.10","FIToFICstmrCdtTrf":{"GrpHdr":{"MsgId":"cabb-32c3-4ecf-944e-654855c80c38","CreDtTm":"2023-02-03T07:17:52.216Z","NbOfTxs":1,"SttlmInf":{"SttlmMtd":"CLRG"}},"CdtTrfTxInf":{"PmtId":{"InstrId":"4ca819baa65d4a2c9e062f2055525046","EndToEndId":"701b-ae14-46fd-a2cf-88dda2875fdd"},"IntrBkSttlmAmt":{"Amt":{"Amt":31020.89,"Ccy":"USD"}},"InstdAmt":{"Amt":{"Amt":9000,"Ccy":"ZAR"}},"ChrgBr":"DEBT","ChrgsInf":{"Amt":{"Amt":307.14,"Ccy":"USD"},"Agt":{"FinInstnId":{"ClrSysMmbId":{"MmbId":"typology003"}}}},"InitgPty":{"Nm":"April Blake Grant","Id":{"PrvtId":{"DtAndPlcOfBirth":{"BirthDt":"1968-02-01","CityOfBirth":"Unknown","CtryOfBirth":"ZZ"},"Othr":[{"Id":"+01-710694778","SchmeNm":{"Prtry":"MSISDN"}}]}},"CtctDtls":{"MobNb":"+01-710694778"}},"Dbtr":{"Nm":"April Blake Grant","Id":{"PrvtId":{"DtAndPlcOfBirth":{"BirthDt":"1968-02-01","CityOfBirth":"Unknown","CtryOfBirth":"ZZ"},"Othr":[{"Id":"+01-710694778","SchmeNm":{"Prtry":"MSISDN"}}]}},"CtctDtls":{"MobNb":"+01-710694778"}},"DbtrAcct":{"Id":{"Othr":[{"Id":"+01-710694778","SchmeNm":{"Prtry":"MSISDN"}}]},"Nm":"April Grant"},"DbtrAgt":{"FinInstnId":{"ClrSysMmbId":{"MmbId":"typology003"}}},"CdtrAgt":{"FinInstnId":{"ClrSysMmbId":{"MmbId":"dfsp002"}}},"Cdtr":{"Nm":"Felicia Easton Quill","Id":{"PrvtId":{"DtAndPlcOfBirth":{"BirthDt":"1935-05-08","CityOfBirth":"Unknown","CtryOfBirth":"ZZ"},"Othr":[{"Id":"+07-197368463","SchmeNm":{"Prtry":"MSISDN"}}]}},"CtctDtls":{"MobNb":"+07-197368463"}},"CdtrAcct":{"Id":{"Othr":[{"Id":"+07-197368463","SchmeNm":{"Prtry":"MSISDN"}}]},"Nm":"Felicia Quill"},"Purp":{"Cd":"MP2P"}},"RgltryRptg":{"Dtls":{"Tp":"BALANCE OF PAYMENTS","Cd":"100"}},"RmtInf":{"Ustrd":"Payment of USD 30713.75 from April to Felicia"},"SplmtryData":{"Envlp":{"Doc":{"Xprtn":"2023-02-03T07:17:52.216Z"}}}}}]]', + '[[{"TxTp":"pacs.008.001.10","FIToFICstmrCdtTrf":{"GrpHdr":{"MsgId":"cabb-32c3-4ecf-944e-654855c80c38","CreDtTm":"2023-02-03T07:17:52.216Z","NbOfTxs":1,"SttlmInf":{"SttlmMtd":"CLRG"}},"CdtTrfTxInf":{"PmtId":{"InstrId":"4ca819baa65d4a2c9e062f2055525046","EndToEndId":"701b-ae14-46fd-a2cf-88dda2875fdd"},"IntrBkSttlmAmt":{"Amt":{"Amt":31020.89,"Ccy":"USD"}},"InstdAmt":{"Amt":{"Amt":9000,"Ccy":"ZAR"}},"xchgRate":17.536082,"ChrgBr":"DEBT","ChrgsInf":{"Amt":{"Amt":307.14,"Ccy":"USD"},"Agt":{"FinInstnId":{"ClrSysMmbId":{"MmbId":"typology003"}}}},"InitgPty":{"Nm":"April Blake Grant","Id":{"PrvtId":{"DtAndPlcOfBirth":{"BirthDt":"1968-02-01","CityOfBirth":"Unknown","CtryOfBirth":"ZZ"},"Othr":[{"Id":"+01-710694778","SchmeNm":{"Prtry":"MSISDN"}}]}},"CtctDtls":{"MobNb":"+01-710694778"}},"Dbtr":{"Nm":"April Blake Grant","Id":{"PrvtId":{"DtAndPlcOfBirth":{"BirthDt":"1968-02-01","CityOfBirth":"Unknown","CtryOfBirth":"ZZ"},"Othr":[{"Id":"+01-710694778","SchmeNm":{"Prtry":"MSISDN"}}]}},"CtctDtls":{"MobNb":"+01-710694778"}},"DbtrAcct":{"Id":{"Othr":[{"Id":"+01-710694778","SchmeNm":{"Prtry":"MSISDN"}}]},"Nm":"April Grant"},"DbtrAgt":{"FinInstnId":{"ClrSysMmbId":{"MmbId":"typology003"}}},"CdtrAgt":{"FinInstnId":{"ClrSysMmbId":{"MmbId":"dfsp002"}}},"Cdtr":{"Nm":"Felicia Easton Quill","Id":{"PrvtId":{"DtAndPlcOfBirth":{"BirthDt":"1935-05-08","CityOfBirth":"Unknown","CtryOfBirth":"ZZ"},"Othr":[{"Id":"+07-197368463","SchmeNm":{"Prtry":"MSISDN"}}]}},"CtctDtls":{"MobNb":"+07-197368463"}},"CdtrAcct":{"Id":{"Othr":[{"Id":"+07-197368463","SchmeNm":{"Prtry":"MSISDN"}}]},"Nm":"Felicia Quill"},"Purp":{"Cd":"MP2P"}},"RgltryRptg":{"Dtls":{"Tp":"BALANCE OF PAYMENTS","Cd":"100"}},"RmtInf":{"Ustrd":"Payment of USD 30713.75 from April to Felicia"},"SplmtryData":{"Envlp":{"Doc":{"Xprtn":"2023-02-03T07:17:52.216Z"}}}}}]]', ), ); }); + jest.spyOn(cacheDatabaseManager, 'getBuffer').mockImplementationOnce(() => { + return Promise.resolve({ + DataCache: { + dbtrId: '1234', + cdtrId: '5678', + dbtrAcctId: '4321', + cdtrAcctId: '8765', + }, + }); + }); + const request = Pacs002Sample as Pacs002; + const rebuildCacheSpy = jest.spyOn(LogicService, 'rebuildCache'); const handleSpy = jest.spyOn(LogicService, 'handlePacs002'); + await LogicService.handlePacs002(request, 'pacs.002.001.12'); + + expect(rebuildCacheSpy).toHaveBeenCalledTimes(0); expect(handleSpy).toHaveBeenCalledTimes(1); expect(handleSpy).toHaveReturned(); }); - it('should handle pacs.002, database error', async () => { + it('should handle pacs.002 - rebuildCache', async () => { jest.spyOn(cacheDatabaseManager, 'getTransactionPacs008').mockImplementation((EndToEndId: string) => { + return Promise.resolve( + JSON.parse( + '[[{"TxTp":"pacs.008.001.10","FIToFICstmrCdtTrf":{"GrpHdr":{"MsgId":"cabb-32c3-4ecf-944e-654855c80c38","CreDtTm":"2023-02-03T07:17:52.216Z","NbOfTxs":1,"SttlmInf":{"SttlmMtd":"CLRG"}},"CdtTrfTxInf":{"PmtId":{"InstrId":"4ca819baa65d4a2c9e062f2055525046","EndToEndId":"701b-ae14-46fd-a2cf-88dda2875fdd"},"IntrBkSttlmAmt":{"Amt":{"Amt":31020.89,"Ccy":"USD"}},"InstdAmt":{"Amt":{"Amt":9000,"Ccy":"ZAR"}},"xchgRate":17.536082,"ChrgBr":"DEBT","ChrgsInf":{"Amt":{"Amt":307.14,"Ccy":"USD"},"Agt":{"FinInstnId":{"ClrSysMmbId":{"MmbId":"typology003"}}}},"InitgPty":{"Nm":"April Blake Grant","Id":{"PrvtId":{"DtAndPlcOfBirth":{"BirthDt":"1968-02-01","CityOfBirth":"Unknown","CtryOfBirth":"ZZ"},"Othr":[{"Id":"+01-710694778","SchmeNm":{"Prtry":"MSISDN"}}]}},"CtctDtls":{"MobNb":"+01-710694778"}},"Dbtr":{"Nm":"April Blake Grant","Id":{"PrvtId":{"DtAndPlcOfBirth":{"BirthDt":"1968-02-01","CityOfBirth":"Unknown","CtryOfBirth":"ZZ"},"Othr":[{"Id":"+01-710694778","SchmeNm":{"Prtry":"MSISDN"}}]}},"CtctDtls":{"MobNb":"+01-710694778"}},"DbtrAcct":{"Id":{"Othr":[{"Id":"+01-710694778","SchmeNm":{"Prtry":"MSISDN"}}]},"Nm":"April Grant"},"DbtrAgt":{"FinInstnId":{"ClrSysMmbId":{"MmbId":"typology003"}}},"CdtrAgt":{"FinInstnId":{"ClrSysMmbId":{"MmbId":"dfsp002"}}},"Cdtr":{"Nm":"Felicia Easton Quill","Id":{"PrvtId":{"DtAndPlcOfBirth":{"BirthDt":"1935-05-08","CityOfBirth":"Unknown","CtryOfBirth":"ZZ"},"Othr":[{"Id":"+07-197368463","SchmeNm":{"Prtry":"MSISDN"}}]}},"CtctDtls":{"MobNb":"+07-197368463"}},"CdtrAcct":{"Id":{"Othr":[{"Id":"+07-197368463","SchmeNm":{"Prtry":"MSISDN"}}]},"Nm":"Felicia Quill"},"Purp":{"Cd":"MP2P"}},"RgltryRptg":{"Dtls":{"Tp":"BALANCE OF PAYMENTS","Cd":"100"}},"RmtInf":{"Ustrd":"Payment of USD 30713.75 from April to Felicia"},"SplmtryData":{"Envlp":{"Doc":{"Xprtn":"2023-02-03T07:17:52.216Z"}}}}}]]', + ), + ); + }); + + const request = Pacs002Sample as Pacs002; + + const rebuildCacheSpy = jest.spyOn(LogicService, 'rebuildCache'); + const handleSpy = jest.spyOn(LogicService, 'handlePacs002'); + + await LogicService.handlePacs002(request, 'pacs.002.001.12'); + expect(rebuildCacheSpy).toHaveBeenCalledTimes(1); + expect(handleSpy).toHaveBeenCalledTimes(1); + expect(handleSpy).toHaveReturned(); + }); + + it('should handle pacs.002, database error', async () => { + jest.spyOn(cacheDatabaseManager, 'getTransactionPacs008').mockImplementationOnce((EndToEndId: string) => { return new Promise((resolve, reject) => { throw new Error('Deliberate Error'); }); @@ -228,6 +284,32 @@ describe('App Controller & Logic Service', () => { } expect(error).toEqual('Deliberate Error'); }); + + it('should handle pacs.002, database unable to save', async () => { + jest.spyOn(cacheDatabaseManager, 'getBuffer').mockImplementationOnce(() => { + return Promise.resolve({ + DataCache: { + dbtrId: '1234', + cdtrId: '5678', + dbtrAcctId: '4321', + cdtrAcctId: '8765', + }, + }); + }); + jest.spyOn(cacheDatabaseManager, 'saveTransactionHistory').mockRejectedValueOnce(new Error('Deliberate Error')); + + const request = Pacs002Sample as Pacs002; + + const handleSpy = jest.spyOn(LogicService, 'handlePacs002'); + try { + await LogicService.handlePacs002(request, 'pacs.002.001.12'); + + expect(true).toBe(false); // unreachable + } catch (err) { + expect(handleSpy).toHaveBeenCalledTimes(1); + expect(err).toEqual(new Error('Deliberate Error')); + } + }); }); describe('Error cases', () => { @@ -295,4 +377,66 @@ describe('App Controller & Logic Service', () => { expect(handleSpy).toHaveReturned(); }); }); + + describe('rebuildCache', () => { + it('no pacs008', async () => { + const EndToEndId = crypto.randomUUID(); + const Id = crypto.randomUUID(); + + jest.spyOn(cacheDatabaseManager, 'getTransactionPacs008').mockImplementationOnce((key: any) => { + return Promise.resolve([[]]); + }); + + const rebuildCacheSpy = jest.spyOn(LogicService, 'rebuildCache'); + const loggerErrorSpy = jest.spyOn(loggerService, 'error'); + + await LogicService.rebuildCache(EndToEndId, true, Id); + expect(rebuildCacheSpy).toHaveBeenCalledTimes(1); + expect(loggerErrorSpy).toHaveBeenCalledWith('Could not find pacs008 transaction to rebuild dataCache with', 'rebuildCache()', Id); + }); + + it('writeToRedis', async () => { + const EndToEndId = crypto.randomUUID(); + const Id = crypto.randomUUID(); + + jest.spyOn(cacheDatabaseManager, 'getTransactionPacs008').mockImplementationOnce((key: any) => { + return Promise.resolve([ + [ + JSON.parse( + '{"TxTp":"pacs.008.001.10","FIToFICstmrCdtTrf":{"GrpHdr":{"MsgId":"cabb-32c3-4ecf-944e-654855c80c38","CreDtTm":"2023-02-03T07:17:52.216Z","NbOfTxs":1,"SttlmInf":{"SttlmMtd":"CLRG"}},"CdtTrfTxInf":{"PmtId":{"InstrId":"4ca819baa65d4a2c9e062f2055525046","EndToEndId":"701b-ae14-46fd-a2cf-88dda2875fdd"},"IntrBkSttlmAmt":{"Amt":{"Amt":31020.89,"Ccy":"USD"}},"InstdAmt":{"Amt":{"Amt":9000,"Ccy":"ZAR"}},"ChrgBr":"DEBT","ChrgsInf":{"Amt":{"Amt":307.14,"Ccy":"USD"},"Agt":{"FinInstnId":{"ClrSysMmbId":{"MmbId":"typology003"}}}},"InitgPty":{"Nm":"April Blake Grant","Id":{"PrvtId":{"DtAndPlcOfBirth":{"BirthDt":"1968-02-01","CityOfBirth":"Unknown","CtryOfBirth":"ZZ"},"Othr":[{"Id":"+01-710694778","SchmeNm":{"Prtry":"MSISDN"}}]}},"CtctDtls":{"MobNb":"+01-710694778"}},"Dbtr":{"Nm":"April Blake Grant","Id":{"PrvtId":{"DtAndPlcOfBirth":{"BirthDt":"1968-02-01","CityOfBirth":"Unknown","CtryOfBirth":"ZZ"},"Othr":[{"Id":"+01-710694778","SchmeNm":{"Prtry":"MSISDN"}}]}},"CtctDtls":{"MobNb":"+01-710694778"}},"DbtrAcct":{"Id":{"Othr":[{"Id":"+01-710694778","SchmeNm":{"Prtry":"MSISDN"}}]},"Nm":"April Grant"},"DbtrAgt":{"FinInstnId":{"ClrSysMmbId":{"MmbId":"typology003"}}},"CdtrAgt":{"FinInstnId":{"ClrSysMmbId":{"MmbId":"dfsp002"}}},"Cdtr":{"Nm":"Felicia Easton Quill","Id":{"PrvtId":{"DtAndPlcOfBirth":{"BirthDt":"1935-05-08","CityOfBirth":"Unknown","CtryOfBirth":"ZZ"},"Othr":[{"Id":"+07-197368463","SchmeNm":{"Prtry":"MSISDN"}}]}},"CtctDtls":{"MobNb":"+07-197368463"}},"CdtrAcct":{"Id":{"Othr":[{"Id":"+07-197368463","SchmeNm":{"Prtry":"MSISDN"}}]},"Nm":"Felicia Quill"},"Purp":{"Cd":"MP2P"}},"RgltryRptg":{"Dtls":{"Tp":"BALANCE OF PAYMENTS","Cd":"100"}},"RmtInf":{"Ustrd":"Payment of USD 30713.75 from April to Felicia"},"SplmtryData":{"Envlp":{"Doc":{"Xprtn":"2023-02-03T07:17:52.216Z"}}}}}', + ), + ], + ]); + }); + + const rebuildCacheSpy = jest.spyOn(LogicService, 'rebuildCache'); + + await LogicService.rebuildCache(EndToEndId, true, Id); + expect(rebuildCacheSpy).toHaveBeenCalledTimes(1); + }); + + it('writeToRedis - createMessageBuffer error', async () => { + const EndToEndId = crypto.randomUUID(); + const Id = crypto.randomUUID(); + + jest.spyOn(cacheDatabaseManager, 'getTransactionPacs008').mockImplementationOnce((key: any) => { + return Promise.resolve([ + [ + JSON.parse( + '{"TxTp":"pacs.008.001.10","FIToFICstmrCdtTrf":{"GrpHdr":{"MsgId":"cabb-32c3-4ecf-944e-654855c80c38","CreDtTm":"2023-02-03T07:17:52.216Z","NbOfTxs":1,"SttlmInf":{"SttlmMtd":"CLRG"}},"CdtTrfTxInf":{"PmtId":{"InstrId":"4ca819baa65d4a2c9e062f2055525046","EndToEndId":"701b-ae14-46fd-a2cf-88dda2875fdd"},"IntrBkSttlmAmt":{"Amt":{"Amt":31020.89,"Ccy":"USD"}},"InstdAmt":{"Amt":{"Amt":9000,"Ccy":"ZAR"}},"ChrgBr":"DEBT","ChrgsInf":{"Amt":{"Amt":307.14,"Ccy":"USD"},"Agt":{"FinInstnId":{"ClrSysMmbId":{"MmbId":"typology003"}}}},"InitgPty":{"Nm":"April Blake Grant","Id":{"PrvtId":{"DtAndPlcOfBirth":{"BirthDt":"1968-02-01","CityOfBirth":"Unknown","CtryOfBirth":"ZZ"},"Othr":[{"Id":"+01-710694778","SchmeNm":{"Prtry":"MSISDN"}}]}},"CtctDtls":{"MobNb":"+01-710694778"}},"Dbtr":{"Nm":"April Blake Grant","Id":{"PrvtId":{"DtAndPlcOfBirth":{"BirthDt":"1968-02-01","CityOfBirth":"Unknown","CtryOfBirth":"ZZ"},"Othr":[{"Id":"+01-710694778","SchmeNm":{"Prtry":"MSISDN"}}]}},"CtctDtls":{"MobNb":"+01-710694778"}},"DbtrAcct":{"Id":{"Othr":[{"Id":"+01-710694778","SchmeNm":{"Prtry":"MSISDN"}}]},"Nm":"April Grant"},"DbtrAgt":{"FinInstnId":{"ClrSysMmbId":{"MmbId":"typology003"}}},"CdtrAgt":{"FinInstnId":{"ClrSysMmbId":{"MmbId":"dfsp002"}}},"Cdtr":{"Nm":"Felicia Easton Quill","Id":{"PrvtId":{"DtAndPlcOfBirth":{"BirthDt":"1935-05-08","CityOfBirth":"Unknown","CtryOfBirth":"ZZ"},"Othr":[{"Id":"+07-197368463","SchmeNm":{"Prtry":"MSISDN"}}]}},"CtctDtls":{"MobNb":"+07-197368463"}},"CdtrAcct":{"Id":{"Othr":[{"Id":"+07-197368463","SchmeNm":{"Prtry":"MSISDN"}}]},"Nm":"Felicia Quill"},"Purp":{"Cd":"MP2P"}},"RgltryRptg":{"Dtls":{"Tp":"BALANCE OF PAYMENTS","Cd":"100"}},"RmtInf":{"Ustrd":"Payment of USD 30713.75 from April to Felicia"},"SplmtryData":{"Envlp":{"Doc":{"Xprtn":"2023-02-03T07:17:52.216Z"}}}}}', + ), + ], + ]); + }); + + jest.spyOn(protobuf, 'createMessageBuffer').mockImplementationOnce(() => undefined); + + const rebuildCacheSpy = jest.spyOn(LogicService, 'rebuildCache'); + const loggerErrorSpy = jest.spyOn(loggerService, 'error'); + + await LogicService.rebuildCache(EndToEndId, true, Id); + expect(rebuildCacheSpy).toHaveBeenCalledTimes(1); + expect(loggerErrorSpy).toHaveBeenCalledWith('[pacs008] could not rebuild redis cache'); + }); + }); }); From 2025f5558eb7da49fa1659106862737a9e8123b0 Mon Sep 17 00:00:00 2001 From: Len Bekker Date: Thu, 24 Oct 2024 13:57:15 +0200 Subject: [PATCH 5/6] docs: update message samples and activity diagram --- README.md | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/README.md b/README.md index 0f8ac49..dd4d1fb 100644 --- a/README.md +++ b/README.md @@ -180,9 +180,9 @@ flowchart TD ```mermaid graph TD A([Start]) --> B{Check Cache} - B -->|Cache Hit?| C[Retrieve Pain.001 from Cache] - B -->|No| D[Get Pain.001 from Database] - D --> E[Cache Pain.001] + B -->|Cache Hit?| C[Retrieve Pacs.008 from Cache] + B -->|No| D[Get Pacs.008 from Database] + D --> E[Cache Pacs.008] C --> F[Insert TransactionHistory] E --> F F --> G[Insert Creditor and Debtor Accounts] @@ -312,7 +312,8 @@ graph TD "Amt": 31020.89, "Ccy": "USD" }, - "CcyOfTrf": "USD" + "CcyOfTrf": "USD", + "XchgRate": 1.00, } }, "ChrgBr": "DEBT", @@ -672,6 +673,7 @@ graph TD "Ccy": "XTS" } }, + "XchgRate": 1.00, "ChrgBr": "DEBT", "ChrgsInf": { "Amt": { From 876963aa3eaac36fb9d11151d4529e8a7bc8030b Mon Sep 17 00:00:00 2001 From: Len Bekker Date: Fri, 25 Oct 2024 09:08:31 +0200 Subject: [PATCH 6/6] fix: pacs008 schema --- src/schemas/pacs.008.json | 32 ++++++++++++++++++++++++++++++-- 1 file changed, 30 insertions(+), 2 deletions(-) diff --git a/src/schemas/pacs.008.json b/src/schemas/pacs.008.json index 5b37c7c..6cd7b0b 100644 --- a/src/schemas/pacs.008.json +++ b/src/schemas/pacs.008.json @@ -504,8 +504,36 @@ "properties": { "Envlp": { "type": "object", - "properties": {}, - "required": [] + "properties": { + "Doc": { + "type": "object", + "properties": { + "InitgPty": { + "type": "object", + "properties": { + "Glctn": { + "type": "object", + "properties": { + "Lat": { + "type": "string" + }, + "Long": { + "type": "string" + } + }, + "required": ["Lat", "Long"] + } + }, + "required": ["Glctn"] + }, + "Xprtn": { + "type": "string" + } + }, + "required": ["InitgPty", "Xprtn"] + } + }, + "required": ["Doc"] } }, "required": ["Envlp"]