@mojaloop/central-ledger 19.14.0 → 20.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (394) hide show
  1. package/.grype.yaml +3 -0
  2. package/.license-scanner.json +16 -0
  3. package/.ncurc.yaml +19 -13
  4. package/.nvmrc +1 -1
  5. package/.nycrc.yml +14 -2
  6. package/CHANGELOG.md +51 -0
  7. package/Dockerfile +13 -10
  8. package/README.md +39 -89
  9. package/audit-ci.jsonc +1 -1
  10. package/config/default.json +60 -3
  11. package/config/deprecated.default-settlement.json +160 -0
  12. package/docker/ml-api-adapter/default.json +28 -0
  13. package/docker-compose.yml +6 -0
  14. package/nodemon.json +6 -0
  15. package/package.json +128 -64
  16. package/{sbom-v19.13.2.csv → sbom-v20.0.0.csv} +886 -736
  17. package/scripts/_wait4_all.js +5 -5
  18. package/scripts/transferSettlementTemp/interchangeFeeCalculation.js +51 -0
  19. package/src/api/index.ts +30 -0
  20. package/src/api/interface/swagger.json +2249 -2054
  21. package/src/api/participants/routes.js +1 -1
  22. package/src/api/root/handler.int.ts +55 -0
  23. package/src/api/routes.js +7 -6
  24. package/src/api/server.ts +27 -0
  25. package/src/api/server.unit.ts +16 -0
  26. package/src/api/settlementModels/routes.js +4 -4
  27. package/src/domain/participant/index.d.ts +47 -0
  28. package/src/domain/participant/index.int.ts +724 -0
  29. package/src/domain/participant/index.js +4 -5
  30. package/src/domain/position/fx-prepare.js +1 -1
  31. package/src/domain/position/fx-timeout-reserved.js +6 -0
  32. package/src/domain/position/prepare.js +1 -1
  33. package/src/handlers/admin/handler.js +1 -1
  34. package/src/handlers/index.js +30 -0
  35. package/src/handlers/positions/handler.js +0 -2
  36. package/src/handlers/positions/handlerBatch.js +5 -5
  37. package/src/handlers/register.js +44 -29
  38. package/src/handlers/timeouts/handler.js +1 -4
  39. package/src/handlers/transfers/fx-abort.int.ts +209 -0
  40. package/src/handlers/transfers/fx-handlers.int.ts +275 -0
  41. package/src/handlers/transfers/fx-timeout.int.ts +284 -0
  42. package/src/handlers/transfers/handler.js +1 -3
  43. package/src/handlers/transfers/handlers.int.ts +538 -0
  44. package/src/handlers/transfers/prepare.int.ts +1283 -0
  45. package/src/handlers/transfers/prepare.js +2 -2
  46. package/src/knexfile.js +19 -0
  47. package/src/lib/cache.js +1 -1
  48. package/src/lib/config/index.ts +60 -0
  49. package/src/lib/config/resolver.ts +231 -0
  50. package/src/lib/config/resolver.unit.ts +72 -0
  51. package/src/lib/config/types.ts +372 -0
  52. package/src/lib/config/util.ts +523 -0
  53. package/src/lib/config/util.unit.ts +825 -0
  54. package/src/lib/migrator.js +3 -3
  55. package/src/lib/proxyCache.js +3 -3
  56. package/{migrations → src/migrations}/100100_event.js +1 -1
  57. package/{migrations → src/migrations}/110100_contactType.js +1 -1
  58. package/{migrations → src/migrations}/110200_currency.js +1 -1
  59. package/{migrations → src/migrations}/110201_currency-scale.js +1 -1
  60. package/{migrations → src/migrations}/110300_endpointType.js +1 -1
  61. package/{migrations → src/migrations}/110400_ledgerEntryType.js +1 -1
  62. package/{migrations → src/migrations}/110450_ledgerAccountType.js +1 -1
  63. package/{migrations → src/migrations}/110500_participantLimitType.js +1 -1
  64. package/{migrations → src/migrations}/110600_transferParticipantRoleType.js +1 -1
  65. package/{migrations → src/migrations}/110700_transferState.js +1 -1
  66. package/{migrations → src/migrations}/110900_settlementState.js +1 -1
  67. package/{migrations → src/migrations}/112000_bulkTransferState.js +1 -1
  68. package/{migrations → src/migrations}/112100_bulkProcessingState.js +1 -1
  69. package/{migrations → src/migrations}/200100_participant.js +1 -1
  70. package/{migrations → src/migrations}/200200_participantContact.js +1 -1
  71. package/{migrations → src/migrations}/200300_participantEndpoint.js +1 -1
  72. package/{migrations → src/migrations}/200400_participantParty.js +1 -1
  73. package/{migrations → src/migrations}/200600_token.js +1 -1
  74. package/{migrations → src/migrations}/200601_token-indexes.js +1 -1
  75. package/{migrations → src/migrations}/300100_transferDuplicateCheck.js +2 -2
  76. package/{migrations → src/migrations}/300150_bulkTransferDuplicateCheck.js +2 -2
  77. package/{migrations → src/migrations}/300200_transfer.js +2 -2
  78. package/{migrations → src/migrations}/300202_transfer-decimal184.js +1 -1
  79. package/{migrations → src/migrations}/300250_bulkTransfer.js +2 -3
  80. package/{migrations → src/migrations}/300275_bulkTransferAssociation.js +2 -3
  81. package/{migrations → src/migrations}/300300_ilpPacket.js +1 -1
  82. package/{migrations → src/migrations}/300400_transferStateChange.js +1 -1
  83. package/{migrations → src/migrations}/300450_bulkTransferStateChange.js +1 -1
  84. package/{migrations → src/migrations}/300500_segment.js +1 -1
  85. package/{migrations → src/migrations}/310100_participantCurrency.js +1 -1
  86. package/{migrations → src/migrations}/310200_transferParticipant.js +1 -1
  87. package/{migrations → src/migrations}/310202_transferParticipant-decimal184.js +1 -1
  88. package/{migrations → src/migrations}/310204_transferParticipant-participantId.js +2 -2
  89. package/{migrations → src/migrations}/310300_participantPosition.js +1 -1
  90. package/{migrations → src/migrations}/310302_participantPosition-decimal184.js +1 -1
  91. package/{migrations → src/migrations}/310400_participantPositionChange.js +1 -1
  92. package/{migrations → src/migrations}/310402_participantPositionChange-decimal184.js +1 -1
  93. package/{migrations → src/migrations}/310403_participantPositionChange-participantCurrencyId.js +2 -2
  94. package/{migrations → src/migrations}/310404_participantPositionChange-change.js +2 -2
  95. package/{migrations → src/migrations}/310500_participantLimit.js +1 -1
  96. package/{migrations → src/migrations}/310502_participantLimit-decimal184.js +1 -1
  97. package/{migrations → src/migrations}/400100_settlementWindow.js +1 -1
  98. package/{migrations → src/migrations}/400200_settlement.js +1 -1
  99. package/{migrations → src/migrations}/400300_settlementSettlementWindow.js +1 -1
  100. package/{migrations → src/migrations}/400301_settlementSettlementWindow-indexes.js +0 -1
  101. package/{migrations → src/migrations}/400400_settlementWindowStateChange.js +1 -1
  102. package/{migrations → src/migrations}/400500_settlementTransferParticipant.js +1 -1
  103. package/{migrations → src/migrations}/400502_settlementTransferParticipant-decimal184.js +1 -1
  104. package/{migrations → src/migrations}/400600_settlementParticipantCurrency.js +1 -1
  105. package/{migrations → src/migrations}/400602_settlementParticipantCurrency-decimal184.js +1 -1
  106. package/{migrations → src/migrations}/400700_settlementParticipantCurrencyStateChange.js +1 -1
  107. package/{migrations → src/migrations}/400800_settlementStateChange.js +1 -1
  108. package/{migrations → src/migrations}/400900_settlementWindowContent.js +1 -1
  109. package/{migrations → src/migrations}/401000_settlementWindowContentStateChange.js +1 -1
  110. package/{migrations → src/migrations}/401001_settlementWindowContentStateChange-indexes.js +6 -6
  111. package/{migrations → src/migrations}/401200_settlementContentAggregation.js +1 -1
  112. package/{migrations → src/migrations}/401300_settlementModel.js +1 -1
  113. package/{migrations → src/migrations}/410100_transferFulfilmentDuplicateCheck.js +1 -1
  114. package/{migrations → src/migrations}/410102_transferFulfilmentDuplicateCheck-dropForeignTransferId.js +1 -1
  115. package/{migrations → src/migrations}/410103_transferFulfilmentDuplicateCheck-hashNullable.js +1 -1
  116. package/{migrations → src/migrations}/410200_transferFulfilment.js +1 -1
  117. package/{migrations → src/migrations}/410202_transferFulfilment-ilpFulfilment.js +1 -1
  118. package/{migrations → src/migrations}/410203_transferFulfilment-foreignTransferFulfilmentId.js +1 -1
  119. package/{migrations → src/migrations}/410240_bulkTransferFulfilmentDuplicateCheck.js +1 -1
  120. package/{migrations → src/migrations}/410250_bulkTransferFulfilment.js +1 -1
  121. package/{migrations → src/migrations}/410300_transferExtension.js +1 -1
  122. package/{migrations → src/migrations}/410302_transferExtension-transferErrorId.js +1 -1
  123. package/{migrations → src/migrations}/410350_bulkTransferExtension.js +1 -1
  124. package/{migrations → src/migrations}/410400_transferTimeout.js +1 -1
  125. package/{migrations → src/migrations}/410450_transferErrorDuplicateCheck.js +1 -1
  126. package/{migrations → src/migrations}/410500_transferError.js +1 -1
  127. package/{migrations → src/migrations}/410502_transferError-foreignTransferErrorDuplicateCheckId.js +1 -1
  128. package/{migrations → src/migrations}/410550_bulkTransferError.js +1 -1
  129. package/{migrations → src/migrations}/410600_expiringTransfer.js +1 -1
  130. package/{migrations → src/migrations}/500401_quote-hotfix-2719.js +8 -8
  131. package/{migrations → src/migrations}/500501_feature-fixSubIdRef.js +12 -12
  132. package/{migrations → src/migrations}/500601_party-2480.js +3 -3
  133. package/{migrations → src/migrations}/501001_quoteExtension-quote-service-174.js +1 -2
  134. package/{migrations → src/migrations}/501002_quoteExtension-2522.js +3 -3
  135. package/{migrations → src/migrations}/600010_fxTransferType.js +1 -1
  136. package/{migrations → src/migrations}/600012_fxParticipantCurrencyType.js +1 -1
  137. package/{migrations → src/migrations}/600100_fxTransferDuplicateCheck.js +2 -2
  138. package/{migrations → src/migrations}/600110_fxTransferErrorDuplicateCheck.js.js +1 -1
  139. package/{migrations → src/migrations}/600200_fxTransfer.js +2 -2
  140. package/{migrations → src/migrations}/600400_fxTransferStateChange.js +1 -1
  141. package/{migrations → src/migrations}/600501_fxWatchList.js +2 -2
  142. package/{migrations → src/migrations}/600600_fxTransferFulfilmentDuplicateCheck.js +1 -1
  143. package/{migrations → src/migrations}/600700_fxTransferFulfilment.js +1 -1
  144. package/{migrations → src/migrations}/600800_fxTransferExtension.js +1 -1
  145. package/{migrations → src/migrations}/601400_fxTransferTimeout.js +1 -1
  146. package/{migrations → src/migrations}/601500_fxTransferError.js +1 -1
  147. package/{migrations → src/migrations}/610200_fxTransferParticipant.js +1 -1
  148. package/{migrations → src/migrations}/610202_fxTransferParticipant-participantId.js +2 -2
  149. package/{migrations → src/migrations}/610403_participantPositionChange-fxTransfer.js +1 -1
  150. package/{migrations → src/migrations}/800100_quotePartyView.js +1 -1
  151. package/{migrations → src/migrations}/800101_feature-fixSubIdRef.js +1 -1
  152. package/{migrations → src/migrations}/800200_quoteView.js +1 -1
  153. package/{migrations → src/migrations}/800300_quoteResponseView.js +1 -1
  154. package/{migrations → src/migrations}/900100_foreign-keys.js +18 -18
  155. package/{migrations → src/migrations}/900101_foreign-keys.js +6 -6
  156. package/{migrations → src/migrations}/910100_feature904DDL.js +7 -7
  157. package/{migrations → src/migrations}/910101_feature904DataMigration.js +1 -1
  158. package/{migrations → src/migrations}/910102_feature949DataMigration.js +1 -1
  159. package/{migrations → src/migrations}/910103_feature1093SettlementByCurrency.js +3 -3
  160. package/{migrations → src/migrations}/940101_quotePartyIdInfoExtension-2522.js +25 -25
  161. package/{migrations → src/migrations}/950101_transferParticipantStateChange.js +2 -2
  162. package/{migrations → src/migrations}/950102_settlementModel-adjustPosition.js +2 -2
  163. package/{migrations → src/migrations}/950103_dropTransferParticipantStateChange.js +3 -3
  164. package/{migrations → src/migrations}/950104_settlementModel-settlementAccountTypeId.js +3 -3
  165. package/src/migrations/950107_settlementWindowContent_foreign_settlementModel.js +63 -0
  166. package/{migrations → src/migrations}/950108_participantProxy.js +1 -2
  167. package/{migrations → src/migrations}/950120_fxQuoteConversionTerms_sourceAmount_nullable.js +1 -1
  168. package/{migrations → src/migrations}/960100_create_externalParticipant.js +2 -2
  169. package/{migrations → src/migrations}/960110_alter_transferParticipant__addFiled_externalParticipantId.js +2 -2
  170. package/{migrations → src/migrations}/961001_transferForwarded.js +1 -1
  171. package/{migrations → src/migrations}/961002_fxTransferForwarded.js +1 -1
  172. package/src/models/participant/externalParticipant.int.ts +85 -0
  173. package/src/models/participant/facade.js +4 -2
  174. package/src/models/participant/participantCurrencyCached.js +1 -1
  175. package/src/models/position/facade.js +8 -2
  176. package/src/models/transfer/facade.int.ts +139 -0
  177. package/src/models/transfer/facade.js +8 -3
  178. package/src/models/transfer/transferDuplicateCheck.js +2 -2
  179. package/src/models/transfer/transferError.int.ts +112 -0
  180. package/src/models/transfer/transferExtension.int.ts +137 -0
  181. package/src/models/transfer/transferStateChange.int.ts +117 -0
  182. package/{seeds → src/seeds}/participant.js +1 -1
  183. package/{seeds → src/seeds}/settlementInterchange.js +1 -0
  184. package/src/settlement/api/handlerRoutes.js +71 -0
  185. package/src/settlement/api/handlers/health.js +10 -0
  186. package/src/settlement/api/handlers/index.js +57 -0
  187. package/src/settlement/api/handlers/settlementWindows/{id}.js +103 -0
  188. package/src/settlement/api/handlers/settlementWindows.js +77 -0
  189. package/src/settlement/api/handlers/settlements/{id}.js +133 -0
  190. package/src/settlement/api/handlers/settlements/{sid}/participants/{pid}/accounts/{aid}.js +127 -0
  191. package/src/settlement/api/handlers/settlements/{sid}/participants/{pid}.js +127 -0
  192. package/src/settlement/api/handlers/settlements.js +114 -0
  193. package/src/settlement/api/openapiRouting.js +87 -0
  194. package/src/settlement/api/routes.js +176 -0
  195. package/src/settlement/domain/rules/index.js +46 -0
  196. package/src/settlement/domain/settlement/index.js +405 -0
  197. package/src/settlement/domain/settlementWindow/index.js +117 -0
  198. package/src/settlement/domain/transactions/index.js +83 -0
  199. package/src/settlement/domain/transferSettlement/index.js +57 -0
  200. package/src/settlement/handlers/deferredSettlement/handler.js +180 -0
  201. package/src/settlement/handlers/grossSettlement/handler.js +180 -0
  202. package/src/settlement/handlers/index.js +101 -0
  203. package/src/settlement/handlers/register.js +92 -0
  204. package/src/settlement/handlers/rules/handler.js +164 -0
  205. package/src/settlement/interface/swagger-handler.json +34 -0
  206. package/src/settlement/interface/swagger.json +1560 -0
  207. package/src/settlement/lib/db.js +32 -0
  208. package/src/settlement/lib/kafka/index.js +41 -0
  209. package/src/settlement/lib/kafka/producer.js +142 -0
  210. package/src/settlement/lib/scriptEngine.js +99 -0
  211. package/src/settlement/lib/scriptsLoader.js +175 -0
  212. package/src/settlement/lib/utility.js +243 -0
  213. package/src/settlement/models/ilpPackets/ilpPacket.js +42 -0
  214. package/src/settlement/models/lib/enums.js +192 -0
  215. package/src/settlement/models/misc/migrationLock.js +53 -0
  216. package/src/settlement/models/settlement/facade.js +1652 -0
  217. package/src/settlement/models/settlement/index.js +55 -0
  218. package/src/settlement/models/settlement/participantCurrency.js +48 -0
  219. package/src/settlement/models/settlement/settlement.js +49 -0
  220. package/src/settlement/models/settlement/settlementModel.js +44 -0
  221. package/src/settlement/models/settlement/settlementParticipantCurrency.js +61 -0
  222. package/src/settlement/models/settlement/settlementStateChange.js +44 -0
  223. package/src/settlement/models/settlement/settlementTransferParticipant.js +46 -0
  224. package/src/settlement/models/settlementWindow/facade.js +345 -0
  225. package/src/settlement/models/settlementWindow/index.js +47 -0
  226. package/src/settlement/models/settlementWindow/settlementWindowStateChange.js +55 -0
  227. package/src/settlement/models/settlementWindowContent/facade.js +79 -0
  228. package/src/settlement/models/settlementWindowContent/index.js +40 -0
  229. package/src/settlement/models/settlementWindowContent/settlementWindowContentStateChange.js +53 -0
  230. package/src/settlement/models/transferSettlement/facade.js +363 -0
  231. package/src/settlement/models/transferSettlement/index.js +42 -0
  232. package/src/settlement/shared/logger/index.js +7 -0
  233. package/src/settlement/shared/plugins.js +88 -0
  234. package/src/settlement/shared/setup.js +224 -0
  235. package/src/settlement/utils/truthyProperty.js +37 -0
  236. package/src/shared/legacyUtilShim.js +20 -0
  237. package/src/shared/plugins.js +3 -3
  238. package/src/shared/results.ts +22 -0
  239. package/src/shared/results.unit.ts +38 -0
  240. package/src/shared/setup.js +14 -1
  241. package/src/testing/api-helpers.ts +1222 -0
  242. package/src/testing/exec-async.ts +67 -0
  243. package/src/testing/harness.snapshot.sql +3895 -0
  244. package/src/testing/harness.test.ts +15 -0
  245. package/src/testing/harness.ts +1160 -0
  246. package/src/testing/kafka.ts +221 -0
  247. package/src/testing/prng.ts +82 -0
  248. package/src/testing/rdkafka.test.ts +263 -0
  249. package/src/testing/run.ts +559 -0
  250. package/src/testing/snapshot.test.ts +92 -0
  251. package/src/testing/snapshot.ts +549 -0
  252. package/src/testing/tap-stream.ts +164 -0
  253. package/src/testing/types.ts +95 -0
  254. package/src/testing/util.ts +333 -0
  255. package/src/types/central-services-error-handling.d.ts +72 -0
  256. package/src/types/central-services-stream.d.ts +148 -0
  257. package/test-integration.Dockerfile +0 -2
  258. package/test.Dockerfile +0 -2
  259. package/tsconfig.json +40 -0
  260. package/config/knexfile.js +0 -19
  261. package/migrations/950107_settlementWindowContent_foreign_settlementModel.js +0 -63
  262. package/src/api/index.js +0 -16
  263. package/src/lib/config.js +0 -81
  264. /package/{migrations → src/migrations}/110101_contactType-indexes.js +0 -0
  265. /package/{migrations → src/migrations}/110301_endpointType-indexes.js +0 -0
  266. /package/{migrations → src/migrations}/110401_ledgerEntryType-indexes.js +0 -0
  267. /package/{migrations → src/migrations}/110451_ledgerAccountType-indexes.js +0 -0
  268. /package/{migrations → src/migrations}/110501_participantLimitType-indexes.js +0 -0
  269. /package/{migrations → src/migrations}/110601_transferParticipantRoleType-indexes.js +0 -0
  270. /package/{migrations → src/migrations}/110800_settlementWindowState.js +0 -0
  271. /package/{migrations → src/migrations}/111000_amountType.js +0 -0
  272. /package/{migrations → src/migrations}/111001_amountType-indexes.js +0 -0
  273. /package/{migrations → src/migrations}/111100_balanceOfPayments.js +0 -0
  274. /package/{migrations → src/migrations}/111101_balanceOfPayments-indexes.js +0 -0
  275. /package/{migrations → src/migrations}/111200_partyIdentifierType.js +0 -0
  276. /package/{migrations → src/migrations}/111201_partyIdentifierType-indexes.js +0 -0
  277. /package/{migrations → src/migrations}/111300_partyType.js +0 -0
  278. /package/{migrations → src/migrations}/111301_partyType-indexes.js +0 -0
  279. /package/{migrations → src/migrations}/111400_quoteDuplicateCheck.js +0 -0
  280. /package/{migrations → src/migrations}/111500_transactionInitiator.js +0 -0
  281. /package/{migrations → src/migrations}/111501_transactionInitiator-indexes.js +0 -0
  282. /package/{migrations → src/migrations}/111600_transactionInitiatorType.js +0 -0
  283. /package/{migrations → src/migrations}/111601_transactionInitiatorType-indexes.js +0 -0
  284. /package/{migrations → src/migrations}/111700_settlementDelay.js +0 -0
  285. /package/{migrations → src/migrations}/111701_settlementDelay-indexes.js +0 -0
  286. /package/{migrations → src/migrations}/111800_settlementInterchange.js +0 -0
  287. /package/{migrations → src/migrations}/111801_settlementInterchange-indexes.js +0 -0
  288. /package/{migrations → src/migrations}/111900_settlementGranularity.js +0 -0
  289. /package/{migrations → src/migrations}/111901_settlementGranularity-indexes.js +0 -0
  290. /package/{migrations → src/migrations}/112101_bulkProcessingState-indexes.js +0 -0
  291. /package/{migrations → src/migrations}/200101_participant-indexes.js +0 -0
  292. /package/{migrations → src/migrations}/200201_participantContact-indexes.js +0 -0
  293. /package/{migrations → src/migrations}/200301_participantEndpoint-indexes.js +0 -0
  294. /package/{migrations → src/migrations}/200401_participantParty-indexes.js +0 -0
  295. /package/{migrations → src/migrations}/300201_transfer-indexes.js +0 -0
  296. /package/{migrations → src/migrations}/300251_bulkTransfer-indexes.js +0 -0
  297. /package/{migrations → src/migrations}/300276_bulkTransferAssociation-indexes.js +0 -0
  298. /package/{migrations → src/migrations}/300401_transferStateChange-indexes.js +0 -0
  299. /package/{migrations → src/migrations}/300451_bulkTransferStateChange-indexes.js +0 -0
  300. /package/{migrations → src/migrations}/300501_segment-indexes.js +0 -0
  301. /package/{migrations → src/migrations}/310101_participantCurrency-indexes.js +0 -0
  302. /package/{migrations → src/migrations}/310201_transferParticipant-indexes.js +0 -0
  303. /package/{migrations → src/migrations}/310203_transferParticipant-indexes.js +0 -0
  304. /package/{migrations → src/migrations}/310301_participantPosition-indexes.js +0 -0
  305. /package/{migrations → src/migrations}/310401_participantPositionChange-indexes.js +0 -0
  306. /package/{migrations → src/migrations}/310405_participantPositionChange-positionChange.js +0 -0
  307. /package/{migrations → src/migrations}/310406_participantPositionChange-positionChange-revert.js +0 -0
  308. /package/{migrations → src/migrations}/310501_participantLimit-indexes.js +0 -0
  309. /package/{migrations → src/migrations}/310503_participantLimit-indexes-composite.js +0 -0
  310. /package/{migrations → src/migrations}/400401_settlmentWindowStateChange-indexes.js +0 -0
  311. /package/{migrations → src/migrations}/400501_settlementTransferParticipant-indexes.js +0 -0
  312. /package/{migrations → src/migrations}/400601_settlementParticipantCurrency-indexes.js +0 -0
  313. /package/{migrations → src/migrations}/400701_settlementParticipantCurrencyStateChange-indexes.js +0 -0
  314. /package/{migrations → src/migrations}/400801_settlementStateChange-indexes.js +0 -0
  315. /package/{migrations → src/migrations}/401002_settlementWindowContent-indexes.js +0 -0
  316. /package/{migrations → src/migrations}/401201_settlementContentAggregation-indexes.js +0 -0
  317. /package/{migrations → src/migrations}/401301_settlementModel-indexes.js +0 -0
  318. /package/{migrations → src/migrations}/410101_transferFulfilmentDuplicateCheck-indexes.js +0 -0
  319. /package/{migrations → src/migrations}/410201_transferFulfilment-indexes.js +0 -0
  320. /package/{migrations → src/migrations}/410301_transferExtension-indexes.js +0 -0
  321. /package/{migrations → src/migrations}/410351_bulkTransferExtension-indexes.js +0 -0
  322. /package/{migrations → src/migrations}/410401_transferTimeout-indexes.js +0 -0
  323. /package/{migrations → src/migrations}/410501_transferError-indexes.js +0 -0
  324. /package/{migrations → src/migrations}/410551_bulkTransferError-indexes.js +0 -0
  325. /package/{migrations → src/migrations}/410601_expiringTransfer-indexes.js +0 -0
  326. /package/{migrations → src/migrations}/500100_transactionReference.js +0 -0
  327. /package/{migrations → src/migrations}/500101_transactionReference-indexes.js +0 -0
  328. /package/{migrations → src/migrations}/500200_transactionScenario.js +0 -0
  329. /package/{migrations → src/migrations}/500201_transactionScenario-indexes.js +0 -0
  330. /package/{migrations → src/migrations}/500300_transactionSubScenario.js +0 -0
  331. /package/{migrations → src/migrations}/500400_quote.js +0 -0
  332. /package/{migrations → src/migrations}/500500_quoteParty.js +0 -0
  333. /package/{migrations → src/migrations}/500600_party.js +0 -0
  334. /package/{migrations → src/migrations}/500700_quoteResponse.js +0 -0
  335. /package/{migrations → src/migrations}/500800_quoteResponseIlpPacket.js +0 -0
  336. /package/{migrations → src/migrations}/500900_geoCode.js +0 -0
  337. /package/{migrations → src/migrations}/501000_quoteExtension.js +0 -0
  338. /package/{migrations → src/migrations}/501100_quoteResponseDuplicateCheck.js +0 -0
  339. /package/{migrations → src/migrations}/501200_quoteError.js +0 -0
  340. /package/{migrations → src/migrations}/501300_transferRules.js +0 -0
  341. /package/{migrations → src/migrations}/600011_fxTransferType-indexes.js +0 -0
  342. /package/{migrations → src/migrations}/600013_fxParticipantCurrencyType-indexes.js +0 -0
  343. /package/{migrations → src/migrations}/600201_fxTransfer-indexes.js +0 -0
  344. /package/{migrations → src/migrations}/600401_fxTransferStateChange-indexes.js +0 -0
  345. /package/{migrations → src/migrations}/600502_fxWatchList-indexes.js +0 -0
  346. /package/{migrations → src/migrations}/600601_fxTransferFulfilmentDuplicateCheck-indexes.js +0 -0
  347. /package/{migrations → src/migrations}/600701_fxTransferFulfilment-indexes.js +0 -0
  348. /package/{migrations → src/migrations}/601401_fxTransferTimeout-indexes.js +0 -0
  349. /package/{migrations → src/migrations}/601501_fxTransferError-indexes.js +0 -0
  350. /package/{migrations → src/migrations}/610201_fxTransferParticipant-indexes.js +0 -0
  351. /package/{migrations → src/migrations}/910102_hotfix954.js +0 -0
  352. /package/{migrations → src/migrations}/940100_quotePartyIdInfoExtension.js +0 -0
  353. /package/{migrations → src/migrations}/950100_settlementContentAggregation-decimal184.js +0 -0
  354. /package/{migrations → src/migrations}/950106_unique_settlement_model_ledger_account_currency.js +0 -0
  355. /package/{migrations → src/migrations}/950109_fxQuote.js +0 -0
  356. /package/{migrations → src/migrations}/950110_fxQuoteResponse.js +0 -0
  357. /package/{migrations → src/migrations}/950111_fxQuoteError.js +0 -0
  358. /package/{migrations → src/migrations}/950113_fxQuoteDuplicateCheck.js +0 -0
  359. /package/{migrations → src/migrations}/950114_fxQuoteResponseDuplicateCheck.js +0 -0
  360. /package/{migrations → src/migrations}/950115_fxQuoteConversionTerms.js +0 -0
  361. /package/{migrations → src/migrations}/950116_fxQuoteConversionTermsExtension.js +0 -0
  362. /package/{migrations → src/migrations}/950117_fxQuoteResponseConversionTerms.js +0 -0
  363. /package/{migrations → src/migrations}/950118_fxQuoteResponseConversionTermsExtension.js +0 -0
  364. /package/{migrations → src/migrations}/950119_fxCharge.js +0 -0
  365. /package/{migrations → src/migrations}/960111_alter_fxTransferParticipant__addFiled_externalParticipantId.js +0 -0
  366. /package/{migrations → src/migrations}/960112_fxTransfer-indexes2.js +0 -0
  367. /package/{migrations → src/migrations}/960113_fxTransferExtension-indexes.js +0 -0
  368. /package/{migrations → src/migrations}/960114_fxTransferTimeout-indexes2.js +0 -0
  369. /package/{migrations → src/migrations}/961000_participantEndpoint_unique.js +0 -0
  370. /package/{migrations → src/migrations}/migration.template +0 -0
  371. /package/{seeds → src/seeds}/amountType.js +0 -0
  372. /package/{seeds → src/seeds}/balanceOfPayments.js +0 -0
  373. /package/{seeds → src/seeds}/bulkProcessingState.js +0 -0
  374. /package/{seeds → src/seeds}/bulkTransferState.js +0 -0
  375. /package/{seeds → src/seeds}/currency.js +0 -0
  376. /package/{seeds → src/seeds}/endpointType.js +0 -0
  377. /package/{seeds → src/seeds}/fxParticipantCurrencyType.js +0 -0
  378. /package/{seeds → src/seeds}/fxTransferType.js +0 -0
  379. /package/{seeds → src/seeds}/ledgerAccountType.js +0 -0
  380. /package/{seeds → src/seeds}/ledgerEntryType.js +0 -0
  381. /package/{seeds → src/seeds}/participantLimitType.js +0 -0
  382. /package/{seeds → src/seeds}/partyIdentifierType.js +0 -0
  383. /package/{seeds → src/seeds}/partyType.js +0 -0
  384. /package/{seeds → src/seeds}/settlementDelay.js +0 -0
  385. /package/{seeds → src/seeds}/settlementGranularity.js +0 -0
  386. /package/{seeds → src/seeds}/settlementState.js +0 -0
  387. /package/{seeds → src/seeds}/settlementWindow1State.js +0 -0
  388. /package/{seeds → src/seeds}/settlementWindow2Open.js +0 -0
  389. /package/{seeds → src/seeds}/transactionInitiator.js +0 -0
  390. /package/{seeds → src/seeds}/transactionInitiatorType.js +0 -0
  391. /package/{seeds → src/seeds}/transactionScenario.js +0 -0
  392. /package/{seeds → src/seeds}/transferParticipantRoleType.js +0 -0
  393. /package/{seeds → src/seeds}/transferState.js +0 -0
  394. /package/{seeds → src/seeds}/z1000_settlementModel-deprecated.js- +0 -0
