@actual-app/sync-server 25.5.0 → 25.6.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 (244) hide show
  1. package/{app.js → build/app.js} +4 -5
  2. package/build/bin/actual-server.js +101 -0
  3. package/build/migrations/1694360000000-create-folders.js +21 -0
  4. package/{migrations → build/migrations}/1694360479680-create-account-db.js +2 -4
  5. package/{migrations → build/migrations}/1694362247011-create-secret-table.js +2 -4
  6. package/build/migrations/1702667624000-rename-nordigen-secrets.js +9 -0
  7. package/{migrations → build/migrations}/1718889148000-openid.js +4 -10
  8. package/{migrations → build/migrations}/1719409568000-multiuser.js +10 -26
  9. package/build/src/account-db.js +182 -0
  10. package/build/src/accounts/openid.js +287 -0
  11. package/build/src/accounts/password.js +98 -0
  12. package/build/src/app-account.js +125 -0
  13. package/build/src/app-admin.js +317 -0
  14. package/build/src/app-admin.test.js +303 -0
  15. package/build/src/app-gocardless/app-gocardless.js +193 -0
  16. package/build/src/app-gocardless/bank-factory.js +84 -0
  17. package/build/src/app-gocardless/banks/abanca_caglesmm.js +17 -0
  18. package/build/src/app-gocardless/banks/abnamro_abnanl2a.js +37 -0
  19. package/build/src/app-gocardless/banks/american_express_aesudef1.js +32 -0
  20. package/build/src/app-gocardless/banks/bancsabadell_bsabesbbb.js +22 -0
  21. package/build/src/app-gocardless/banks/bank.interface.js +1 -0
  22. package/build/src/app-gocardless/banks/bank_of_ireland_b365_bofiie2d.js +25 -0
  23. package/build/src/app-gocardless/banks/bankinter_bkbkesmm.js +18 -0
  24. package/build/src/app-gocardless/banks/belfius_gkccbebb.js +13 -0
  25. package/build/src/app-gocardless/banks/berliner_sparkasse_beladebexxx.js +48 -0
  26. package/build/src/app-gocardless/banks/bnp_be_gebabebb.js +64 -0
  27. package/build/src/app-gocardless/banks/boursobank_bousfrppxxx.js +73 -0
  28. package/build/src/app-gocardless/banks/cbc_cregbebb.js +27 -0
  29. package/build/src/app-gocardless/banks/commerzbank_cobadeff.js +43 -0
  30. package/build/src/app-gocardless/banks/danskebank_dabno22.js +26 -0
  31. package/build/src/app-gocardless/banks/direkt_heladef1822.js +13 -0
  32. package/build/src/app-gocardless/banks/easybank_bawaatww.js +42 -0
  33. package/build/src/app-gocardless/banks/entercard_swednokk.js +28 -0
  34. package/build/src/app-gocardless/banks/fortuneo_ftnofrp1xxx.js +34 -0
  35. package/build/src/app-gocardless/banks/hype_hyeeit22.js +63 -0
  36. package/build/src/app-gocardless/banks/ing_ingbrobu.js +56 -0
  37. package/build/src/app-gocardless/banks/ing_ingddeff.js +34 -0
  38. package/build/src/app-gocardless/banks/ing_pl_ingbplpw.js +29 -0
  39. package/build/src/app-gocardless/banks/integration-bank.js +78 -0
  40. package/build/src/app-gocardless/banks/isybank_itbbitmm.js +13 -0
  41. package/build/src/app-gocardless/banks/kbc_kredbebb.js +26 -0
  42. package/build/src/app-gocardless/banks/lhv-lhvbee22.js +24 -0
  43. package/build/src/app-gocardless/banks/mbank_retail_brexplpw.js +41 -0
  44. package/build/src/app-gocardless/banks/nationwide_naiagb21.js +32 -0
  45. package/build/src/app-gocardless/banks/nbg_ethngraaxxx.js +39 -0
  46. package/build/src/app-gocardless/banks/norwegian_xx_norwnok1.js +61 -0
  47. package/build/src/app-gocardless/banks/revolut_revolt21.js +20 -0
  48. package/build/src/app-gocardless/banks/sandboxfinance_sfin0000.js +21 -0
  49. package/build/src/app-gocardless/banks/seb_kort_bank_ab.js +63 -0
  50. package/build/src/app-gocardless/banks/seb_privat.js +19 -0
  51. package/build/src/app-gocardless/banks/sparnord_spnodk22.js +19 -0
  52. package/build/src/app-gocardless/banks/spk_karlsruhe_karsde66.js +48 -0
  53. package/build/src/app-gocardless/banks/spk_marburg_biedenkopf_heladef1mar.js +25 -0
  54. package/build/src/app-gocardless/banks/spk_worms_alzey_ried_malade51wor.js +14 -0
  55. package/build/src/app-gocardless/banks/ssk_dusseldorf_dussdeddxxx.js +36 -0
  56. package/build/src/app-gocardless/banks/swedbank_habalv22.js +30 -0
  57. package/build/src/app-gocardless/banks/tests/abanca_caglesmm.spec.js +17 -0
  58. package/build/src/app-gocardless/banks/tests/abnamro_abnanl2a.spec.js +45 -0
  59. package/build/src/app-gocardless/banks/tests/bancsabadell_bsabesbbb.spec.js +41 -0
  60. package/build/src/app-gocardless/banks/tests/belfius_gkccbebb.spec.js +16 -0
  61. package/build/src/app-gocardless/banks/tests/boursobank_bousfrppxxx.spec.js +102 -0
  62. package/build/src/app-gocardless/banks/tests/cbc_cregbebb.spec.js +24 -0
  63. package/build/src/app-gocardless/banks/tests/commerzbank_cobadeff.spec.js +105 -0
  64. package/build/src/app-gocardless/banks/tests/easybank_bawaatww.spec.js +36 -0
  65. package/build/src/app-gocardless/banks/tests/fortuneo_ftnofrp1xxx.spec.js +159 -0
  66. package/build/src/app-gocardless/banks/tests/ing_ingddeff.spec.js +267 -0
  67. package/build/src/app-gocardless/banks/tests/ing_pl_ingbplpw.spec.js +186 -0
  68. package/build/src/app-gocardless/banks/tests/integration_bank.spec.js +127 -0
  69. package/build/src/app-gocardless/banks/tests/kbc_kredbebb.spec.js +24 -0
  70. package/build/src/app-gocardless/banks/tests/lhv-lhvbee22.spec.js +50 -0
  71. package/build/src/app-gocardless/banks/tests/mbank_retail_brexplpw.spec.js +156 -0
  72. package/build/src/app-gocardless/banks/tests/nationwide_naiagb21.spec.js +64 -0
  73. package/build/src/app-gocardless/banks/tests/nbg_ethngraaxxx.spec.js +36 -0
  74. package/build/src/app-gocardless/banks/tests/revolut_revolt21.spec.js +30 -0
  75. package/build/src/app-gocardless/banks/tests/sandboxfinance_sfin0000.spec.js +112 -0
  76. package/build/src/app-gocardless/banks/tests/spk_marburg_biedenkopf_heladef1mar.spec.js +214 -0
  77. package/build/src/app-gocardless/banks/tests/ssk_dusseldorf_dussdeddxxx.spec.js +60 -0
  78. package/build/src/app-gocardless/banks/tests/swedbank_habalv22.spec.js +45 -0
  79. package/build/src/app-gocardless/banks/tests/virgin_nrnbgb22.spec.js +36 -0
  80. package/{src → build/src}/app-gocardless/banks/util/escape-regexp.js +1 -1
  81. package/{src → build/src}/app-gocardless/banks/util/extract-payeeName-from-remittanceInfo.js +11 -16
  82. package/build/src/app-gocardless/banks/virgin_nrnbgb22.js +31 -0
  83. package/build/src/app-gocardless/errors.js +67 -0
  84. package/build/src/app-gocardless/gocardless-node.types.js +1 -0
  85. package/build/src/app-gocardless/gocardless.types.js +1 -0
  86. package/build/src/app-gocardless/services/gocardless-service.js +504 -0
  87. package/build/src/app-gocardless/services/tests/fixtures.js +165 -0
  88. package/build/src/app-gocardless/services/tests/gocardless-service.spec.js +387 -0
  89. package/build/src/app-gocardless/tests/bank-factory.spec.js +13 -0
  90. package/build/src/app-gocardless/tests/utils.spec.js +158 -0
  91. package/build/src/app-gocardless/util/handle-error.js +15 -0
  92. package/build/src/app-gocardless/utils.js +41 -0
  93. package/build/src/app-openid.js +83 -0
  94. package/build/src/app-pluggyai/app-pluggyai.js +164 -0
  95. package/build/src/app-pluggyai/pluggyai-service.js +97 -0
  96. package/build/src/app-secrets.js +48 -0
  97. package/build/src/app-simplefin/app-simplefin.js +335 -0
  98. package/build/src/app-sync/errors.js +12 -0
  99. package/build/src/app-sync/services/files-service.js +158 -0
  100. package/build/src/app-sync/tests/services/files-service.test.js +192 -0
  101. package/build/src/app-sync/validation.js +65 -0
  102. package/build/src/app-sync.js +302 -0
  103. package/build/src/app-sync.test.js +655 -0
  104. package/build/src/app.js +138 -0
  105. package/build/src/config-types.js +1 -0
  106. package/build/src/db.js +50 -0
  107. package/build/src/load-config.js +274 -0
  108. package/build/src/migrations.js +23 -0
  109. package/build/src/scripts/disable-openid.js +31 -0
  110. package/build/src/scripts/enable-openid.js +36 -0
  111. package/build/src/scripts/health-check.js +16 -0
  112. package/build/src/scripts/reset-password.js +40 -0
  113. package/build/src/scripts/run-migrations.js +6 -0
  114. package/build/src/secrets.test.js +68 -0
  115. package/build/src/services/secrets-service.js +79 -0
  116. package/build/src/services/user-service.js +201 -0
  117. package/build/src/sync-simple.js +68 -0
  118. package/{src → build/src}/util/hash.js +1 -2
  119. package/build/src/util/middlewares.js +49 -0
  120. package/{src → build/src}/util/paths.js +3 -6
  121. package/build/src/util/payee-name.js +37 -0
  122. package/build/src/util/prompt.js +70 -0
  123. package/build/src/util/title/index.js +43 -0
  124. package/build/src/util/title/lower-case.js +90 -0
  125. package/build/src/util/title/specials.js +21 -0
  126. package/build/src/util/validate-user.js +55 -0
  127. package/package.json +32 -36
  128. package/bin/actual-server.js +0 -117
  129. package/migrations/1694360000000-create-folders.js +0 -25
  130. package/migrations/1702667624000-rename-nordigen-secrets.js +0 -19
  131. package/src/account-db.js +0 -239
  132. package/src/accounts/openid.js +0 -368
  133. package/src/accounts/password.js +0 -149
  134. package/src/app-account.js +0 -155
  135. package/src/app-admin.js +0 -410
  136. package/src/app-admin.test.js +0 -381
  137. package/src/app-gocardless/app-gocardless.js +0 -274
  138. package/src/app-gocardless/bank-factory.js +0 -91
  139. package/src/app-gocardless/banks/abanca_caglesmm.js +0 -22
  140. package/src/app-gocardless/banks/abnamro_abnanl2a.js +0 -57
  141. package/src/app-gocardless/banks/american_express_aesudef1.js +0 -40
  142. package/src/app-gocardless/banks/bancsabadell_bsabesbbb.js +0 -31
  143. package/src/app-gocardless/banks/bank.interface.ts +0 -51
  144. package/src/app-gocardless/banks/bank_of_ireland_b365_bofiie2d.js +0 -39
  145. package/src/app-gocardless/banks/bankinter_bkbkesmm.js +0 -24
  146. package/src/app-gocardless/banks/belfius_gkccbebb.js +0 -17
  147. package/src/app-gocardless/banks/berliner_sparkasse_beladebexxx.js +0 -61
  148. package/src/app-gocardless/banks/bnp_be_gebabebb.js +0 -73
  149. package/src/app-gocardless/banks/cbc_cregbebb.js +0 -34
  150. package/src/app-gocardless/banks/commerzbank_cobadeff.js +0 -54
  151. package/src/app-gocardless/banks/danskebank_dabno22.js +0 -39
  152. package/src/app-gocardless/banks/direkt_heladef1822.js +0 -18
  153. package/src/app-gocardless/banks/easybank_bawaatww.js +0 -50
  154. package/src/app-gocardless/banks/entercard_swednokk.js +0 -40
  155. package/src/app-gocardless/banks/fortuneo_ftnofrp1xxx.js +0 -46
  156. package/src/app-gocardless/banks/hype_hyeeit22.js +0 -74
  157. package/src/app-gocardless/banks/ing_ingbrobu.js +0 -70
  158. package/src/app-gocardless/banks/ing_ingddeff.js +0 -47
  159. package/src/app-gocardless/banks/ing_pl_ingbplpw.js +0 -46
  160. package/src/app-gocardless/banks/integration-bank.js +0 -115
  161. package/src/app-gocardless/banks/isybank_itbbitmm.js +0 -18
  162. package/src/app-gocardless/banks/kbc_kredbebb.js +0 -33
  163. package/src/app-gocardless/banks/lhv-lhvbee22.js +0 -36
  164. package/src/app-gocardless/banks/mbank_retail_brexplpw.js +0 -56
  165. package/src/app-gocardless/banks/nationwide_naiagb21.js +0 -46
  166. package/src/app-gocardless/banks/nbg_ethngraaxxx.js +0 -51
  167. package/src/app-gocardless/banks/norwegian_xx_norwnok1.js +0 -74
  168. package/src/app-gocardless/banks/revolut_revolt21.js +0 -37
  169. package/src/app-gocardless/banks/sandboxfinance_sfin0000.js +0 -28
  170. package/src/app-gocardless/banks/seb_kort_bank_ab.js +0 -59
  171. package/src/app-gocardless/banks/seb_privat.js +0 -29
  172. package/src/app-gocardless/banks/sparnord_spnodk22.js +0 -24
  173. package/src/app-gocardless/banks/spk_karlsruhe_karsde66.js +0 -61
  174. package/src/app-gocardless/banks/spk_marburg_biedenkopf_heladef1mar.js +0 -30
  175. package/src/app-gocardless/banks/spk_worms_alzey_ried_malade51wor.js +0 -19
  176. package/src/app-gocardless/banks/ssk_dusseldorf_dussdeddxxx.js +0 -50
  177. package/src/app-gocardless/banks/swedbank_habalv22.js +0 -47
  178. package/src/app-gocardless/banks/tests/abanca_caglesmm.spec.js +0 -21
  179. package/src/app-gocardless/banks/tests/abnamro_abnanl2a.spec.js +0 -61
  180. package/src/app-gocardless/banks/tests/bancsabadell_bsabesbbb.spec.js +0 -53
  181. package/src/app-gocardless/banks/tests/belfius_gkccbebb.spec.js +0 -22
  182. package/src/app-gocardless/banks/tests/cbc_cregbebb.spec.js +0 -34
  183. package/src/app-gocardless/banks/tests/commerzbank_cobadeff.spec.js +0 -133
  184. package/src/app-gocardless/banks/tests/easybank_bawaatww.spec.js +0 -54
  185. package/src/app-gocardless/banks/tests/fortuneo_ftnofrp1xxx.spec.js +0 -206
  186. package/src/app-gocardless/banks/tests/ing_ingddeff.spec.js +0 -302
  187. package/src/app-gocardless/banks/tests/ing_pl_ingbplpw.spec.js +0 -202
  188. package/src/app-gocardless/banks/tests/integration_bank.spec.js +0 -156
  189. package/src/app-gocardless/banks/tests/kbc_kredbebb.spec.js +0 -38
  190. package/src/app-gocardless/banks/tests/lhv-lhvbee22.spec.js +0 -68
  191. package/src/app-gocardless/banks/tests/mbank_retail_brexplpw.spec.js +0 -171
  192. package/src/app-gocardless/banks/tests/nationwide_naiagb21.spec.js +0 -105
  193. package/src/app-gocardless/banks/tests/nbg_ethngraaxxx.spec.js +0 -48
  194. package/src/app-gocardless/banks/tests/revolut_revolt21.spec.js +0 -42
  195. package/src/app-gocardless/banks/tests/sandboxfinance_sfin0000.spec.js +0 -133
  196. package/src/app-gocardless/banks/tests/spk_marburg_biedenkopf_heladef1mar.spec.js +0 -255
  197. package/src/app-gocardless/banks/tests/ssk_dusseldorf_dussdeddxxx.spec.js +0 -100
  198. package/src/app-gocardless/banks/tests/swedbank_habalv22.spec.js +0 -57
  199. package/src/app-gocardless/banks/tests/virgin_nrnbgb22.spec.js +0 -54
  200. package/src/app-gocardless/banks/virgin_nrnbgb22.js +0 -39
  201. package/src/app-gocardless/errors.js +0 -84
  202. package/src/app-gocardless/gocardless-node.types.ts +0 -497
  203. package/src/app-gocardless/gocardless.types.ts +0 -93
  204. package/src/app-gocardless/link.html +0 -18
  205. package/src/app-gocardless/services/gocardless-service.js +0 -620
  206. package/src/app-gocardless/services/tests/fixtures.js +0 -181
  207. package/src/app-gocardless/services/tests/gocardless-service.spec.js +0 -537
  208. package/src/app-gocardless/tests/bank-factory.spec.js +0 -20
  209. package/src/app-gocardless/tests/utils.spec.js +0 -162
  210. package/src/app-gocardless/util/handle-error.js +0 -16
  211. package/src/app-gocardless/utils.js +0 -45
  212. package/src/app-openid.js +0 -108
  213. package/src/app-pluggyai/app-pluggyai.js +0 -215
  214. package/src/app-pluggyai/pluggyai-service.js +0 -120
  215. package/src/app-secrets.js +0 -61
  216. package/src/app-simplefin/app-simplefin.js +0 -405
  217. package/src/app-sync/errors.js +0 -13
  218. package/src/app-sync/services/files-service.js +0 -243
  219. package/src/app-sync/tests/services/files-service.test.js +0 -247
  220. package/src/app-sync/validation.js +0 -77
  221. package/src/app-sync.js +0 -391
  222. package/src/app-sync.test.js +0 -877
  223. package/src/app.js +0 -149
  224. package/src/config-types.ts +0 -44
  225. package/src/db.js +0 -58
  226. package/src/load-config.js +0 -307
  227. package/src/migrations.js +0 -36
  228. package/src/run-migrations.js +0 -8
  229. package/src/scripts/disable-openid.js +0 -44
  230. package/src/scripts/enable-openid.js +0 -53
  231. package/src/scripts/health-check.js +0 -23
  232. package/src/scripts/reset-password.js +0 -51
  233. package/src/secrets.test.js +0 -83
  234. package/src/services/secrets-service.js +0 -94
  235. package/src/services/user-service.js +0 -272
  236. package/src/sync-simple.js +0 -95
  237. package/src/util/middlewares.js +0 -62
  238. package/src/util/payee-name.js +0 -45
  239. package/src/util/prompt.js +0 -88
  240. package/src/util/title/index.js +0 -59
  241. package/src/util/title/lower-case.js +0 -93
  242. package/src/util/title/specials.js +0 -21
  243. package/src/util/validate-user.js +0 -68
  244. /package/{src → build/src}/sql/messages.sql +0 -0
