@actual-app/sync-server 25.4.0-alpha.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 (137) hide show
  1. package/.dockerignore +12 -0
  2. package/README.md +19 -0
  3. package/app.js +11 -0
  4. package/babel.config.json +3 -0
  5. package/bin/@actual-app/sync-server +55 -0
  6. package/docker/alpine.Dockerfile +62 -0
  7. package/docker/ubuntu.Dockerfile +63 -0
  8. package/docker-compose.yml +29 -0
  9. package/jest.config.json +19 -0
  10. package/jest.global-setup.js +101 -0
  11. package/jest.global-teardown.js +6 -0
  12. package/migrations/1694360000000-create-folders.js +25 -0
  13. package/migrations/1694360479680-create-account-db.js +30 -0
  14. package/migrations/1694362247011-create-secret-table.js +16 -0
  15. package/migrations/1702667624000-rename-nordigen-secrets.js +19 -0
  16. package/migrations/1718889148000-openid.js +41 -0
  17. package/migrations/1719409568000-multiuser.js +116 -0
  18. package/package.json +64 -0
  19. package/src/account-db.js +239 -0
  20. package/src/accounts/openid.js +361 -0
  21. package/src/accounts/password.js +149 -0
  22. package/src/app-account.js +155 -0
  23. package/src/app-admin.js +410 -0
  24. package/src/app-admin.test.js +381 -0
  25. package/src/app-gocardless/README.md +198 -0
  26. package/src/app-gocardless/app-gocardless.js +274 -0
  27. package/src/app-gocardless/bank-factory.js +91 -0
  28. package/src/app-gocardless/banks/abanca_caglesmm.js +22 -0
  29. package/src/app-gocardless/banks/abnamro_abnanl2a.js +57 -0
  30. package/src/app-gocardless/banks/american_express_aesudef1.js +40 -0
  31. package/src/app-gocardless/banks/bancsabadell_bsabesbbb.js +31 -0
  32. package/src/app-gocardless/banks/bank.interface.ts +51 -0
  33. package/src/app-gocardless/banks/bank_of_ireland_b365_bofiie2d.js +39 -0
  34. package/src/app-gocardless/banks/bankinter_bkbkesmm.js +24 -0
  35. package/src/app-gocardless/banks/belfius_gkccbebb.js +17 -0
  36. package/src/app-gocardless/banks/berliner_sparkasse_beladebexxx.js +61 -0
  37. package/src/app-gocardless/banks/bnp_be_gebabebb.js +73 -0
  38. package/src/app-gocardless/banks/cbc_cregbebb.js +34 -0
  39. package/src/app-gocardless/banks/commerzbank_cobadeff.js +51 -0
  40. package/src/app-gocardless/banks/danskebank_dabno22.js +39 -0
  41. package/src/app-gocardless/banks/direkt_heladef1822.js +18 -0
  42. package/src/app-gocardless/banks/easybank_bawaatww.js +50 -0
  43. package/src/app-gocardless/banks/entercard_swednokk.js +40 -0
  44. package/src/app-gocardless/banks/fortuneo_ftnofrp1xxx.js +46 -0
  45. package/src/app-gocardless/banks/hype_hyeeit22.js +74 -0
  46. package/src/app-gocardless/banks/ing_ingbrobu.js +70 -0
  47. package/src/app-gocardless/banks/ing_ingddeff.js +47 -0
  48. package/src/app-gocardless/banks/ing_pl_ingbplpw.js +46 -0
  49. package/src/app-gocardless/banks/integration-bank.js +115 -0
  50. package/src/app-gocardless/banks/isybank_itbbitmm.js +18 -0
  51. package/src/app-gocardless/banks/kbc_kredbebb.js +33 -0
  52. package/src/app-gocardless/banks/lhv-lhvbee22.js +36 -0
  53. package/src/app-gocardless/banks/mbank_retail_brexplpw.js +56 -0
  54. package/src/app-gocardless/banks/nationwide_naiagb21.js +46 -0
  55. package/src/app-gocardless/banks/nbg_ethngraaxxx.js +51 -0
  56. package/src/app-gocardless/banks/norwegian_xx_norwnok1.js +74 -0
  57. package/src/app-gocardless/banks/revolut_revolt21.js +37 -0
  58. package/src/app-gocardless/banks/sandboxfinance_sfin0000.js +28 -0
  59. package/src/app-gocardless/banks/seb_kort_bank_ab.js +58 -0
  60. package/src/app-gocardless/banks/seb_privat.js +29 -0
  61. package/src/app-gocardless/banks/sparnord_spnodk22.js +24 -0
  62. package/src/app-gocardless/banks/spk_karlsruhe_karsde66.js +61 -0
  63. package/src/app-gocardless/banks/spk_marburg_biedenkopf_heladef1mar.js +30 -0
  64. package/src/app-gocardless/banks/spk_worms_alzey_ried_malade51wor.js +19 -0
  65. package/src/app-gocardless/banks/ssk_dusseldorf_dussdeddxxx.js +50 -0
  66. package/src/app-gocardless/banks/swedbank_habalv22.js +47 -0
  67. package/src/app-gocardless/banks/tests/abanca_caglesmm.spec.js +21 -0
  68. package/src/app-gocardless/banks/tests/abnamro_abnanl2a.spec.js +61 -0
  69. package/src/app-gocardless/banks/tests/bancsabadell_bsabesbbb.spec.js +53 -0
  70. package/src/app-gocardless/banks/tests/belfius_gkccbebb.spec.js +22 -0
  71. package/src/app-gocardless/banks/tests/cbc_cregbebb.spec.js +34 -0
  72. package/src/app-gocardless/banks/tests/commerzbank_cobadeff.spec.js +110 -0
  73. package/src/app-gocardless/banks/tests/easybank_bawaatww.spec.js +54 -0
  74. package/src/app-gocardless/banks/tests/fortuneo_ftnofrp1xxx.spec.js +206 -0
  75. package/src/app-gocardless/banks/tests/ing_ingddeff.spec.js +302 -0
  76. package/src/app-gocardless/banks/tests/ing_pl_ingbplpw.spec.js +202 -0
  77. package/src/app-gocardless/banks/tests/integration_bank.spec.js +158 -0
  78. package/src/app-gocardless/banks/tests/kbc_kredbebb.spec.js +38 -0
  79. package/src/app-gocardless/banks/tests/lhv-lhvbee22.spec.js +68 -0
  80. package/src/app-gocardless/banks/tests/mbank_retail_brexplpw.spec.js +171 -0
  81. package/src/app-gocardless/banks/tests/nationwide_naiagb21.spec.js +105 -0
  82. package/src/app-gocardless/banks/tests/nbg_ethngraaxxx.spec.js +48 -0
  83. package/src/app-gocardless/banks/tests/revolut_revolt21.spec.js +42 -0
  84. package/src/app-gocardless/banks/tests/sandboxfinance_sfin0000.spec.js +133 -0
  85. package/src/app-gocardless/banks/tests/spk_marburg_biedenkopf_heladef1mar.spec.js +256 -0
  86. package/src/app-gocardless/banks/tests/ssk_dusseldorf_dussdeddxxx.spec.js +102 -0
  87. package/src/app-gocardless/banks/tests/swedbank_habalv22.spec.js +57 -0
  88. package/src/app-gocardless/banks/tests/virgin_nrnbgb22.spec.js +54 -0
  89. package/src/app-gocardless/banks/util/extract-payeeName-from-remittanceInfo.js +36 -0
  90. package/src/app-gocardless/banks/virgin_nrnbgb22.js +39 -0
  91. package/src/app-gocardless/errors.js +84 -0
  92. package/src/app-gocardless/gocardless-node.types.ts +497 -0
  93. package/src/app-gocardless/gocardless.types.ts +93 -0
  94. package/src/app-gocardless/link.html +18 -0
  95. package/src/app-gocardless/services/gocardless-service.js +620 -0
  96. package/src/app-gocardless/services/tests/fixtures.js +181 -0
  97. package/src/app-gocardless/services/tests/gocardless-service.spec.js +537 -0
  98. package/src/app-gocardless/tests/bank-factory.spec.js +20 -0
  99. package/src/app-gocardless/tests/utils.spec.js +162 -0
  100. package/src/app-gocardless/util/handle-error.js +16 -0
  101. package/src/app-gocardless/utils.js +45 -0
  102. package/src/app-openid.js +108 -0
  103. package/src/app-pluggyai/app-pluggyai.js +215 -0
  104. package/src/app-pluggyai/pluggyai-service.js +120 -0
  105. package/src/app-secrets.js +61 -0
  106. package/src/app-simplefin/app-simplefin.js +418 -0
  107. package/src/app-sync/errors.js +13 -0
  108. package/src/app-sync/services/files-service.js +243 -0
  109. package/src/app-sync/tests/services/files-service.test.js +250 -0
  110. package/src/app-sync/validation.js +77 -0
  111. package/src/app-sync.js +391 -0
  112. package/src/app-sync.test.js +877 -0
  113. package/src/app.js +145 -0
  114. package/src/config-types.ts +44 -0
  115. package/src/db.js +58 -0
  116. package/src/load-config.js +307 -0
  117. package/src/migrations.js +36 -0
  118. package/src/run-migrations.js +8 -0
  119. package/src/scripts/disable-openid.js +44 -0
  120. package/src/scripts/enable-openid.js +53 -0
  121. package/src/scripts/health-check.js +23 -0
  122. package/src/scripts/reset-password.js +51 -0
  123. package/src/secrets.test.js +83 -0
  124. package/src/services/secrets-service.js +94 -0
  125. package/src/services/user-service.js +272 -0
  126. package/src/sql/messages.sql +9 -0
  127. package/src/sync-simple.js +95 -0
  128. package/src/util/hash.js +5 -0
  129. package/src/util/middlewares.js +62 -0
  130. package/src/util/paths.js +13 -0
  131. package/src/util/payee-name.js +45 -0
  132. package/src/util/prompt.js +88 -0
  133. package/src/util/title/index.js +59 -0
  134. package/src/util/title/lower-case.js +93 -0
  135. package/src/util/title/specials.js +21 -0
  136. package/src/util/validate-user.js +68 -0
  137. package/tsconfig.json +21 -0