@@ -0,0 +1,825 @@
1
+ /*****
2
+ License
3
+ --------------
4
+ Copyright © 2020-2026 Mojaloop Foundation
5
+ 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
6
+
7
+ http://www.apache.org/licenses/LICENSE-2.0
8
+
9
+ 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.
10
+
11
+ Contributors
12
+ --------------
13
+ This is the official list of the Mojaloop project contributors for this file.
14
+ Names of the original copyright holders (individuals or organizations)
15
+ should be listed with a '*' in the first column. People who have
16
+ contributed from an organization can be listed under the organization
17
+ that actually holds the copyright for their contributions (see the
18
+ Mojaloop Foundation for an example). Those individuals should have
19
+ their names indented and be marked with a '-'. Email address can be added
20
+ optionally within square brackets <email>.
21
+
22
+ * TigerBeetle
23
+ - Lewis Daly <lewis@tigerbeetle.com>
24
+ --------------
25
+
26
+ ******/
27
+
28
+ import { describe, it } from 'node:test'
29
+ import assert from 'node:assert'
30
+ import {
31
+ assertString,
32
+ assertNumber,
33
+ assertBoolean,
34
+ assertStringOrNull,
35
+ assertProxyCacheConfig,
36
+ defaultTo,
37
+ stringToBoolean,
38
+ convertBigIntToNumber,
39
+ safeStringToNumber,
40
+ assertRange,
41
+ assertNestedFields,
42
+ defaultEnvString,
43
+ assertKafkaConfig,
44
+ assertProvisioning,
45
+ kafkaWithBrokerDefaults,
46
+ deepMerge
47
+ } from './util'
48
+ import { KafkaConfig, KafkaConsumerConfig, KafkaProducerConfig } from './types'
49
+ import PRNG from '../../testing/prng'
50
+ import { deleteAtPath, enumeratePaths, replaceAtPath } from '../../testing/util'
51
+
52
+ describe('lib/config/util', () => {
53
+ describe('assertString', () => {
54
+ it('accepts a valid string', () => {
55
+ assert.doesNotThrow(() => assertString('hello'))
56
+ assert.doesNotThrow(() => assertString(''))
57
+ })
58
+
59
+ it('throws for non-string values', () => {
60
+ assert.throws(() => assertString(123), /expected 'string', instead found number/)
61
+ assert.throws(() => assertString(null), /expected 'string', instead found object/)
62
+ assert.throws(() => assertString(undefined), /expected 'string', instead found undefined/)
63
+ assert.throws(() => assertString({}), /expected 'string', instead found object/)
64
+ assert.throws(() => assertString([]), /expected 'string', instead found object/)
65
+ assert.throws(() => assertString(true), /expected 'string', instead found boolean/)
66
+ })
67
+ })
68
+
69
+ describe('assertNumber', () => {
70
+ it('accepts valid numbers', () => {
71
+ assert.doesNotThrow(() => assertNumber(123))
72
+ assert.doesNotThrow(() => assertNumber(0))
73
+ assert.doesNotThrow(() => assertNumber(-42))
74
+ assert.doesNotThrow(() => assertNumber(3.14))
75
+ })
76
+
77
+ it('throws for non-number values', () => {
78
+ assert.throws(() => assertNumber('123'), /expected 'number', instead found string/)
79
+ assert.throws(() => assertNumber(null), /expected 'number', instead found object/)
80
+ assert.throws(() => assertNumber(undefined), /expected 'number', instead found undefined/)
81
+ assert.throws(() => assertNumber({}), /expected 'number', instead found object/)
82
+ })
83
+
84
+ it('throws for NaN', () => {
85
+ assert.throws(() => assertNumber(Number.NaN), /expected 'number', instead found NaN/)
86
+ })
87
+ })
88
+
89
+ describe('assertBoolean', () => {
90
+ it('accepts valid booleans', () => {
91
+ assert.doesNotThrow(() => assertBoolean(true))
92
+ assert.doesNotThrow(() => assertBoolean(false))
93
+ })
94
+
95
+ it('throws for non-boolean values', () => {
96
+ assert.throws(() => assertBoolean('true'), /expected 'boolean', instead found string/)
97
+ assert.throws(() => assertBoolean(1), /expected 'boolean', instead found number/)
98
+ assert.throws(() => assertBoolean(0), /expected 'boolean', instead found number/)
99
+ assert.throws(() => assertBoolean(null), /expected 'boolean', instead found object/)
100
+ assert.throws(() => assertBoolean(undefined), /expected 'boolean', instead found undefined/)
101
+ })
102
+ })
103
+
104
+ describe('assertStringOrNull', () => {
105
+ it('accepts valid strings', () => {
106
+ assert.doesNotThrow(() => assertStringOrNull('hello'))
107
+ assert.doesNotThrow(() => assertStringOrNull(''))
108
+ })
109
+
110
+ it('accepts null', () => {
111
+ assert.doesNotThrow(() => assertStringOrNull(null))
112
+ })
113
+
114
+ it('throws for non-string, non-null values', () => {
115
+ assert.throws(() => assertStringOrNull(123), /expected 'string', instead found number/)
116
+ assert.throws(() => assertStringOrNull(undefined), /expected 'string', instead found undefined/)
117
+ assert.throws(() => assertStringOrNull({}), /expected 'string', instead found object/)
118
+ })
119
+ })
120
+
121
+ describe('assertProxyCacheConfig', () => {
122
+ it('accepts valid RedisClusterProxyCacheConfig', () => {
123
+ assert.doesNotThrow(() => assertProxyCacheConfig({
124
+ cluster: [{ host: 'redis', port: 8888 }]
125
+ }))
126
+ assert.doesNotThrow(() => assertProxyCacheConfig({
127
+ cluster: [
128
+ { host: 'redis-1', port: 8888 },
129
+ { host: 'redis-2', port: 8888 },
130
+ ]
131
+ }))
132
+ assert.doesNotThrow(() => assertProxyCacheConfig({
133
+ username: 'redis_user',
134
+ cluster: [
135
+ { host: 'redis-1', port: 8888 },
136
+ { host: 'redis-2', port: 8888 },
137
+ ]
138
+ }))
139
+ })
140
+
141
+ it('throws for invalid RedisClusterProxyCacheConfig', () => {
142
+ assert.throws(() => assertProxyCacheConfig({
143
+ cluster: []
144
+ }))
145
+ assert.throws(() => assertProxyCacheConfig({
146
+ cluster: [
147
+ { host: 'redis-1', port: 8888 },
148
+ { host: 'redis-2', port: '8888' },
149
+ ]
150
+ }))
151
+ assert.throws(() => assertProxyCacheConfig({
152
+ username: 'redis_user',
153
+ db: 'db',
154
+ cluster: [
155
+ { host: 'redis-1', port: 8888 },
156
+ { host: 'redis-2', port: 8888 },
157
+ ]
158
+ }))
159
+ })
160
+
161
+ it('accepts valid RedisProxyCacheConfig', () => {
162
+ assert.doesNotThrow(() => assertProxyCacheConfig({
163
+ host: 'redis', port: 8000
164
+ }))
165
+ assert.doesNotThrow(() => assertProxyCacheConfig({
166
+ host: 'redis', port: 8000, username: 'testing'
167
+ }))
168
+ })
169
+
170
+ it('throws for invalid RedisProxyCacheConfig', () => {
171
+ assert.throws(() => assertProxyCacheConfig({
172
+ host: 'redis', port: '8000'
173
+ }))
174
+ assert.throws(() => assertProxyCacheConfig({
175
+ host: 'redis', port: 8000, username: 1234
176
+ }))
177
+ assert.throws(() => assertProxyCacheConfig({
178
+ host: 'redis', port: 8000, username: 'testing', lazyConnect: 'false'
179
+ }))
180
+ assert.throws(() => assertProxyCacheConfig({
181
+ host: 'redis', port: 8000, username: 'testing', lazyConnect: false, db: '2'
182
+ }))
183
+ })
184
+
185
+ it('accepts valid MySqlProxyCacheConfig', () => {
186
+ assert.doesNotThrow(() => assertProxyCacheConfig({
187
+ host: 'mysql', port: 8000
188
+ }))
189
+ assert.doesNotThrow(() => assertProxyCacheConfig({
190
+ host: 'redis', port: 8000, user: 'testing'
191
+ }))
192
+ })
193
+
194
+ it('throws for invalid MySqlProxyCacheConfig', () => {
195
+ assert.throws(() => assertProxyCacheConfig({
196
+ host: 'mysql',
197
+ }))
198
+ assert.throws(() => assertProxyCacheConfig({
199
+ host: 'mysql', port: 8000, database: 12345
200
+ }))
201
+ assert.throws(() => assertProxyCacheConfig({
202
+ host: 'mysql', port: 8000, user: 12345
203
+ }))
204
+ })
205
+ })
206
+
207
+ describe('defaultTo', () => {
208
+ it('returns the input if defined', () => {
209
+ assert.strictEqual(defaultTo('value', 'default'), 'value')
210
+ assert.strictEqual(defaultTo(42, 0), 42)
211
+ assert.strictEqual(defaultTo(false, true), false)
212
+ })
213
+
214
+ it('returns the default if input is undefined', () => {
215
+ assert.strictEqual(defaultTo(undefined, 'default'), 'default')
216
+ assert.strictEqual(defaultTo(undefined, 42), 42)
217
+ assert.strictEqual(defaultTo(undefined, true), true)
218
+ })
219
+
220
+ it('throws if input type does not match default type', () => {
221
+ assert.throws(() => defaultTo('string', 123))
222
+ assert.throws(() => defaultTo(123, 'string'))
223
+ })
224
+ })
225
+
226
+ describe('stringToBoolean', () => {
227
+ it('converts `true` to true (case insensitive)', () => {
228
+ assert.strictEqual(stringToBoolean('true'), true)
229
+ assert.strictEqual(stringToBoolean('TRUE'), true)
230
+ assert.strictEqual(stringToBoolean('True'), true)
231
+ })
232
+
233
+ it('converts `false` to false (case insensitive)', () => {
234
+ assert.strictEqual(stringToBoolean('false'), false)
235
+ assert.strictEqual(stringToBoolean('FALSE'), false)
236
+ assert.strictEqual(stringToBoolean('False'), false)
237
+ })
238
+
239
+ it('throws for invalid input', () => {
240
+ assert.throws(() => stringToBoolean('yes'), /unknown input/)
241
+ assert.throws(() => stringToBoolean('no'), /unknown input/)
242
+ assert.throws(() => stringToBoolean('1'), /unknown input/)
243
+ assert.throws(() => stringToBoolean('0'), /unknown input/)
244
+ assert.throws(() => stringToBoolean(''), /unknown input/)
245
+ })
246
+ })
247
+
248
+ describe('convertBigIntToNumber', () => {
249
+ it('converts bigint within safe range', () => {
250
+ assert.strictEqual(convertBigIntToNumber(BigInt(123)), 123)
251
+ assert.strictEqual(convertBigIntToNumber(BigInt(0)), 0)
252
+ assert.strictEqual(convertBigIntToNumber(BigInt(-42)), -42)
253
+ assert.strictEqual(convertBigIntToNumber(BigInt(Number.MAX_SAFE_INTEGER)), Number.MAX_SAFE_INTEGER)
254
+ assert.strictEqual(convertBigIntToNumber(BigInt(Number.MIN_SAFE_INTEGER)), Number.MIN_SAFE_INTEGER)
255
+ })
256
+
257
+ it('throws for bigint outside safe range', () => {
258
+ assert.throws(
259
+ () => convertBigIntToNumber(BigInt(Number.MAX_SAFE_INTEGER) + BigInt(1)),
260
+ /input is outside of safe range/
261
+ )
262
+ assert.throws(
263
+ () => convertBigIntToNumber(BigInt(Number.MIN_SAFE_INTEGER) - BigInt(1)),
264
+ /input is outside of safe range/
265
+ )
266
+ })
267
+ })
268
+
269
+ describe('safeStringToNumber', () => {
270
+ it('converts valid number strings', () => {
271
+ assert.strictEqual(safeStringToNumber('123'), 123)
272
+ assert.strictEqual(safeStringToNumber('0'), 0)
273
+ assert.strictEqual(safeStringToNumber('-42'), -42)
274
+ assert.strictEqual(safeStringToNumber('3.14'), 3.14)
275
+ assert.strictEqual(safeStringToNumber(' 123 '), 123)
276
+ })
277
+
278
+ it('throws for invalid number strings', () => {
279
+ assert.throws(() => safeStringToNumber(''), /Invalid number string/)
280
+ assert.throws(() => safeStringToNumber('abc'), /Invalid number string/)
281
+ assert.throws(() => safeStringToNumber('12abc'), /Invalid number string/)
282
+ assert.throws(() => safeStringToNumber(' '), /Invalid number string/)
283
+ })
284
+
285
+ it('throws for infinity', () => {
286
+ assert.throws(() => safeStringToNumber('Infinity'), /Number out of range/)
287
+ assert.throws(() => safeStringToNumber('-Infinity'), /Number out of range/)
288
+ })
289
+ })
290
+
291
+ describe('assertRange', () => {
292
+ it('accepts numbers within range', () => {
293
+ assert.doesNotThrow(() => assertRange(5, 0, 10))
294
+ assert.doesNotThrow(() => assertRange(0, 0, 10))
295
+ assert.doesNotThrow(() => assertRange(10, 0, 10))
296
+ assert.doesNotThrow(() => assertRange(-5, -10, 0))
297
+ })
298
+
299
+ it('throws for numbers outside range', () => {
300
+ assert.throws(() => assertRange(-1, 0, 10), /valid range/)
301
+ assert.throws(() => assertRange(11, 0, 10), /valid range/)
302
+ })
303
+
304
+ it('throws for non-numbers', () => {
305
+ assert.throws(() => assertRange('5' as any, 0, 10))
306
+ })
307
+
308
+ it('throws for invalid range arguments', () => {
309
+ assert.throws(() => assertRange(5, 10, 0), /expected maxInclusive > minInclusive/)
310
+ })
311
+ })
312
+
313
+ describe('assertNestedFields', () => {
314
+ it('passes for existing top-level field', () => {
315
+ const config = { DATABASE: { HOST: 'localhost' } }
316
+ assert.doesNotThrow(() => assertNestedFields(config, 'DATABASE'))
317
+ })
318
+
319
+ it('passes for existing nested fields', () => {
320
+ const config = {
321
+ HANDLERS: {
322
+ API: {
323
+ DISABLED: false
324
+ }
325
+ }
326
+ }
327
+ assert.doesNotThrow(() => assertNestedFields(config, 'HANDLERS'))
328
+ assert.doesNotThrow(() => assertNestedFields(config, 'HANDLERS.API'))
329
+ assert.doesNotThrow(() => assertNestedFields(config, 'HANDLERS.API.DISABLED'))
330
+ })
331
+
332
+ it('throws for missing top-level field', () => {
333
+ const config = { DATABASE: {} }
334
+ assert.throws(() => assertNestedFields(config, 'MISSING'), /expected `MISSING` to be defined/)
335
+ })
336
+
337
+ it('throws for missing nested field', () => {
338
+ const config = { HANDLERS: {} }
339
+ assert.throws(() => assertNestedFields(config, 'HANDLERS.API'), /expected `HANDLERS.API` to be defined/)
340
+ })
341
+
342
+ it('throws for deeply missing nested field', () => {
343
+ const config = { HANDLERS: { API: {} } }
344
+ assert.throws(
345
+ () => assertNestedFields(config, 'HANDLERS.API.MISSING'),
346
+ /expected `HANDLERS.API.MISSING` to be defined/
347
+ )
348
+ })
349
+ })
350
+
351
+ describe('assertKafkaConfig', () => {
352
+ const prng = new PRNG(100)
353
+
354
+ it('passes for a valid kafka config', () => {
355
+ // Base config is valid.
356
+ const valid = makeBaseConfig()
357
+
358
+ assert.doesNotThrow(() => assertKafkaConfig(valid))
359
+ })
360
+
361
+ /**
362
+ * Fuzz test for Kafka Config.
363
+ * Mutates the Kafka Config and checks if it remains valid.
364
+ */
365
+ type MutationOption = 'DELETE_ELEMENT' | 'MUTATE_ACTION_TOPIC_MAP'
366
+ const mutationOptions: Record<string, number> = {
367
+ 'DELETE_ELEMENT': 5,
368
+ 'MUTATE_ACTION_TOPIC_MAP': 1
369
+ }
370
+ const weightedMutationOptions = Object.keys(mutationOptions).reduce((acc, curr: string) => {
371
+ const occurrences = mutationOptions[curr]
372
+ acc = acc.concat(new Array(occurrences).fill((curr as unknown as MutationOption), 0, occurrences))
373
+ return acc
374
+ }, [] as Array<MutationOption>)
375
+
376
+ const mutateConfigKafka = (input: any, times: number): [any, 'FAIL' | 'PASS', any] => {
377
+ assert(times > 0, 'Expected times to be a positive integer')
378
+ const choices: Array<string> = []
379
+ // By default we expect to start with a valid config.
380
+ let expectPassOrFail: 'FAIL' | 'PASS' = 'PASS' as unknown as 'FAIL' | 'PASS'
381
+ const mutatablePaths = enumeratePaths(input).filter(path =>
382
+ path.match(/EVENT_TYPE_ACTION_TOPIC_MAP\|POSITION\|/)
383
+ )
384
+
385
+ const _handleDeleteElement = (input: any): void => {
386
+ const paths = enumeratePaths(input)
387
+ const path = prng.randomElementFrom(paths)
388
+
389
+ // Mark this path as uneligible to be mutated in future rounds.
390
+ const idxPath = mutatablePaths.indexOf(path)
391
+ if (idxPath >= 0) {
392
+ mutatablePaths.splice(idxPath, 1)
393
+ }
394
+
395
+ deleteAtPath(input, path)
396
+ choices.push(`DELETE_ELEMENT: ${path}`)
397
+
398
+ // Once it's failed, it can't be saved.
399
+ if (expectPassOrFail === 'FAIL') return
400
+
401
+ // Non fatal - final leaf nodes on the consumer or producer options.
402
+ if (path.match(/config\|(options|rdkafkaConf|topicConf)\|(.*)/)) return
403
+
404
+ const elements = path.split('|')
405
+ if (elements.length >= 1 && elements.length <= 5) {
406
+ expectPassOrFail = 'FAIL'
407
+ }
408
+ }
409
+
410
+ const _handleMutateActionTopicMap = (input: any): void => {
411
+ if (!input.EVENT_TYPE_ACTION_TOPIC_MAP || !input.EVENT_TYPE_ACTION_TOPIC_MAP.POSITION) {
412
+ // We can't mutate these fields, they've been deleted.
413
+ return
414
+ }
415
+ if (mutatablePaths.length === 0) return
416
+
417
+ const path = prng.randomElementFrom(mutatablePaths)
418
+
419
+ // Make sure this path doesn't get mutated again by removing from the array.
420
+ const idxPath = mutatablePaths.indexOf(path)
421
+ assert(idxPath >= 0)
422
+ mutatablePaths.splice(idxPath, 1)
423
+
424
+ const newValue = prng.randomElementFrom([null, 'test-string', undefined, 1323])
425
+ replaceAtPath(input, path, newValue)
426
+ choices.push(`MUTATE_ACTION_TOPIC_MAP: ${path} : newValue: ${newValue}`)
427
+
428
+ if (expectPassOrFail === 'FAIL') return
429
+
430
+ // Null or string values are valid.
431
+ if (newValue === null || newValue === 'test-string') return
432
+
433
+ expectPassOrFail = 'FAIL'
434
+ }
435
+
436
+ const _mutateConfig = (input: any): any => {
437
+ const choice = prng.randomElementFrom(weightedMutationOptions)
438
+
439
+ switch (choice) {
440
+ case 'DELETE_ELEMENT':
441
+ _handleDeleteElement(input)
442
+ break
443
+ case 'MUTATE_ACTION_TOPIC_MAP':
444
+ _handleMutateActionTopicMap(input)
445
+ break
446
+ }
447
+
448
+ return input
449
+ }
450
+
451
+ for (let idx = times; idx >= 0; idx--) {
452
+ input = _mutateConfig(input)
453
+ }
454
+ return [input, expectPassOrFail, choices]
455
+ }
456
+
457
+ it('fuzz: fails for an invalid kafka config', () => {
458
+ const ITERATIONS = 100
459
+ for (let idx = 0; idx < ITERATIONS; idx++) {
460
+ const baseConfig = makeBaseConfig()
461
+ const [mutated, expectPassOrFail, choices] = mutateConfigKafka(baseConfig, 4)
462
+ switch (expectPassOrFail) {
463
+ // Mutation has made config invalid.
464
+ case 'FAIL': {
465
+ try {
466
+ assertKafkaConfig(mutated)
467
+ throw new Error(`Test Error`)
468
+ } catch (err: any) {
469
+ if (err.message === 'Test Error') {
470
+ console.error(`Expected mutated config to fail validation: (iteration: ${idx}).`)
471
+ console.error(`Choices: \n\t${choices.join('\n\t')}`)
472
+ throw err
473
+ }
474
+ }
475
+ break
476
+ }
477
+ // Mutation hasn't made config invalid.
478
+ case 'PASS': {
479
+ assert.doesNotThrow(
480
+ () => assertKafkaConfig(mutated),
481
+ `Expected mutated kafka config to pass validation: (iteration: ${idx}).`
482
+ )
483
+ }
484
+ }
485
+ }
486
+ })
487
+ })
488
+
489
+ describe('kafkaWithBrokerDefaults', () => {
490
+ it('overrides the broker defaults', () => {
491
+ // Arrange
492
+ const base = makeBaseConfig()
493
+
494
+ // Delete some of these so they get overriden.
495
+ deleteAtPath(base, 'CONSUMER|BULK|PREPARE|config|rdkafkaConf|metadata.broker.list')
496
+ deleteAtPath(base, 'CONSUMER|TRANSFER|PREPARE|config|rdkafkaConf|metadata.broker.list')
497
+ deleteAtPath(base, 'CONSUMER|ADMIN|TRANSFER|config|rdkafkaConf|metadata.broker.list')
498
+
499
+ // Act
500
+ const overriden = kafkaWithBrokerDefaults(base, 'testing-1234')
501
+
502
+ // Assert
503
+
504
+ // Modified fields:
505
+ assert.strictEqual(
506
+ overriden.CONSUMER.BULK.PREPARE.config.rdkafkaConf['metadata.broker.list'],
507
+ 'testing-1234'
508
+ )
509
+ assert.strictEqual(
510
+ overriden.CONSUMER.TRANSFER.PREPARE.config.rdkafkaConf['metadata.broker.list'],
511
+ 'testing-1234'
512
+ )
513
+ assert.strictEqual(
514
+ overriden.CONSUMER.ADMIN.TRANSFER.config.rdkafkaConf['metadata.broker.list'],
515
+ 'testing-1234'
516
+ )
517
+
518
+ // Unmodified fields:
519
+ assert.strictEqual(
520
+ overriden.PRODUCER.ADMIN.TRANSFER.config.rdkafkaConf['metadata.broker.list'],
521
+ 'localhost:9092'
522
+ )
523
+ })
524
+ })
525
+
526
+ describe('assertProvisioning', () => {
527
+ it('passes for a valid config', () => {
528
+ assert.doesNotThrow(() => assertProvisioning({
529
+ enabled: true,
530
+ currencies: ['KES']
531
+ }))
532
+ assert.doesNotThrow(() => assertProvisioning({
533
+ enabled: true, currencies: ['KES']
534
+ }))
535
+ assert.doesNotThrow(() => assertProvisioning({
536
+ enabled: false, currencies: []
537
+ }))
538
+ assert.doesNotThrow(() => assertProvisioning({
539
+ enabled: true, currencies: ['ABC'], hubAlertEmailAddress: 'test_email'
540
+ }))
541
+ })
542
+
543
+ it('fails for invalid config', () => {
544
+ assert.throws(() => assertProvisioning(undefined as unknown))
545
+ assert.throws(() => assertProvisioning({
546
+ enabled: undefined,
547
+ currencies: ['KES']
548
+ }))
549
+ assert.throws(() => assertProvisioning({
550
+ enabled: true, currencies: []
551
+ }))
552
+ assert.throws(() => assertProvisioning({
553
+ enabled: true, currencies: [1234]
554
+ }))
555
+ assert.throws(() => assertProvisioning({
556
+ enabled: true, currencies: ['ABC'], hubAlertEmailAddress: 12345
557
+ }))
558
+ })
559
+ })
560
+
561
+ describe('defaultEnvString', () => {
562
+ it('handles a default value of `false`', () => {
563
+ const result = defaultEnvString({}, 'LOG_ENABLED', 'false')
564
+ assert.strictEqual(result, 'false')
565
+
566
+ assert.strictEqual(
567
+ defaultEnvString({ LOG_ENABLED: 'true' }, 'LOG_ENABLED', 'false'),
568
+ 'true'
569
+ )
570
+
571
+ assert.strictEqual(
572
+ defaultEnvString({ LEDGER: ['TIGERBEETLE'] } as unknown as NodeJS.ProcessEnv, 'LEDGER', 'false'),
573
+ 'TIGERBEETLE'
574
+ )
575
+
576
+ assert.strictEqual(
577
+ defaultEnvString({ LOG_ENABLED: undefined }, 'LOG_ENABLED', 'false'),
578
+ 'false'
579
+ )
580
+
581
+ assert.strictEqual(
582
+ defaultEnvString({ LEDGER: [undefined] } as unknown as NodeJS.ProcessEnv, 'LEDGER', 'MYSQL'),
583
+ 'MYSQL'
584
+ )
585
+ })
586
+
587
+ it('throws on invalid input', () => {
588
+ assert.throws(() => defaultEnvString(undefined as unknown as NodeJS.ProcessEnv, 'LOG_ENABLED', 'false'))
589
+ assert.throws(() => defaultEnvString({}, false as unknown as string, 'false'))
590
+ assert.throws(() => defaultEnvString({}, undefined as unknown as string, 'false'))
591
+ assert.throws(() => defaultEnvString({}, undefined as unknown as string, 'false'))
592
+ assert.throws(() => defaultEnvString({}, 'LOG_ENABLED', undefined as unknown as string))
593
+ assert.throws(() => defaultEnvString({}, 'LOG_ENABLED', {} as unknown as string))
594
+ })
595
+ })
596
+
597
+ describe('deepMerge', () => {
598
+ it('merges top-level', () => {
599
+ const target = { a: 1, b: 2 }
600
+ const source = { b: 3, c: 4 }
601
+ const result = deepMerge(target, source)
602
+
603
+ assert.deepStrictEqual(result, { a: 1, b: 3, c: 4 })
604
+ assert.strictEqual(result, target)
605
+ })
606
+
607
+ it('deep merges nested objects', () => {
608
+ const target: any = {
609
+ config: {
610
+ host: 'localhost',
611
+ port: 3000,
612
+ options: { timeout: 5000 }
613
+ }
614
+ }
615
+ const source: any = {
616
+ config: {
617
+ port: 8080,
618
+ options: { retries: 3 }
619
+ }
620
+ }
621
+ const result = deepMerge(target, source)
622
+
623
+ assert.deepStrictEqual(result, {
624
+ config: {
625
+ host: 'localhost',
626
+ port: 8080,
627
+ options: { timeout: 5000, retries: 3 }
628
+ }
629
+ })
630
+ })
631
+
632
+ it('replaces arrays', () => {
633
+ const target = { items: [1, 2, 3], name: 'test' }
634
+ const source = { items: [4, 5] }
635
+ const result = deepMerge(target, source)
636
+
637
+ assert.deepStrictEqual(result, { items: [4, 5], name: 'test' })
638
+ })
639
+
640
+ it('handles nulls in source', () => {
641
+ const target = { a: { nested: 'value' }, b: 2 }
642
+ const source = { a: null }
643
+ const result = deepMerge(target, source as any)
644
+
645
+ assert.deepStrictEqual(result, { a: null, b: 2 })
646
+ })
647
+
648
+ it('handles nulls in target', () => {
649
+ const target: any = { a: null, b: 2 }
650
+ const source: any = { a: { nested: 'value' } }
651
+ const result = deepMerge(target, source)
652
+
653
+ assert.deepStrictEqual(result, { a: { nested: 'value' }, b: 2 })
654
+ })
655
+
656
+ it('works with empty source', () => {
657
+ const target = { a: 1, b: { c: 2 } }
658
+ const source = {}
659
+ const result = deepMerge(target, source)
660
+
661
+ assert.deepStrictEqual(result, { a: 1, b: { c: 2 } })
662
+ })
663
+
664
+ it('works with deeply nested structures', () => {
665
+ const target = {
666
+ level1: {
667
+ level2: {
668
+ level3: {
669
+ value: 'original',
670
+ keep: 'this'
671
+ }
672
+ }
673
+ }
674
+ }
675
+ const source = {
676
+ level1: {
677
+ level2: {
678
+ level3: {
679
+ value: 'updated'
680
+ }
681
+ }
682
+ }
683
+ }
684
+ const result = deepMerge(target, source)
685
+
686
+ assert.deepStrictEqual(result, {
687
+ level1: {
688
+ level2: {
689
+ level3: {
690
+ value: 'updated',
691
+ keep: 'this'
692
+ }
693
+ }
694
+ }
695
+ })
696
+ })
697
+
698
+ it('returns the modified target', () => {
699
+ const target = { a: 1 }
700
+ const source: any = { b: 2 }
701
+ const result = deepMerge(target, source)
702
+
703
+ assert.strictEqual(result, target)
704
+ })
705
+ })
706
+ })
707
+
708
+ const makeBaseConfig = (): KafkaConfig => {
709
+ return {
710
+ EVENT_TYPE_ACTION_TOPIC_MAP: {
711
+ POSITION: {
712
+ PREPARE: null,
713
+ FX_PREPARE: 'topic-transfer-position-batch',
714
+ BULK_PREPARE: null,
715
+ COMMIT: null,
716
+ BULK_COMMIT: null,
717
+ RESERVE: null,
718
+ FX_RESERVE: 'topic-transfer-position-batch',
719
+ TIMEOUT_RESERVED: null,
720
+ FX_TIMEOUT_RESERVED: 'topic-transfer-position-batch',
721
+ ABORT: null,
722
+ FX_ABORT: 'topic-transfer-position-batch'
723
+ }
724
+ },
725
+ TOPIC_TEMPLATES: {
726
+ PARTICIPANT_TOPIC_TEMPLATE: {
727
+ TEMPLATE: 'topic-{{participantName}}-{{functionality}}-{{action}}',
728
+ REGEX: 'topic-(.*)-(.*)-(.*)'
729
+ },
730
+ GENERAL_TOPIC_TEMPLATE: {
731
+ TEMPLATE: 'topic-{{functionality}}-{{action}}',
732
+ REGEX: 'topic-(.*)-(.*)'
733
+ }
734
+ },
735
+ CONSUMER: {
736
+ BULK: {
737
+ PREPARE: generateValidConsumer(),
738
+ PROCESSING: generateValidConsumer(),
739
+ FULFIL: generateValidConsumer(),
740
+ GET: generateValidConsumer(),
741
+ },
742
+ TRANSFER: {
743
+ PREPARE: generateValidConsumer(),
744
+ GET: generateValidConsumer(),
745
+ FULFIL: generateValidConsumer(),
746
+ POSITION: generateValidConsumer(),
747
+ POSITION_BATCH: generateValidConsumer(),
748
+ },
749
+ ADMIN: {
750
+ TRANSFER: generateValidConsumer()
751
+ },
752
+ NOTIFICATION: {
753
+ EVENT: generateValidConsumer()
754
+ },
755
+ DEFERREDSETTLEMENT: {
756
+ CLOSE: generateValidConsumer()
757
+ }
758
+ },
759
+ PRODUCER: {
760
+ BULK: {
761
+ PROCESSING: generateValidProducer()
762
+ },
763
+ TRANSFER: {
764
+ PREPARE: generateValidProducer(),
765
+ FULFIL: generateValidProducer(),
766
+ POSITION: generateValidProducer(),
767
+ },
768
+ NOTIFICATION: {
769
+ EVENT: generateValidProducer(),
770
+ },
771
+ ADMIN: {
772
+ TRANSFER: generateValidProducer(),
773
+ }
774
+ }
775
+ }
776
+ }
777
+
778
+ const generateValidConsumer = (): KafkaConsumerConfig => {
779
+ return {
780
+ config: {
781
+ options: {
782
+ mode: 2,
783
+ batchSize: 1,
784
+ pollFrequency: 10,
785
+ recursiveTimeout: 100,
786
+ messageCharset: 'utf8',
787
+ messageAsJSON: true,
788
+ sync: true,
789
+ consumeTimeout: 1000
790
+ },
791
+ rdkafkaConf: {
792
+ 'client.id': 'test-client-id',
793
+ 'group.id': 'test-group-id',
794
+ 'metadata.broker.list': 'localhost:9092',
795
+ 'socket.keepalive.enable': true,
796
+ 'allow.auto.create.topics': true
797
+ },
798
+ topicConf: {
799
+ 'auto.offset.reset': 'earliest'
800
+ }
801
+ }
802
+ }
803
+ }
804
+
805
+ const generateValidProducer = (): KafkaProducerConfig => {
806
+ return {
807
+ config: {
808
+ options: {
809
+ messageCharset: 'utf8'
810
+ },
811
+ rdkafkaConf: {
812
+ 'metadata.broker.list': 'localhost:9092',
813
+ 'client.id': 'test-client-id',
814
+ 'event_cb': true,
815
+ 'dr_cb': true,
816
+ 'socket.keepalive.enable': true,
817
+ 'queue.buffering.max.messages': 10000000
818
+ },
819
+ topicConf: {
820
+ 'request.required.acks': 'all',
821
+ 'partitioner': 'murmur2_random'
822
+ }
823
+ }
824
+ }
825
+ }