@@ -0,0 +1,387 @@
1
+ import { AccessDeniedError, AccountNotLinkedToRequisition, GenericGoCardlessError, InvalidInputDataError, InvalidGoCardlessTokenError, NotFoundError, RateLimitError, ResourceSuspended, RequisitionNotLinked, ServiceError, UnknownError, } from '../../errors.js';
2
+ import { goCardlessService, handleGoCardlessError, client, } from '../gocardless-service.js';
3
+ import { mockedBalances, mockTransactions, mockDetailedAccount, mockInstitution, mockAccountMetaData, mockAccountDetails, mockRequisition, mockDeleteRequisition, mockCreateRequisition, mockRequisitionWithExampleAccounts, mockDetailedAccountExample1, mockDetailedAccountExample2, mockExtendAccountsAboutInstitutions, } from './fixtures.js';
4
+ describe('goCardlessService', () => {
5
+ const accountId = mockAccountMetaData.id;
6
+ const requisitionId = mockRequisition.id;
7
+ let getBalancesSpy;
8
+ let getTransactionsSpy;
9
+ let getDetailsSpy;
10
+ let getMetadataSpy;
11
+ let getInstitutionsSpy;
12
+ let getInstitutionSpy;
13
+ let getRequisitionsSpy;
14
+ let deleteRequisitionsSpy;
15
+ let createRequisitionSpy;
16
+ let setTokenSpy;
17
+ beforeEach(() => {
18
+ getInstitutionsSpy = vi.spyOn(client, 'getInstitutions');
19
+ getInstitutionSpy = vi.spyOn(client, 'getInstitutionById');
20
+ getRequisitionsSpy = vi.spyOn(client, 'getRequisitionById');
21
+ deleteRequisitionsSpy = vi.spyOn(client, 'deleteRequisition');
22
+ createRequisitionSpy = vi.spyOn(client, 'initSession');
23
+ getBalancesSpy = vi.spyOn(client, 'getBalances');
24
+ getTransactionsSpy = vi.spyOn(client, 'getTransactions');
25
+ getDetailsSpy = vi.spyOn(client, 'getDetails');
26
+ getMetadataSpy = vi.spyOn(client, 'getMetadata');
27
+ setTokenSpy = vi.spyOn(goCardlessService, 'setToken');
28
+ });
29
+ afterEach(() => {
30
+ vi.resetAllMocks();
31
+ });
32
+ describe('#getLinkedRequisition', () => {
33
+ it('returns requisition', async () => {
34
+ setTokenSpy.mockResolvedValue();
35
+ vi.spyOn(goCardlessService, 'getRequisition').mockResolvedValue(mockRequisition);
36
+ expect(await goCardlessService.getLinkedRequisition(requisitionId)).toEqual(mockRequisition);
37
+ });
38
+ it('throws RequisitionNotLinked error if requisition status is different than LN', async () => {
39
+ setTokenSpy.mockResolvedValue();
40
+ vi.spyOn(goCardlessService, 'getRequisition').mockResolvedValue({
41
+ ...mockRequisition,
42
+ status: 'ER',
43
+ });
44
+ await expect(() => goCardlessService.getLinkedRequisition(requisitionId)).rejects.toThrow(RequisitionNotLinked);
45
+ });
46
+ });
47
+ describe('#getRequisitionWithAccounts', () => {
48
+ it('returns combined data', async () => {
49
+ vi.spyOn(goCardlessService, 'getRequisition').mockResolvedValue(mockRequisitionWithExampleAccounts);
50
+ vi.spyOn(goCardlessService, 'getDetailedAccount').mockResolvedValueOnce(mockDetailedAccountExample1);
51
+ vi.spyOn(goCardlessService, 'getDetailedAccount').mockResolvedValueOnce(mockDetailedAccountExample2);
52
+ vi.spyOn(goCardlessService, 'getInstitution').mockResolvedValue(mockInstitution);
53
+ vi.spyOn(goCardlessService, 'extendAccountsAboutInstitutions').mockResolvedValue([
54
+ {
55
+ ...mockExtendAccountsAboutInstitutions[0],
56
+ institution_id: 'NEWONE',
57
+ },
58
+ {
59
+ ...mockExtendAccountsAboutInstitutions[1],
60
+ institution_id: 'NEWONE',
61
+ },
62
+ ]);
63
+ const response = await goCardlessService.getRequisitionWithAccounts(mockRequisitionWithExampleAccounts.id);
64
+ expect(response.accounts.length).toEqual(2);
65
+ expect(response.accounts).toMatchObject(expect.arrayContaining([
66
+ expect.objectContaining({
67
+ account_id: mockDetailedAccountExample1.id,
68
+ institution: mockInstitution,
69
+ official_name: 'Savings Account for Individuals (Retail)',
70
+ }),
71
+ expect.objectContaining({
72
+ account_id: mockDetailedAccountExample2.id,
73
+ institution: mockInstitution,
74
+ official_name: 'Savings Account for Individuals (Retail)',
75
+ }),
76
+ ]));
77
+ expect(response.requisition).toEqual(mockRequisitionWithExampleAccounts);
78
+ });
79
+ });
80
+ describe('#getTransactionsWithBalance', () => {
81
+ const requisitionId = mockRequisition.id;
82
+ it('returns transaction with starting balance', async () => {
83
+ vi.spyOn(goCardlessService, 'getLinkedRequisition').mockResolvedValue(mockRequisition);
84
+ vi.spyOn(goCardlessService, 'getAccountMetadata').mockResolvedValue(mockAccountMetaData);
85
+ vi.spyOn(goCardlessService, 'getTransactions').mockResolvedValue(mockTransactions);
86
+ vi.spyOn(goCardlessService, 'getBalances').mockResolvedValue(mockedBalances);
87
+ expect(await goCardlessService.getTransactionsWithBalance(requisitionId, accountId, undefined, undefined)).toEqual(expect.objectContaining({
88
+ balances: mockedBalances.balances,
89
+ institutionId: mockRequisition.institution_id,
90
+ startingBalance: expect.any(Number),
91
+ transactions: {
92
+ all: expect.arrayContaining([
93
+ expect.objectContaining({
94
+ bookingDate: expect.any(String),
95
+ transactionAmount: {
96
+ amount: expect.any(String),
97
+ currency: 'EUR',
98
+ },
99
+ transactionId: expect.any(String),
100
+ valueDate: expect.any(String),
101
+ }),
102
+ expect.objectContaining({
103
+ transactionAmount: {
104
+ amount: expect.any(String),
105
+ currency: 'EUR',
106
+ },
107
+ valueDate: expect.any(String),
108
+ }),
109
+ ]),
110
+ booked: expect.arrayContaining([
111
+ expect.objectContaining({
112
+ bookingDate: expect.any(String),
113
+ transactionAmount: {
114
+ amount: expect.any(String),
115
+ currency: 'EUR',
116
+ },
117
+ transactionId: expect.any(String),
118
+ valueDate: expect.any(String),
119
+ }),
120
+ ]),
121
+ pending: expect.arrayContaining([
122
+ expect.objectContaining({
123
+ transactionAmount: {
124
+ amount: expect.any(String),
125
+ currency: 'EUR',
126
+ },
127
+ valueDate: expect.any(String),
128
+ }),
129
+ ]),
130
+ },
131
+ }));
132
+ });
133
+ it('throws AccountNotLinkedToRequisition error if requisition accounts not includes requested account', async () => {
134
+ vi.spyOn(goCardlessService, 'getLinkedRequisition').mockResolvedValue(mockRequisition);
135
+ await expect(() => goCardlessService.getTransactionsWithBalance({
136
+ requisitionId,
137
+ accountId: 'some-unknown-account-id',
138
+ startDate: undefined,
139
+ endDate: undefined,
140
+ })).rejects.toThrow(AccountNotLinkedToRequisition);
141
+ });
142
+ });
143
+ describe('#createRequisition', () => {
144
+ const institutionId = 'some-institution-id';
145
+ const params = {
146
+ host: 'https://exemple.com',
147
+ institutionId,
148
+ accessValidForDays: 90,
149
+ };
150
+ it('calls goCardlessClient and delete requisition', async () => {
151
+ setTokenSpy.mockResolvedValue();
152
+ getInstitutionSpy.mockResolvedValue(mockInstitution);
153
+ createRequisitionSpy.mockResolvedValue(mockCreateRequisition);
154
+ expect(await goCardlessService.createRequisition(params)).toEqual({
155
+ link: expect.any(String),
156
+ requisitionId: expect.any(String),
157
+ });
158
+ expect(createRequisitionSpy).toBeCalledTimes(1);
159
+ });
160
+ });
161
+ describe('#deleteRequisition', () => {
162
+ const requisitionId = 'some-requisition-id';
163
+ it('calls goCardlessClient and delete requisition', async () => {
164
+ setTokenSpy.mockResolvedValue();
165
+ getRequisitionsSpy.mockResolvedValue(mockRequisition);
166
+ deleteRequisitionsSpy.mockResolvedValue(mockDeleteRequisition);
167
+ expect(await goCardlessService.deleteRequisition(requisitionId)).toEqual(mockDeleteRequisition);
168
+ expect(getRequisitionsSpy).toBeCalledTimes(1);
169
+ expect(deleteRequisitionsSpy).toBeCalledTimes(1);
170
+ });
171
+ });
172
+ describe('#getRequisition', () => {
173
+ const requisitionId = 'some-requisition-id';
174
+ it('calls goCardlessClient and fetch requisition', async () => {
175
+ setTokenSpy.mockResolvedValue();
176
+ getRequisitionsSpy.mockResolvedValue(mockRequisition);
177
+ expect(await goCardlessService.getRequisition(requisitionId)).toEqual(mockRequisition);
178
+ expect(setTokenSpy).toBeCalledTimes(1);
179
+ expect(getRequisitionsSpy).toBeCalledTimes(1);
180
+ });
181
+ });
182
+ describe('#getDetailedAccount', () => {
183
+ it('returns merged object', async () => {
184
+ getDetailsSpy.mockResolvedValue(mockAccountDetails);
185
+ getMetadataSpy.mockResolvedValue(mockAccountMetaData);
186
+ expect(await goCardlessService.getDetailedAccount(accountId)).toEqual({
187
+ ...mockAccountMetaData,
188
+ ...mockAccountDetails.account,
189
+ });
190
+ expect(getDetailsSpy).toBeCalledTimes(1);
191
+ expect(getMetadataSpy).toBeCalledTimes(1);
192
+ });
193
+ });
194
+ describe('#getInstitutions', () => {
195
+ const country = 'IE';
196
+ it('calls goCardlessClient and fetch institution details', async () => {
197
+ getInstitutionsSpy.mockResolvedValue([mockInstitution]);
198
+ expect(await goCardlessService.getInstitutions({ country })).toEqual([
199
+ mockInstitution,
200
+ ]);
201
+ expect(getInstitutionsSpy).toBeCalledTimes(1);
202
+ });
203
+ });
204
+ describe('#getInstitution', () => {
205
+ const institutionId = 'fake-institution-id';
206
+ it('calls goCardlessClient and fetch institution details', async () => {
207
+ getInstitutionSpy.mockResolvedValue(mockInstitution);
208
+ expect(await goCardlessService.getInstitution(institutionId)).toEqual(mockInstitution);
209
+ expect(getInstitutionSpy).toBeCalledTimes(1);
210
+ });
211
+ });
212
+ describe('#extendAccountsAboutInstitutions', () => {
213
+ it('extends accounts with the corresponding institution', async () => {
214
+ const institutionA = { ...mockInstitution, id: 'INSTITUTION_A' };
215
+ const institutionB = { ...mockInstitution, id: 'INSTITUTION_B' };
216
+ const accountAA = {
217
+ ...mockDetailedAccount,
218
+ id: 'AA',
219
+ institution_id: 'INSTITUTION_A',
220
+ };
221
+ const accountBB = {
222
+ ...mockDetailedAccount,
223
+ id: 'BB',
224
+ institution_id: 'INSTITUTION_B',
225
+ };
226
+ const accounts = [accountAA, accountBB];
227
+ const institutions = [institutionA, institutionB];
228
+ const expected = [
229
+ {
230
+ ...accountAA,
231
+ institution: institutionA,
232
+ },
233
+ {
234
+ ...accountBB,
235
+ institution: institutionB,
236
+ },
237
+ ];
238
+ const result = await goCardlessService.extendAccountsAboutInstitutions({
239
+ accounts,
240
+ institutions,
241
+ });
242
+ expect(result).toEqual(expected);
243
+ });
244
+ it('returns accounts with missing institutions as null', async () => {
245
+ const accountAA = {
246
+ ...mockDetailedAccount,
247
+ id: 'AA',
248
+ institution_id: 'INSTITUTION_A',
249
+ };
250
+ const accountBB = {
251
+ ...mockDetailedAccount,
252
+ id: 'BB',
253
+ institution_id: 'INSTITUTION_B',
254
+ };
255
+ const accounts = [accountAA, accountBB];
256
+ const institutionA = { ...mockInstitution, id: 'INSTITUTION_A' };
257
+ const institutions = [institutionA];
258
+ const expected = [
259
+ {
260
+ ...accountAA,
261
+ institution: institutionA,
262
+ },
263
+ {
264
+ ...accountBB,
265
+ institution: null,
266
+ },
267
+ ];
268
+ const result = await goCardlessService.extendAccountsAboutInstitutions({
269
+ accounts,
270
+ institutions,
271
+ });
272
+ expect(result).toEqual(expected);
273
+ });
274
+ });
275
+ describe('#getTransactions', () => {
276
+ it('calls goCardlessClient and fetch transactions for provided accountId', async () => {
277
+ getTransactionsSpy.mockResolvedValue(mockTransactions);
278
+ expect(await goCardlessService.getTransactions({
279
+ institutionId: 'SANDBOXFINANCE_SFIN0000',
280
+ accountId,
281
+ startDate: '',
282
+ endDate: '',
283
+ })).toMatchInlineSnapshot(`
284
+ {
285
+ "transactions": {
286
+ "booked": [
287
+ {
288
+ "bankTransactionCode": "string",
289
+ "bookingDate": "2000-01-01",
290
+ "date": "2000-01-01",
291
+ "debtorAccount": {
292
+ "iban": "string",
293
+ },
294
+ "debtorName": "string",
295
+ "notes": undefined,
296
+ "payeeName": "String (stri XXX ring)",
297
+ "remittanceInformationStructuredArrayString": undefined,
298
+ "remittanceInformationUnstructuredArrayString": undefined,
299
+ "transactionAmount": {
300
+ "amount": "328.18",
301
+ "currency": "EUR",
302
+ },
303
+ "transactionId": "string",
304
+ "valueDate": "2000-01-01",
305
+ },
306
+ {
307
+ "bankTransactionCode": "string",
308
+ "bookingDate": "2000-01-01",
309
+ "date": "2000-01-01",
310
+ "notes": undefined,
311
+ "payeeName": "",
312
+ "remittanceInformationStructuredArrayString": undefined,
313
+ "remittanceInformationUnstructuredArrayString": undefined,
314
+ "transactionAmount": {
315
+ "amount": "947.26",
316
+ "currency": "EUR",
317
+ },
318
+ "transactionId": "string",
319
+ "valueDate": "2000-01-01",
320
+ },
321
+ ],
322
+ "pending": [
323
+ {
324
+ "date": "2000-01-01",
325
+ "notes": undefined,
326
+ "payeeName": "",
327
+ "remittanceInformationStructuredArrayString": undefined,
328
+ "remittanceInformationUnstructuredArrayString": undefined,
329
+ "transactionAmount": {
330
+ "amount": "947.26",
331
+ "currency": "EUR",
332
+ },
333
+ "valueDate": "2000-01-01",
334
+ },
335
+ ],
336
+ },
337
+ }
338
+ `);
339
+ expect(getTransactionsSpy).toBeCalledTimes(1);
340
+ });
341
+ });
342
+ describe('#getBalances', () => {
343
+ it('calls goCardlessClient and fetch balances for provided accountId', async () => {
344
+ getBalancesSpy.mockResolvedValue(mockedBalances);
345
+ expect(await goCardlessService.getBalances(accountId)).toEqual(mockedBalances);
346
+ expect(getBalancesSpy).toBeCalledTimes(1);
347
+ });
348
+ });
349
+ });
350
+ describe('#handleGoCardlessError', () => {
351
+ it('throws InvalidInputDataError for status code 400', () => {
352
+ const response = { response: { status: 400 } };
353
+ expect(() => handleGoCardlessError(response)).toThrow(InvalidInputDataError);
354
+ });
355
+ it('throws InvalidGoCardlessTokenError for status code 401', () => {
356
+ const response = { response: { status: 401 } };
357
+ expect(() => handleGoCardlessError(response)).toThrow(InvalidGoCardlessTokenError);
358
+ });
359
+ it('throws AccessDeniedError for status code 403', () => {
360
+ const response = { response: { status: 403 } };
361
+ expect(() => handleGoCardlessError(response)).toThrow(AccessDeniedError);
362
+ });
363
+ it('throws NotFoundError for status code 404', () => {
364
+ const response = { response: { status: 404 } };
365
+ expect(() => handleGoCardlessError(response)).toThrow(NotFoundError);
366
+ });
367
+ it('throws ResourceSuspended for status code 409', () => {
368
+ const response = { response: { status: 409 } };
369
+ expect(() => handleGoCardlessError(response)).toThrow(ResourceSuspended);
370
+ });
371
+ it('throws RateLimitError for status code 429', () => {
372
+ const response = { response: { status: 429 } };
373
+ expect(() => handleGoCardlessError(response)).toThrow(RateLimitError);
374
+ });
375
+ it('throws UnknownError for status code 500', () => {
376
+ const response = { response: { status: 500 } };
377
+ expect(() => handleGoCardlessError(response)).toThrow(UnknownError);
378
+ });
379
+ it('throws ServiceError for status code 503', () => {
380
+ const response = { response: { status: 503 } };
381
+ expect(() => handleGoCardlessError(response)).toThrow(ServiceError);
382
+ });
383
+ it('throws a generic error when the status code is not recognised', () => {
384
+ const response = { response: { status: 0 } };
385
+ expect(() => handleGoCardlessError(response)).toThrow(GenericGoCardlessError);
386
+ });
387
+ });
@@ -0,0 +1,13 @@
1
+ import { BankFactory, banks } from '../bank-factory.js';
2
+ import IntegrationBank from '../banks/integration-bank.js';
3
+ describe('BankFactory', () => {
4
+ it.each(banks.flatMap(bank => bank.institutionIds))(`should return same institutionId`, institutionId => {
5
+ const result = BankFactory(institutionId);
6
+ expect(result.institutionIds).toContain(institutionId);
7
+ });
8
+ it('should return IntegrationBank when institutionId is not found', () => {
9
+ const institutionId = IntegrationBank.institutionIds[0];
10
+ const result = BankFactory('fake-id-not-found');
11
+ expect(result.institutionIds).toContain(institutionId);
12
+ });
13
+ });
@@ -0,0 +1,158 @@
1
+ import { mockTransactionAmount } from '../services/tests/fixtures.js';
2
+ import { sortByBookingDateOrValueDate } from '../utils.js';
3
+ describe('utils', () => {
4
+ describe('#sortByBookingDate', () => {
5
+ it('sorts transactions by bookingDate field from newest to oldest', () => {
6
+ const transactions = [
7
+ {
8
+ bookingDate: '2023-01-01',
9
+ transactionAmount: mockTransactionAmount,
10
+ },
11
+ {
12
+ bookingDate: '2023-01-20',
13
+ transactionAmount: mockTransactionAmount,
14
+ },
15
+ {
16
+ bookingDate: '2023-01-10',
17
+ transactionAmount: mockTransactionAmount,
18
+ },
19
+ ];
20
+ expect(sortByBookingDateOrValueDate(transactions)).toEqual([
21
+ {
22
+ bookingDate: '2023-01-20',
23
+ transactionAmount: mockTransactionAmount,
24
+ },
25
+ {
26
+ bookingDate: '2023-01-10',
27
+ transactionAmount: mockTransactionAmount,
28
+ },
29
+ {
30
+ bookingDate: '2023-01-01',
31
+ transactionAmount: mockTransactionAmount,
32
+ },
33
+ ]);
34
+ });
35
+ it('should sort by valueDate if bookingDate is missing', () => {
36
+ const transactions = [
37
+ {
38
+ valueDate: '2023-01-01',
39
+ transactionAmount: mockTransactionAmount,
40
+ },
41
+ {
42
+ valueDate: '2023-01-20',
43
+ transactionAmount: mockTransactionAmount,
44
+ },
45
+ {
46
+ valueDate: '2023-01-10',
47
+ transactionAmount: mockTransactionAmount,
48
+ },
49
+ ];
50
+ expect(sortByBookingDateOrValueDate(transactions)).toEqual([
51
+ {
52
+ valueDate: '2023-01-20',
53
+ transactionAmount: mockTransactionAmount,
54
+ },
55
+ {
56
+ valueDate: '2023-01-10',
57
+ transactionAmount: mockTransactionAmount,
58
+ },
59
+ {
60
+ valueDate: '2023-01-01',
61
+ transactionAmount: mockTransactionAmount,
62
+ },
63
+ ]);
64
+ });
65
+ it('should use bookingDate primarily even if bookingDateTime is on an other date', () => {
66
+ const transactions = [
67
+ {
68
+ bookingDate: '2023-01-01',
69
+ bookingDateTime: '2023-01-01T00:00:00Z',
70
+ transactionAmount: mockTransactionAmount,
71
+ },
72
+ {
73
+ bookingDate: '2023-01-10',
74
+ bookingDateTime: '2023-01-01T12:00:00Z',
75
+ transactionAmount: mockTransactionAmount,
76
+ },
77
+ {
78
+ bookingDate: '2023-01-01',
79
+ bookingDateTime: '2023-01-01T12:00:00Z',
80
+ transactionAmount: mockTransactionAmount,
81
+ },
82
+ {
83
+ bookingDate: '2023-01-10',
84
+ bookingDateTime: '2023-01-01T00:00:00Z',
85
+ transactionAmount: mockTransactionAmount,
86
+ },
87
+ ];
88
+ expect(sortByBookingDateOrValueDate(transactions)).toEqual([
89
+ {
90
+ bookingDate: '2023-01-10',
91
+ bookingDateTime: '2023-01-01T12:00:00Z',
92
+ transactionAmount: mockTransactionAmount,
93
+ },
94
+ {
95
+ bookingDate: '2023-01-10',
96
+ bookingDateTime: '2023-01-01T00:00:00Z',
97
+ transactionAmount: mockTransactionAmount,
98
+ },
99
+ {
100
+ bookingDate: '2023-01-01',
101
+ bookingDateTime: '2023-01-01T12:00:00Z',
102
+ transactionAmount: mockTransactionAmount,
103
+ },
104
+ {
105
+ bookingDate: '2023-01-01',
106
+ bookingDateTime: '2023-01-01T00:00:00Z',
107
+ transactionAmount: mockTransactionAmount,
108
+ },
109
+ ]);
110
+ });
111
+ it('should sort on booking date if value date is widely off', () => {
112
+ const transactions = [
113
+ {
114
+ bookingDate: '2023-01-01',
115
+ valueDateTime: '2023-01-31T00:00:00Z',
116
+ transactionAmount: mockTransactionAmount,
117
+ },
118
+ {
119
+ bookingDate: '2023-01-02',
120
+ valueDateTime: '2023-01-02T12:00:00Z',
121
+ transactionAmount: mockTransactionAmount,
122
+ },
123
+ {
124
+ bookingDate: '2023-01-30',
125
+ valueDateTime: '2023-01-01T12:00:00Z',
126
+ transactionAmount: mockTransactionAmount,
127
+ },
128
+ {
129
+ bookingDate: '2023-01-30',
130
+ valueDateTime: '2023-01-01T00:00:00Z',
131
+ transactionAmount: mockTransactionAmount,
132
+ },
133
+ ];
134
+ expect(sortByBookingDateOrValueDate(transactions)).toEqual([
135
+ {
136
+ bookingDate: '2023-01-30',
137
+ valueDateTime: '2023-01-01T12:00:00Z',
138
+ transactionAmount: mockTransactionAmount,
139
+ },
140
+ {
141
+ bookingDate: '2023-01-30',
142
+ valueDateTime: '2023-01-01T00:00:00Z',
143
+ transactionAmount: mockTransactionAmount,
144
+ },
145
+ {
146
+ bookingDate: '2023-01-02',
147
+ valueDateTime: '2023-01-02T12:00:00Z',
148
+ transactionAmount: mockTransactionAmount,
149
+ },
150
+ {
151
+ bookingDate: '2023-01-01',
152
+ valueDateTime: '2023-01-31T00:00:00Z',
153
+ transactionAmount: mockTransactionAmount,
154
+ },
155
+ ]);
156
+ });
157
+ });
158
+ });
@@ -0,0 +1,15 @@
1
+ import { inspect } from 'util';
2
+ export function handleError(func) {
3
+ return (req, res) => {
4
+ func(req, res).catch(err => {
5
+ console.log('Error', req.originalUrl, inspect(err, { depth: null }));
6
+ res.send({
7
+ status: 'ok',
8
+ data: {
9
+ error_code: 'INTERNAL_ERROR',
10
+ error_type: err.message ? err.message : 'internal-error',
11
+ },
12
+ });
13
+ });
14
+ };
15
+ }
@@ -0,0 +1,41 @@
1
+ export const printIban = account => {
2
+ if (account.iban) {
3
+ return '(XXX ' + account.iban.slice(-4) + ')';
4
+ }
5
+ else {
6
+ return '';
7
+ }
8
+ };
9
+ const compareDates = (
10
+ /** @type {string | number | Date | undefined} */ a,
11
+ /** @type {string | number | Date | undefined} */ b) => {
12
+ if (a == null && b == null) {
13
+ return 0;
14
+ }
15
+ else if (a == null) {
16
+ return 1;
17
+ }
18
+ else if (b == null) {
19
+ return -1;
20
+ }
21
+ return +new Date(a) - +new Date(b);
22
+ };
23
+ /**
24
+ * @type {(function(*, *): number)[]}
25
+ */
26
+ const compareFunctions = [
27
+ (a, b) => compareDates(a.bookingDate, b.bookingDate),
28
+ (a, b) => compareDates(a.bookingDateTime, b.bookingDateTime),
29
+ (a, b) => compareDates(a.valueDate, b.valueDate),
30
+ (a, b) => compareDates(a.valueDateTime, b.valueDateTime),
31
+ ];
32
+ export const sortByBookingDateOrValueDate = (transactions = []) => transactions.sort((a, b) => {
33
+ for (const sortFunction of compareFunctions) {
34
+ const result = sortFunction(b, a);
35
+ if (result !== 0) {
36
+ return result;
37
+ }
38
+ }
39
+ return 0;
40
+ });
41
+ export const amountToInteger = n => Math.round(n * 100);