@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,105 @@
1
+ import { mockTransactionAmount } from '../../services/tests/fixtures.js';
2
+ import Nationwide from '../nationwide_naiagb21.js';
3
+
4
+ describe('Nationwide', () => {
5
+ describe('#normalizeTransaction', () => {
6
+ it('retains date for booked transaction', () => {
7
+ const d = new Date();
8
+ d.setDate(d.getDate() - 7);
9
+
10
+ const date = d.toISOString().split('T')[0];
11
+
12
+ const transaction = {
13
+ bookingDate: date,
14
+ transactionAmount: mockTransactionAmount,
15
+ };
16
+
17
+ const normalizedTransaction = Nationwide.normalizeTransaction(
18
+ transaction,
19
+ true,
20
+ );
21
+
22
+ expect(normalizedTransaction.date).toEqual(date);
23
+ });
24
+
25
+ it('fixes date for pending transactions', () => {
26
+ const d = new Date();
27
+ const date = d.toISOString().split('T')[0];
28
+
29
+ const transaction = {
30
+ bookingDate: date,
31
+ transactionAmount: mockTransactionAmount,
32
+ };
33
+
34
+ const normalizedTransaction = Nationwide.normalizeTransaction(
35
+ transaction,
36
+ false,
37
+ );
38
+
39
+ expect(new Date(normalizedTransaction.date).getTime()).toBeLessThan(
40
+ d.getTime(),
41
+ );
42
+ });
43
+
44
+ it('keeps transactionId if in the correct format', () => {
45
+ const transactionId = 'a896729bb8b30b5ca862fe70bd5967185e2b5d3a';
46
+ const transaction = {
47
+ bookingDate: '2024-01-01T00:00:00Z',
48
+ transactionId,
49
+ transactionAmount: mockTransactionAmount,
50
+ };
51
+
52
+ const normalizedTransaction = Nationwide.normalizeTransaction(
53
+ transaction,
54
+ false,
55
+ );
56
+
57
+ expect(normalizedTransaction.transactionId).toBe(transactionId);
58
+ });
59
+
60
+ it('unsets transactionId if not valid length', () => {
61
+ const transaction = {
62
+ bookingDate: '2024-01-01T00:00:00Z',
63
+ transactionId: '0123456789',
64
+ transactionAmount: mockTransactionAmount,
65
+ };
66
+
67
+ const normalizedTransaction = Nationwide.normalizeTransaction(
68
+ transaction,
69
+ false,
70
+ );
71
+
72
+ expect(normalizedTransaction.transactionId).toBeNull();
73
+ });
74
+
75
+ it('unsets transactionId if debit placeholder found', () => {
76
+ const transaction = {
77
+ bookingDate: '2024-01-01T00:00:00Z',
78
+ transactionId: '00DEBIT202401010000000000-1000SUPERMARKET',
79
+ transactionAmount: mockTransactionAmount,
80
+ };
81
+
82
+ const normalizedTransaction = Nationwide.normalizeTransaction(
83
+ transaction,
84
+ false,
85
+ );
86
+
87
+ expect(normalizedTransaction.transactionId).toBeNull();
88
+ });
89
+
90
+ it('unsets transactionId if credit placeholder found', () => {
91
+ const transaction = {
92
+ bookingDate: '2024-01-01T00:00:00Z',
93
+ transactionId: '00CREDIT202401010000000000-1000SUPERMARKET',
94
+ transactionAmount: mockTransactionAmount,
95
+ };
96
+
97
+ const normalizedTransaction = Nationwide.normalizeTransaction(
98
+ transaction,
99
+ false,
100
+ );
101
+
102
+ expect(normalizedTransaction.transactionId).toBeNull();
103
+ });
104
+ });
105
+ });
@@ -0,0 +1,48 @@
1
+ import NbgEthngraaxxx from '../nbg_ethngraaxxx.js';
2
+
3
+ describe('NbgEthngraaxxx', () => {
4
+ describe('#normalizeTransaction', () => {
5
+ it('provides correct amount in pending transaction and removes payee prefix', () => {
6
+ const transaction = {
7
+ bookingDate: '2024-09-03',
8
+ date: '2024-09-03',
9
+ remittanceInformationUnstructured: 'ΑΓΟΡΑ testingson',
10
+ transactionAmount: {
11
+ amount: '100.00',
12
+ currency: 'EUR',
13
+ },
14
+ valueDate: '2024-09-03',
15
+ };
16
+
17
+ const normalizedTransaction = NbgEthngraaxxx.normalizeTransaction(
18
+ transaction,
19
+ false,
20
+ );
21
+
22
+ expect(normalizedTransaction.transactionAmount.amount).toEqual('-100.00');
23
+ expect(normalizedTransaction.payeeName).toEqual('Testingson');
24
+ });
25
+ });
26
+
27
+ it('provides correct amount and payee in booked transaction', () => {
28
+ const transaction = {
29
+ transactionId: 'O244015L68IK',
30
+ bookingDate: '2024-09-03',
31
+ date: '2024-09-03',
32
+ remittanceInformationUnstructured: 'testingson',
33
+ transactionAmount: {
34
+ amount: '-100.00',
35
+ currency: 'EUR',
36
+ },
37
+ valueDate: '2024-09-03',
38
+ };
39
+
40
+ const normalizedTransaction = NbgEthngraaxxx.normalizeTransaction(
41
+ transaction,
42
+ true,
43
+ );
44
+
45
+ expect(normalizedTransaction.transactionAmount.amount).toEqual('-100.00');
46
+ expect(normalizedTransaction.payeeName).toEqual('Testingson');
47
+ });
48
+ });
@@ -0,0 +1,42 @@
1
+ import RevolutRevolt21 from '../revolut_revolt21.js';
2
+
3
+ describe('RevolutRevolt21', () => {
4
+ describe('#normalizeTransaction', () => {
5
+ it('returns the expected remittanceInformationUnstructured from a bizum expense transfer', () => {
6
+ const transaction = {
7
+ transactionAmount: { amount: '-1.00', currency: 'EUR' },
8
+ remittanceInformationUnstructuredArray: [
9
+ 'Bizum payment to: CREDITOR NAME',
10
+ 'Bizum description',
11
+ ],
12
+ bookingDate: '2024-09-21',
13
+ };
14
+
15
+ const normalizedTransaction = RevolutRevolt21.normalizeTransaction(
16
+ transaction,
17
+ true,
18
+ );
19
+
20
+ expect(normalizedTransaction.notes).toEqual('Bizum description');
21
+ });
22
+ });
23
+
24
+ it('returns the expected payeeName and remittanceInformationUnstructured from a bizum income transfer', () => {
25
+ const transaction = {
26
+ transactionAmount: { amount: '1.00', currency: 'EUR' },
27
+ remittanceInformationUnstructuredArray: [
28
+ 'Bizum payment from: DEBTOR NAME',
29
+ 'Bizum description',
30
+ ],
31
+ bookingDate: '2024-09-21',
32
+ };
33
+
34
+ const normalizedTransaction = RevolutRevolt21.normalizeTransaction(
35
+ transaction,
36
+ true,
37
+ );
38
+
39
+ expect(normalizedTransaction.payeeName).toEqual('DEBTOR NAME');
40
+ expect(normalizedTransaction.notes).toEqual('Bizum description');
41
+ });
42
+ });
@@ -0,0 +1,133 @@
1
+ import SandboxfinanceSfin0000 from '../sandboxfinance_sfin0000.js';
2
+
3
+ describe('SandboxfinanceSfin0000', () => {
4
+ describe('#normalizeAccount', () => {
5
+ /** @type {import('../../gocardless.types.js').DetailedAccountWithInstitution} */
6
+ const accountRaw = {
7
+ resourceId: '01F3NS5ASCNMVCTEJDT0G215YE',
8
+ iban: 'GL0865354374424724',
9
+ currency: 'EUR',
10
+ ownerName: 'Jane Doe',
11
+ name: 'Main Account',
12
+ product: 'Checkings',
13
+ cashAccountType: 'CACC',
14
+ id: '99a0bfe2-0bef-46df-bff2-e9ae0c6c5838',
15
+ created: '2022-02-21T13:43:55.608911Z',
16
+ last_accessed: '2023-01-25T16:50:15.078264Z',
17
+ institution_id: 'SANDBOXFINANCE_SFIN0000',
18
+ status: 'READY',
19
+ owner_name: 'Jane Doe',
20
+ institution: {
21
+ id: 'SANDBOXFINANCE_SFIN0000',
22
+ name: 'Sandbox Finance',
23
+ bic: 'SFIN0000',
24
+ transaction_total_days: '90',
25
+ max_access_valid_for_days: '90',
26
+ countries: ['XX'],
27
+ logo: 'https://cdn.nordigen.com/ais/SANDBOXFINANCE_SFIN0000.png',
28
+ supported_payments: {},
29
+ supported_features: [],
30
+ },
31
+ };
32
+
33
+ it('returns normalized account data returned to Frontend', () => {
34
+ expect(SandboxfinanceSfin0000.normalizeAccount(accountRaw))
35
+ .toMatchInlineSnapshot(`
36
+ {
37
+ "account_id": "99a0bfe2-0bef-46df-bff2-e9ae0c6c5838",
38
+ "iban": "GL0865354374424724",
39
+ "institution": {
40
+ "bic": "SFIN0000",
41
+ "countries": [
42
+ "XX",
43
+ ],
44
+ "id": "SANDBOXFINANCE_SFIN0000",
45
+ "logo": "https://cdn.nordigen.com/ais/SANDBOXFINANCE_SFIN0000.png",
46
+ "max_access_valid_for_days": "90",
47
+ "name": "Sandbox Finance",
48
+ "supported_features": [],
49
+ "supported_payments": {},
50
+ "transaction_total_days": "90",
51
+ },
52
+ "mask": "4724",
53
+ "name": "Main Account (XXX 4724) EUR",
54
+ "official_name": "Checkings",
55
+ "type": "checking",
56
+ }
57
+ `);
58
+ });
59
+ });
60
+
61
+ describe('#sortTransactions', () => {
62
+ it('handles empty arrays', () => {
63
+ const transactions = [];
64
+ const sortedTransactions =
65
+ SandboxfinanceSfin0000.sortTransactions(transactions);
66
+ expect(sortedTransactions).toEqual([]);
67
+ });
68
+
69
+ it('returns empty array for undefined input', () => {
70
+ const sortedTransactions =
71
+ SandboxfinanceSfin0000.sortTransactions(undefined);
72
+ expect(sortedTransactions).toEqual([]);
73
+ });
74
+ });
75
+
76
+ describe('#countStartingBalance', () => {
77
+ /** @type {import('../../gocardless-node.types.js').Balance[]} */
78
+ const balances = [
79
+ {
80
+ balanceAmount: { amount: '1000.00', currency: 'PLN' },
81
+ balanceType: 'interimAvailable',
82
+ },
83
+ ];
84
+
85
+ it('should calculate the starting balance correctly', () => {
86
+ const sortedTransactions = [
87
+ {
88
+ transactionId: '2022-01-01-1',
89
+ transactionAmount: { amount: '-100.00', currency: 'USD' },
90
+ },
91
+ {
92
+ transactionId: '2022-01-01-2',
93
+ transactionAmount: { amount: '50.00', currency: 'USD' },
94
+ },
95
+ {
96
+ transactionId: '2022-01-01-3',
97
+ transactionAmount: { amount: '-25.00', currency: 'USD' },
98
+ },
99
+ ];
100
+
101
+ const startingBalance = SandboxfinanceSfin0000.calculateStartingBalance(
102
+ sortedTransactions,
103
+ balances,
104
+ );
105
+
106
+ expect(startingBalance).toEqual(107500);
107
+ });
108
+
109
+ it('returns the same balance amount when no transactions', () => {
110
+ const transactions = [];
111
+
112
+ expect(
113
+ SandboxfinanceSfin0000.calculateStartingBalance(transactions, balances),
114
+ ).toEqual(100000);
115
+ });
116
+
117
+ it('returns the balance minus the available transactions', () => {
118
+ /** @type {import('../../gocardless-node.types.js').Transaction[]} */
119
+ const transactions = [
120
+ {
121
+ transactionAmount: { amount: '200.00', currency: 'PLN' },
122
+ },
123
+ {
124
+ transactionAmount: { amount: '300.50', currency: 'PLN' },
125
+ },
126
+ ];
127
+
128
+ expect(
129
+ SandboxfinanceSfin0000.calculateStartingBalance(transactions, balances),
130
+ ).toEqual(49950);
131
+ });
132
+ });
133
+ });
@@ -0,0 +1,256 @@
1
+ import SpkMarburgBiedenkopfHeladef1mar from '../spk_marburg_biedenkopf_heladef1mar.js';
2
+
3
+ describe('SpkMarburgBiedenkopfHeladef1mar', () => {
4
+ describe('#normalizeAccount', () => {
5
+ /** @type {import('../../gocardless.types.js').DetailedAccountWithInstitution} */
6
+ const accountRaw = {
7
+ resourceId: 'e896eec6-6096-4efc-a941-756bd9d74765',
8
+ iban: 'DE50533500000123456789',
9
+ currency: 'EUR',
10
+ ownerName: 'JANE DOE',
11
+ product: 'Sichteinlagen',
12
+ bic: 'HELADEF1MAR',
13
+ usage: 'PRIV',
14
+ id: 'a787ba27-02ee-4fd6-be86-73831adc5498',
15
+ created: '2024-01-01T14:17:11.630352Z',
16
+ last_accessed: '2024-01-01T14:19:42.709478Z',
17
+ institution_id: 'SPK_MARBURG_BIEDENKOPF_HELADEF1MAR',
18
+ status: 'READY',
19
+ owner_name: 'JANE DOE',
20
+ institution: {
21
+ id: 'SPK_MARBURG_BIEDENKOPF_HELADEF1MAR',
22
+ name: 'Sparkasse Marburg-Biedenkopf',
23
+ bic: 'HELADEF1MAR',
24
+ transaction_total_days: '360',
25
+ max_access_valid_for_days: '90',
26
+ countries: ['DE'],
27
+ logo: 'https://storage.googleapis.com/gc-prd-institution_icons-production/DE/PNG/sparkasse.png',
28
+ supported_payments: {
29
+ 'single-payment': ['SCT', 'ISCT'],
30
+ },
31
+ supported_features: [
32
+ 'card_accounts',
33
+ 'payments',
34
+ 'pending_transactions',
35
+ ],
36
+ /*"identification_codes": []*/
37
+ },
38
+ };
39
+
40
+ it('returns normalized account data returned to Frontend', () => {
41
+ expect(
42
+ SpkMarburgBiedenkopfHeladef1mar.normalizeAccount(accountRaw),
43
+ ).toEqual({
44
+ account_id: 'a787ba27-02ee-4fd6-be86-73831adc5498',
45
+ iban: 'DE50533500000123456789',
46
+ institution: {
47
+ bic: 'HELADEF1MAR',
48
+ countries: ['DE'],
49
+ id: 'SPK_MARBURG_BIEDENKOPF_HELADEF1MAR',
50
+ logo: 'https://storage.googleapis.com/gc-prd-institution_icons-production/DE/PNG/sparkasse.png',
51
+ name: 'Sparkasse Marburg-Biedenkopf',
52
+ supported_features: [
53
+ 'card_accounts',
54
+ 'payments',
55
+ 'pending_transactions',
56
+ ],
57
+ supported_payments: {
58
+ 'single-payment': ['SCT', 'ISCT'],
59
+ },
60
+ transaction_total_days: '360',
61
+ max_access_valid_for_days: '90',
62
+ },
63
+ mask: '6789',
64
+ name: 'Sichteinlagen (XXX 6789) EUR',
65
+ official_name: 'Sichteinlagen',
66
+ type: 'checking',
67
+ });
68
+ });
69
+ });
70
+
71
+ const transactionsRaw = [
72
+ {
73
+ transactionId: 'fefa0b605ac14a7eb14f4c8ab6a6af55',
74
+ bookingDate: '2023-12-29',
75
+ valueDate: '2023-12-29',
76
+ transactionAmount: {
77
+ amount: '-40.00',
78
+ currency: 'EUR',
79
+ },
80
+ creditorName: 'JET Tankstelle',
81
+ remittanceInformationStructured: 'AUTORISATION 28.12. 18:30',
82
+ proprietaryBankTransactionCode: 'NSTO+000+0000+000-AA',
83
+ internalTransactionId: '761660c052ed48e78c2be39775f08da9',
84
+ date: '2023-12-29',
85
+ },
86
+ {
87
+ transactionId: '1a8e5d0df259472694f13132001af0a6',
88
+ bookingDate: '2023-12-28',
89
+ valueDate: '2023-12-28',
90
+ transactionAmount: {
91
+ amount: '-1242.47',
92
+ currency: 'EUR',
93
+ },
94
+ creditorName: 'Peter Muster',
95
+ remittanceInformationStructured: 'Miete 12/2023',
96
+ proprietaryBankTransactionCode: 'NSTO+111+1111+111-BB',
97
+ internalTransactionId: '5a20ac78b146401e940b6fee30ee404b',
98
+ date: '2023-12-28',
99
+ },
100
+ {
101
+ transactionId: '166983e65ec54000a361a952e6161f33',
102
+ bookingDate: '2023-12-27',
103
+ valueDate: '2023-12-27',
104
+ transactionAmount: {
105
+ amount: '1541.23',
106
+ currency: 'EUR',
107
+ },
108
+ debtorName: 'Arbeitgeber AG',
109
+ remittanceInformationStructured: 'Lohn/Gehalt 12/2023',
110
+ proprietaryBankTransactionCode: 'NSTO+222+2222+222-CC',
111
+ internalTransactionId: '51630dda877f45f186d315b8058d891a',
112
+ date: '2023-12-27',
113
+ },
114
+ {
115
+ transactionId: '4dd9f4c9968a45739c0705ebc675b54b',
116
+ bookingDate: '2023-12-26',
117
+ valueDate: '2023-12-26',
118
+ transactionAmount: {
119
+ amount: '-8.00',
120
+ currency: 'EUR',
121
+ },
122
+ remittanceInformationStructuredArray: [
123
+ 'Entgeltabrechnung',
124
+ 'siehe Anlage',
125
+ ],
126
+ proprietaryBankTransactionCode: 'NSTO+333+3333+333-DD',
127
+ internalTransactionId: '9c58c87c2d1644e4a5e149c837c16bbb',
128
+ date: '2023-12-26',
129
+ },
130
+ ];
131
+
132
+ describe('#normalizeTransaction', () => {
133
+ it('fallbacks to remittanceInformationStructured when remittanceInformationUnstructed is not set', () => {
134
+ const transaction = {
135
+ transactionId: 'fefa0b605ac14a7eb14f4c8ab6a6af55',
136
+ bookingDate: '2023-12-29',
137
+ valueDate: '2023-12-29',
138
+ transactionAmount: {
139
+ amount: '-40.00',
140
+ currency: 'EUR',
141
+ },
142
+ creditorName: 'JET Tankstelle',
143
+ remittanceInformationStructured: 'AUTORISATION 28.12. 18:30',
144
+ proprietaryBankTransactionCode: 'NSTO+000+0000+000-AA',
145
+ internalTransactionId: '761660c052ed48e78c2be39775f08da9',
146
+ date: '2023-12-29',
147
+ };
148
+
149
+ expect(
150
+ SpkMarburgBiedenkopfHeladef1mar.normalizeTransaction(transaction, true)
151
+ .notes,
152
+ ).toEqual('AUTORISATION 28.12. 18:30');
153
+ });
154
+
155
+ it('fallbacks to remittanceInformationStructuredArray when remittanceInformationUnstructed and remittanceInformationStructured is not set', () => {
156
+ const transaction = {
157
+ transactionId: '4dd9f4c9968a45739c0705ebc675b54b',
158
+ bookingDate: '2023-12-26',
159
+ valueDate: '2023-12-26',
160
+ transactionAmount: {
161
+ amount: '-8.00',
162
+ currency: 'EUR',
163
+ },
164
+ remittanceInformationStructuredArray: [
165
+ 'Entgeltabrechnung',
166
+ 'siehe Anlage',
167
+ ],
168
+ proprietaryBankTransactionCode: 'NSTO+333+3333+333-DD',
169
+ internalTransactionId: '9c58c87c2d1644e4a5e149c837c16bbb',
170
+ date: '2023-12-26',
171
+ };
172
+
173
+ expect(
174
+ SpkMarburgBiedenkopfHeladef1mar.normalizeTransaction(transaction, true)
175
+ .notes,
176
+ ).toEqual('Entgeltabrechnung siehe Anlage');
177
+ });
178
+ });
179
+
180
+ describe('#sortTransactions', () => {
181
+ it('handles empty arrays', () => {
182
+ const transactions = [];
183
+ const sortedTransactions =
184
+ SpkMarburgBiedenkopfHeladef1mar.sortTransactions(transactions);
185
+ expect(sortedTransactions).toEqual([]);
186
+ });
187
+
188
+ it('returns empty array for undefined input', () => {
189
+ const sortedTransactions =
190
+ SpkMarburgBiedenkopfHeladef1mar.sortTransactions(undefined);
191
+ expect(sortedTransactions).toEqual([]);
192
+ });
193
+
194
+ it('returns sorted array for unsorted inputs', () => {
195
+ const normalizeTransactions = transactionsRaw.map(tx =>
196
+ SpkMarburgBiedenkopfHeladef1mar.normalizeTransaction(tx, true),
197
+ );
198
+ const originalOrder = Array.from(normalizeTransactions);
199
+ const swap = (a, b) => {
200
+ const swap = normalizeTransactions[a];
201
+ normalizeTransactions[a] = normalizeTransactions[b];
202
+ normalizeTransactions[b] = swap;
203
+ };
204
+ swap(1, 4);
205
+ swap(3, 6);
206
+ swap(0, 7);
207
+ const sortedTransactions =
208
+ SpkMarburgBiedenkopfHeladef1mar.sortTransactions(normalizeTransactions);
209
+ expect(sortedTransactions).toEqual(originalOrder);
210
+ });
211
+ });
212
+
213
+ describe('#countStartingBalance', () => {
214
+ /** @type {import('../../gocardless-node.types.js').Balance[]} */
215
+ const balances = [
216
+ {
217
+ balanceAmount: { amount: '3596.87', currency: 'EUR' },
218
+ balanceType: 'closingBooked',
219
+ referenceDate: '2023-12-29',
220
+ },
221
+ ];
222
+
223
+ it('should return 0 when no transactions or balances are provided', () => {
224
+ const startingBalance =
225
+ SpkMarburgBiedenkopfHeladef1mar.calculateStartingBalance([], []);
226
+ expect(startingBalance).toEqual(0);
227
+ });
228
+
229
+ it('should calculate the starting balance correctly', () => {
230
+ const normalizeTransactions = transactionsRaw.map(tx =>
231
+ SpkMarburgBiedenkopfHeladef1mar.normalizeTransaction(tx, true),
232
+ );
233
+ const sortedTransactions =
234
+ SpkMarburgBiedenkopfHeladef1mar.sortTransactions(normalizeTransactions);
235
+
236
+ const startingBalance =
237
+ SpkMarburgBiedenkopfHeladef1mar.calculateStartingBalance(
238
+ sortedTransactions,
239
+ balances,
240
+ );
241
+
242
+ expect(startingBalance).toEqual(334611);
243
+ });
244
+
245
+ it('returns the same balance amount when no transactions', () => {
246
+ const transactions = [];
247
+
248
+ expect(
249
+ SpkMarburgBiedenkopfHeladef1mar.calculateStartingBalance(
250
+ transactions,
251
+ balances,
252
+ ),
253
+ ).toEqual(359687);
254
+ });
255
+ });
256
+ });
@@ -0,0 +1,102 @@
1
+ import { jest } from '@jest/globals';
2
+
3
+ import SskDusseldorfDussdeddxxx from '../ssk_dusseldorf_dussdeddxxx.js';
4
+
5
+ describe('ssk_dusseldorf_dussdeddxxx', () => {
6
+ let consoleSpy;
7
+
8
+ beforeEach(() => {
9
+ consoleSpy = jest.spyOn(console, 'debug');
10
+ });
11
+
12
+ afterEach(() => {
13
+ consoleSpy.mockRestore();
14
+ });
15
+
16
+ describe('#normalizeTransaction', () => {
17
+ const bookedTransactionOne = {
18
+ transactionId: '2024102900000000-1',
19
+ bookingDate: '2024-10-29',
20
+ valueDate: '2024-10-29',
21
+ transactionAmount: {
22
+ amount: '-99.99',
23
+ currency: 'EUR',
24
+ },
25
+ creditorName: 'a useful creditor name',
26
+ remittanceInformationStructured: 'structured information',
27
+ remittanceInformationUnstructured: 'unstructured information',
28
+ additionalInformation: 'some additional information',
29
+ };
30
+
31
+ const bookedTransactionTwo = {
32
+ transactionId: '2024102900000000-2',
33
+ bookingDate: '2024-10-29',
34
+ valueDate: '2024-10-29',
35
+ transactionAmount: {
36
+ amount: '-99.99',
37
+ currency: 'EUR',
38
+ },
39
+ creditorName: 'a useful creditor name',
40
+ ultimateCreditor: 'ultimate creditor',
41
+ remittanceInformationStructured: 'structured information',
42
+ additionalInformation: 'some additional information',
43
+ };
44
+
45
+ it('properly combines remittance information', () => {
46
+ expect(
47
+ SskDusseldorfDussdeddxxx.normalizeTransaction(
48
+ bookedTransactionOne,
49
+ true,
50
+ ).notes,
51
+ ).toEqual('unstructured information some additional information');
52
+
53
+ expect(
54
+ SskDusseldorfDussdeddxxx.normalizeTransaction(
55
+ bookedTransactionTwo,
56
+ true,
57
+ ).notes,
58
+ ).toEqual('structured information some additional information');
59
+ });
60
+
61
+ it('prioritizes creditor names correctly', () => {
62
+ expect(
63
+ SskDusseldorfDussdeddxxx.normalizeTransaction(
64
+ bookedTransactionOne,
65
+ true,
66
+ ).payeeName,
67
+ ).toEqual('A Useful Creditor Name');
68
+
69
+ expect(
70
+ SskDusseldorfDussdeddxxx.normalizeTransaction(
71
+ bookedTransactionTwo,
72
+ true,
73
+ ).payeeName,
74
+ ).toEqual('Ultimate Creditor');
75
+ });
76
+
77
+ const unbookedTransaction = {
78
+ transactionId: '2024102900000000-1',
79
+ valueDate: '2024-10-29',
80
+ transactionAmount: {
81
+ amount: '-99.99',
82
+ currency: 'EUR',
83
+ },
84
+ creditorName: 'some nonsensical creditor',
85
+ remittanceInformationUnstructured: 'some nonsensical information',
86
+ };
87
+
88
+ it('returns null for unbooked transactions', () => {
89
+ expect(
90
+ SskDusseldorfDussdeddxxx.normalizeTransaction(
91
+ unbookedTransaction,
92
+ false,
93
+ ),
94
+ ).toBeNull();
95
+
96
+ expect(consoleSpy).toHaveBeenCalledWith(
97
+ 'Skipping unbooked transaction:',
98
+ unbookedTransaction.transactionId,
99
+ );
100
+ });
101
+ });
102
+ });