@@ -0,0 +1,537 @@
1
+ import { jest } from '@jest/globals';
2
+
3
+ import {
4
+ AccessDeniedError,
5
+ AccountNotLinkedToRequisition,
6
+ GenericGoCardlessError,
7
+ InvalidInputDataError,
8
+ InvalidGoCardlessTokenError,
9
+ NotFoundError,
10
+ RateLimitError,
11
+ ResourceSuspended,
12
+ RequisitionNotLinked,
13
+ ServiceError,
14
+ UnknownError,
15
+ } from '../../errors.js';
16
+ import {
17
+ goCardlessService,
18
+ handleGoCardlessError,
19
+ client,
20
+ } from '../gocardless-service.js';
21
+
22
+ import {
23
+ mockedBalances,
24
+ mockTransactions,
25
+ mockDetailedAccount,
26
+ mockInstitution,
27
+ mockAccountMetaData,
28
+ mockAccountDetails,
29
+ mockRequisition,
30
+ mockDeleteRequisition,
31
+ mockCreateRequisition,
32
+ mockRequisitionWithExampleAccounts,
33
+ mockDetailedAccountExample1,
34
+ mockDetailedAccountExample2,
35
+ mockExtendAccountsAboutInstitutions,
36
+ } from './fixtures.js';
37
+
38
+ describe('goCardlessService', () => {
39
+ const accountId = mockAccountMetaData.id;
40
+ const requisitionId = mockRequisition.id;
41
+
42
+ let getBalancesSpy;
43
+ let getTransactionsSpy;
44
+ let getDetailsSpy;
45
+ let getMetadataSpy;
46
+ let getInstitutionsSpy;
47
+ let getInstitutionSpy;
48
+ let getRequisitionsSpy;
49
+ let deleteRequisitionsSpy;
50
+ let createRequisitionSpy;
51
+ let setTokenSpy;
52
+
53
+ beforeEach(() => {
54
+ getInstitutionsSpy = jest.spyOn(client, 'getInstitutions');
55
+ getInstitutionSpy = jest.spyOn(client, 'getInstitutionById');
56
+ getRequisitionsSpy = jest.spyOn(client, 'getRequisitionById');
57
+ deleteRequisitionsSpy = jest.spyOn(client, 'deleteRequisition');
58
+ createRequisitionSpy = jest.spyOn(client, 'initSession');
59
+ getBalancesSpy = jest.spyOn(client, 'getBalances');
60
+ getTransactionsSpy = jest.spyOn(client, 'getTransactions');
61
+ getDetailsSpy = jest.spyOn(client, 'getDetails');
62
+ getMetadataSpy = jest.spyOn(client, 'getMetadata');
63
+ setTokenSpy = jest.spyOn(goCardlessService, 'setToken');
64
+ });
65
+
66
+ afterEach(() => {
67
+ jest.resetAllMocks();
68
+ });
69
+
70
+ describe('#getLinkedRequisition', () => {
71
+ it('returns requisition', async () => {
72
+ setTokenSpy.mockResolvedValue();
73
+
74
+ jest
75
+ .spyOn(goCardlessService, 'getRequisition')
76
+ .mockResolvedValue(mockRequisition);
77
+
78
+ expect(
79
+ await goCardlessService.getLinkedRequisition(requisitionId),
80
+ ).toEqual(mockRequisition);
81
+ });
82
+
83
+ it('throws RequisitionNotLinked error if requisition status is different than LN', async () => {
84
+ setTokenSpy.mockResolvedValue();
85
+
86
+ jest
87
+ .spyOn(goCardlessService, 'getRequisition')
88
+ .mockResolvedValue({ ...mockRequisition, status: 'ER' });
89
+
90
+ await expect(() =>
91
+ goCardlessService.getLinkedRequisition(requisitionId),
92
+ ).rejects.toThrow(RequisitionNotLinked);
93
+ });
94
+ });
95
+
96
+ describe('#getRequisitionWithAccounts', () => {
97
+ it('returns combined data', async () => {
98
+ jest
99
+ .spyOn(goCardlessService, 'getRequisition')
100
+ .mockResolvedValue(mockRequisitionWithExampleAccounts);
101
+ jest
102
+ .spyOn(goCardlessService, 'getDetailedAccount')
103
+ .mockResolvedValueOnce(mockDetailedAccountExample1);
104
+ jest
105
+ .spyOn(goCardlessService, 'getDetailedAccount')
106
+ .mockResolvedValueOnce(mockDetailedAccountExample2);
107
+ jest
108
+ .spyOn(goCardlessService, 'getInstitution')
109
+ .mockResolvedValue(mockInstitution);
110
+ jest
111
+ .spyOn(goCardlessService, 'extendAccountsAboutInstitutions')
112
+ .mockResolvedValue([
113
+ {
114
+ ...mockExtendAccountsAboutInstitutions[0],
115
+ institution_id: 'NEWONE',
116
+ },
117
+ {
118
+ ...mockExtendAccountsAboutInstitutions[1],
119
+ institution_id: 'NEWONE',
120
+ },
121
+ ]);
122
+
123
+ const response = await goCardlessService.getRequisitionWithAccounts(
124
+ mockRequisitionWithExampleAccounts.id,
125
+ );
126
+
127
+ expect(response.accounts.length).toEqual(2);
128
+ expect(response.accounts).toMatchObject(
129
+ expect.arrayContaining([
130
+ expect.objectContaining({
131
+ account_id: mockDetailedAccountExample1.id,
132
+ institution: mockInstitution,
133
+ official_name: 'Savings Account for Individuals (Retail)',
134
+ }),
135
+ expect.objectContaining({
136
+ account_id: mockDetailedAccountExample2.id,
137
+ institution: mockInstitution,
138
+ official_name: 'Savings Account for Individuals (Retail)',
139
+ }),
140
+ ]),
141
+ );
142
+ expect(response.requisition).toEqual(mockRequisitionWithExampleAccounts);
143
+ });
144
+ });
145
+
146
+ describe('#getTransactionsWithBalance', () => {
147
+ const requisitionId = mockRequisition.id;
148
+ it('returns transaction with starting balance', async () => {
149
+ jest
150
+ .spyOn(goCardlessService, 'getLinkedRequisition')
151
+ .mockResolvedValue(mockRequisition);
152
+ jest
153
+ .spyOn(goCardlessService, 'getAccountMetadata')
154
+ .mockResolvedValue(mockAccountMetaData);
155
+ jest
156
+ .spyOn(goCardlessService, 'getTransactions')
157
+ .mockResolvedValue(mockTransactions);
158
+ jest
159
+ .spyOn(goCardlessService, 'getBalances')
160
+ .mockResolvedValue(mockedBalances);
161
+
162
+ expect(
163
+ await goCardlessService.getTransactionsWithBalance(
164
+ requisitionId,
165
+ accountId,
166
+ undefined,
167
+ undefined,
168
+ ),
169
+ ).toEqual(
170
+ expect.objectContaining({
171
+ balances: mockedBalances.balances,
172
+ institutionId: mockRequisition.institution_id,
173
+ startingBalance: expect.any(Number),
174
+ transactions: {
175
+ all: expect.arrayContaining([
176
+ expect.objectContaining({
177
+ bookingDate: expect.any(String),
178
+ transactionAmount: {
179
+ amount: expect.any(String),
180
+ currency: 'EUR',
181
+ },
182
+ transactionId: expect.any(String),
183
+ valueDate: expect.any(String),
184
+ }),
185
+ expect.objectContaining({
186
+ transactionAmount: {
187
+ amount: expect.any(String),
188
+ currency: 'EUR',
189
+ },
190
+ valueDate: expect.any(String),
191
+ }),
192
+ ]),
193
+ booked: expect.arrayContaining([
194
+ expect.objectContaining({
195
+ bookingDate: expect.any(String),
196
+ transactionAmount: {
197
+ amount: expect.any(String),
198
+ currency: 'EUR',
199
+ },
200
+ transactionId: expect.any(String),
201
+ valueDate: expect.any(String),
202
+ }),
203
+ ]),
204
+ pending: expect.arrayContaining([
205
+ expect.objectContaining({
206
+ transactionAmount: {
207
+ amount: expect.any(String),
208
+ currency: 'EUR',
209
+ },
210
+ valueDate: expect.any(String),
211
+ }),
212
+ ]),
213
+ },
214
+ }),
215
+ );
216
+ });
217
+
218
+ it('throws AccountNotLinkedToRequisition error if requisition accounts not includes requested account', async () => {
219
+ jest
220
+ .spyOn(goCardlessService, 'getLinkedRequisition')
221
+ .mockResolvedValue(mockRequisition);
222
+
223
+ await expect(() =>
224
+ goCardlessService.getTransactionsWithBalance({
225
+ requisitionId,
226
+ accountId: 'some-unknown-account-id',
227
+ startDate: undefined,
228
+ endDate: undefined,
229
+ }),
230
+ ).rejects.toThrow(AccountNotLinkedToRequisition);
231
+ });
232
+ });
233
+
234
+ describe('#createRequisition', () => {
235
+ const institutionId = 'some-institution-id';
236
+ const params = {
237
+ host: 'https://exemple.com',
238
+ institutionId,
239
+ accessValidForDays: 90,
240
+ };
241
+
242
+ it('calls goCardlessClient and delete requisition', async () => {
243
+ setTokenSpy.mockResolvedValue();
244
+ getInstitutionSpy.mockResolvedValue(mockInstitution);
245
+
246
+ createRequisitionSpy.mockResolvedValue(mockCreateRequisition);
247
+
248
+ expect(await goCardlessService.createRequisition(params)).toEqual({
249
+ link: expect.any(String),
250
+ requisitionId: expect.any(String),
251
+ });
252
+
253
+ expect(createRequisitionSpy).toBeCalledTimes(1);
254
+ });
255
+ });
256
+
257
+ describe('#deleteRequisition', () => {
258
+ const requisitionId = 'some-requisition-id';
259
+
260
+ it('calls goCardlessClient and delete requisition', async () => {
261
+ setTokenSpy.mockResolvedValue();
262
+
263
+ getRequisitionsSpy.mockResolvedValue(mockRequisition);
264
+ deleteRequisitionsSpy.mockResolvedValue(mockDeleteRequisition);
265
+
266
+ expect(await goCardlessService.deleteRequisition(requisitionId)).toEqual(
267
+ mockDeleteRequisition,
268
+ );
269
+
270
+ expect(getRequisitionsSpy).toBeCalledTimes(1);
271
+ expect(deleteRequisitionsSpy).toBeCalledTimes(1);
272
+ });
273
+ });
274
+
275
+ describe('#getRequisition', () => {
276
+ const requisitionId = 'some-requisition-id';
277
+
278
+ it('calls goCardlessClient and fetch requisition', async () => {
279
+ setTokenSpy.mockResolvedValue();
280
+ getRequisitionsSpy.mockResolvedValue(mockRequisition);
281
+
282
+ expect(await goCardlessService.getRequisition(requisitionId)).toEqual(
283
+ mockRequisition,
284
+ );
285
+
286
+ expect(setTokenSpy).toBeCalledTimes(1);
287
+ expect(getRequisitionsSpy).toBeCalledTimes(1);
288
+ });
289
+ });
290
+
291
+ describe('#getDetailedAccount', () => {
292
+ it('returns merged object', async () => {
293
+ getDetailsSpy.mockResolvedValue(mockAccountDetails);
294
+ getMetadataSpy.mockResolvedValue(mockAccountMetaData);
295
+
296
+ expect(await goCardlessService.getDetailedAccount(accountId)).toEqual({
297
+ ...mockAccountMetaData,
298
+ ...mockAccountDetails.account,
299
+ });
300
+ expect(getDetailsSpy).toBeCalledTimes(1);
301
+ expect(getMetadataSpy).toBeCalledTimes(1);
302
+ });
303
+ });
304
+
305
+ describe('#getInstitutions', () => {
306
+ const country = 'IE';
307
+ it('calls goCardlessClient and fetch institution details', async () => {
308
+ getInstitutionsSpy.mockResolvedValue([mockInstitution]);
309
+
310
+ expect(await goCardlessService.getInstitutions({ country })).toEqual([
311
+ mockInstitution,
312
+ ]);
313
+ expect(getInstitutionsSpy).toBeCalledTimes(1);
314
+ });
315
+ });
316
+
317
+ describe('#getInstitution', () => {
318
+ const institutionId = 'fake-institution-id';
319
+ it('calls goCardlessClient and fetch institution details', async () => {
320
+ getInstitutionSpy.mockResolvedValue(mockInstitution);
321
+
322
+ expect(await goCardlessService.getInstitution(institutionId)).toEqual(
323
+ mockInstitution,
324
+ );
325
+ expect(getInstitutionSpy).toBeCalledTimes(1);
326
+ });
327
+ });
328
+
329
+ describe('#extendAccountsAboutInstitutions', () => {
330
+ it('extends accounts with the corresponding institution', async () => {
331
+ const institutionA = { ...mockInstitution, id: 'INSTITUTION_A' };
332
+ const institutionB = { ...mockInstitution, id: 'INSTITUTION_B' };
333
+ const accountAA = {
334
+ ...mockDetailedAccount,
335
+ id: 'AA',
336
+ institution_id: 'INSTITUTION_A',
337
+ };
338
+ const accountBB = {
339
+ ...mockDetailedAccount,
340
+ id: 'BB',
341
+ institution_id: 'INSTITUTION_B',
342
+ };
343
+
344
+ const accounts = [accountAA, accountBB];
345
+ const institutions = [institutionA, institutionB];
346
+
347
+ const expected = [
348
+ {
349
+ ...accountAA,
350
+ institution: institutionA,
351
+ },
352
+ {
353
+ ...accountBB,
354
+ institution: institutionB,
355
+ },
356
+ ];
357
+
358
+ const result = await goCardlessService.extendAccountsAboutInstitutions({
359
+ accounts,
360
+ institutions,
361
+ });
362
+
363
+ expect(result).toEqual(expected);
364
+ });
365
+
366
+ it('returns accounts with missing institutions as null', async () => {
367
+ const accountAA = {
368
+ ...mockDetailedAccount,
369
+ id: 'AA',
370
+ institution_id: 'INSTITUTION_A',
371
+ };
372
+ const accountBB = {
373
+ ...mockDetailedAccount,
374
+ id: 'BB',
375
+ institution_id: 'INSTITUTION_B',
376
+ };
377
+
378
+ const accounts = [accountAA, accountBB];
379
+
380
+ const institutionA = { ...mockInstitution, id: 'INSTITUTION_A' };
381
+ const institutions = [institutionA];
382
+
383
+ const expected = [
384
+ {
385
+ ...accountAA,
386
+ institution: institutionA,
387
+ },
388
+ {
389
+ ...accountBB,
390
+ institution: null,
391
+ },
392
+ ];
393
+
394
+ const result = await goCardlessService.extendAccountsAboutInstitutions({
395
+ accounts,
396
+ institutions,
397
+ });
398
+
399
+ expect(result).toEqual(expected);
400
+ });
401
+ });
402
+
403
+ describe('#getTransactions', () => {
404
+ it('calls goCardlessClient and fetch transactions for provided accountId', async () => {
405
+ getTransactionsSpy.mockResolvedValue(mockTransactions);
406
+
407
+ expect(
408
+ await goCardlessService.getTransactions({
409
+ institutionId: 'SANDBOXFINANCE_SFIN0000',
410
+ accountId,
411
+ startDate: '',
412
+ endDate: '',
413
+ }),
414
+ ).toMatchInlineSnapshot(`
415
+ {
416
+ "transactions": {
417
+ "booked": [
418
+ {
419
+ "bankTransactionCode": "string",
420
+ "bookingDate": "2000-01-01",
421
+ "date": "2000-01-01",
422
+ "debtorAccount": {
423
+ "iban": "string",
424
+ },
425
+ "debtorName": "string",
426
+ "notes": undefined,
427
+ "payeeName": "String (stri XXX ring)",
428
+ "remittanceInformationStructuredArrayString": undefined,
429
+ "remittanceInformationUnstructuredArrayString": undefined,
430
+ "transactionAmount": {
431
+ "amount": "328.18",
432
+ "currency": "EUR",
433
+ },
434
+ "transactionId": "string",
435
+ "valueDate": "2000-01-01",
436
+ },
437
+ {
438
+ "bankTransactionCode": "string",
439
+ "bookingDate": "2000-01-01",
440
+ "date": "2000-01-01",
441
+ "notes": undefined,
442
+ "payeeName": "",
443
+ "remittanceInformationStructuredArrayString": undefined,
444
+ "remittanceInformationUnstructuredArrayString": undefined,
445
+ "transactionAmount": {
446
+ "amount": "947.26",
447
+ "currency": "EUR",
448
+ },
449
+ "transactionId": "string",
450
+ "valueDate": "2000-01-01",
451
+ },
452
+ ],
453
+ "pending": [
454
+ {
455
+ "date": "2000-01-01",
456
+ "notes": undefined,
457
+ "payeeName": "",
458
+ "remittanceInformationStructuredArrayString": undefined,
459
+ "remittanceInformationUnstructuredArrayString": undefined,
460
+ "transactionAmount": {
461
+ "amount": "947.26",
462
+ "currency": "EUR",
463
+ },
464
+ "valueDate": "2000-01-01",
465
+ },
466
+ ],
467
+ },
468
+ }
469
+ `);
470
+ expect(getTransactionsSpy).toBeCalledTimes(1);
471
+ });
472
+ });
473
+
474
+ describe('#getBalances', () => {
475
+ it('calls goCardlessClient and fetch balances for provided accountId', async () => {
476
+ getBalancesSpy.mockResolvedValue(mockedBalances);
477
+
478
+ expect(await goCardlessService.getBalances(accountId)).toEqual(
479
+ mockedBalances,
480
+ );
481
+ expect(getBalancesSpy).toBeCalledTimes(1);
482
+ });
483
+ });
484
+ });
485
+
486
+ describe('#handleGoCardlessError', () => {
487
+ it('throws InvalidInputDataError for status code 400', () => {
488
+ const response = { response: { status: 400 } };
489
+ expect(() => handleGoCardlessError(response)).toThrow(
490
+ InvalidInputDataError,
491
+ );
492
+ });
493
+
494
+ it('throws InvalidGoCardlessTokenError for status code 401', () => {
495
+ const response = { response: { status: 401 } };
496
+ expect(() => handleGoCardlessError(response)).toThrow(
497
+ InvalidGoCardlessTokenError,
498
+ );
499
+ });
500
+
501
+ it('throws AccessDeniedError for status code 403', () => {
502
+ const response = { response: { status: 403 } };
503
+ expect(() => handleGoCardlessError(response)).toThrow(AccessDeniedError);
504
+ });
505
+
506
+ it('throws NotFoundError for status code 404', () => {
507
+ const response = { response: { status: 404 } };
508
+ expect(() => handleGoCardlessError(response)).toThrow(NotFoundError);
509
+ });
510
+
511
+ it('throws ResourceSuspended for status code 409', () => {
512
+ const response = { response: { status: 409 } };
513
+ expect(() => handleGoCardlessError(response)).toThrow(ResourceSuspended);
514
+ });
515
+
516
+ it('throws RateLimitError for status code 429', () => {
517
+ const response = { response: { status: 429 } };
518
+ expect(() => handleGoCardlessError(response)).toThrow(RateLimitError);
519
+ });
520
+
521
+ it('throws UnknownError for status code 500', () => {
522
+ const response = { response: { status: 500 } };
523
+ expect(() => handleGoCardlessError(response)).toThrow(UnknownError);
524
+ });
525
+
526
+ it('throws ServiceError for status code 503', () => {
527
+ const response = { response: { status: 503 } };
528
+ expect(() => handleGoCardlessError(response)).toThrow(ServiceError);
529
+ });
530
+
531
+ it('throws a generic error when the status code is not recognised', () => {
532
+ const response = { response: { status: 0 } };
533
+ expect(() => handleGoCardlessError(response)).toThrow(
534
+ GenericGoCardlessError,
535
+ );
536
+ });
537
+ });
@@ -0,0 +1,20 @@
1
+ import { BankFactory, banks } from '../bank-factory.js';
2
+ import IntegrationBank from '../banks/integration-bank.js';
3
+
4
+ describe('BankFactory', () => {
5
+ it.each(banks.flatMap(bank => bank.institutionIds))(
6
+ `should return same institutionId`,
7
+ institutionId => {
8
+ const result = BankFactory(institutionId);
9
+
10
+ expect(result.institutionIds).toContain(institutionId);
11
+ },
12
+ );
13
+
14
+ it('should return IntegrationBank when institutionId is not found', () => {
15
+ const institutionId = IntegrationBank.institutionIds[0];
16
+ const result = BankFactory('fake-id-not-found');
17
+
18
+ expect(result.institutionIds).toContain(institutionId);
19
+ });
20
+ });