@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.
- package/.dockerignore +12 -0
- package/README.md +19 -0
- package/app.js +11 -0
- package/babel.config.json +3 -0
- package/bin/@actual-app/sync-server +55 -0
- package/docker/alpine.Dockerfile +62 -0
- package/docker/ubuntu.Dockerfile +63 -0
- package/docker-compose.yml +29 -0
- package/jest.config.json +19 -0
- package/jest.global-setup.js +101 -0
- package/jest.global-teardown.js +6 -0
- package/migrations/1694360000000-create-folders.js +25 -0
- package/migrations/1694360479680-create-account-db.js +30 -0
- package/migrations/1694362247011-create-secret-table.js +16 -0
- package/migrations/1702667624000-rename-nordigen-secrets.js +19 -0
- package/migrations/1718889148000-openid.js +41 -0
- package/migrations/1719409568000-multiuser.js +116 -0
- package/package.json +64 -0
- package/src/account-db.js +239 -0
- package/src/accounts/openid.js +361 -0
- package/src/accounts/password.js +149 -0
- package/src/app-account.js +155 -0
- package/src/app-admin.js +410 -0
- package/src/app-admin.test.js +381 -0
- package/src/app-gocardless/README.md +198 -0
- package/src/app-gocardless/app-gocardless.js +274 -0
- package/src/app-gocardless/bank-factory.js +91 -0
- package/src/app-gocardless/banks/abanca_caglesmm.js +22 -0
- package/src/app-gocardless/banks/abnamro_abnanl2a.js +57 -0
- package/src/app-gocardless/banks/american_express_aesudef1.js +40 -0
- package/src/app-gocardless/banks/bancsabadell_bsabesbbb.js +31 -0
- package/src/app-gocardless/banks/bank.interface.ts +51 -0
- package/src/app-gocardless/banks/bank_of_ireland_b365_bofiie2d.js +39 -0
- package/src/app-gocardless/banks/bankinter_bkbkesmm.js +24 -0
- package/src/app-gocardless/banks/belfius_gkccbebb.js +17 -0
- package/src/app-gocardless/banks/berliner_sparkasse_beladebexxx.js +61 -0
- package/src/app-gocardless/banks/bnp_be_gebabebb.js +73 -0
- package/src/app-gocardless/banks/cbc_cregbebb.js +34 -0
- package/src/app-gocardless/banks/commerzbank_cobadeff.js +51 -0
- package/src/app-gocardless/banks/danskebank_dabno22.js +39 -0
- package/src/app-gocardless/banks/direkt_heladef1822.js +18 -0
- package/src/app-gocardless/banks/easybank_bawaatww.js +50 -0
- package/src/app-gocardless/banks/entercard_swednokk.js +40 -0
- package/src/app-gocardless/banks/fortuneo_ftnofrp1xxx.js +46 -0
- package/src/app-gocardless/banks/hype_hyeeit22.js +74 -0
- package/src/app-gocardless/banks/ing_ingbrobu.js +70 -0
- package/src/app-gocardless/banks/ing_ingddeff.js +47 -0
- package/src/app-gocardless/banks/ing_pl_ingbplpw.js +46 -0
- package/src/app-gocardless/banks/integration-bank.js +115 -0
- package/src/app-gocardless/banks/isybank_itbbitmm.js +18 -0
- package/src/app-gocardless/banks/kbc_kredbebb.js +33 -0
- package/src/app-gocardless/banks/lhv-lhvbee22.js +36 -0
- package/src/app-gocardless/banks/mbank_retail_brexplpw.js +56 -0
- package/src/app-gocardless/banks/nationwide_naiagb21.js +46 -0
- package/src/app-gocardless/banks/nbg_ethngraaxxx.js +51 -0
- package/src/app-gocardless/banks/norwegian_xx_norwnok1.js +74 -0
- package/src/app-gocardless/banks/revolut_revolt21.js +37 -0
- package/src/app-gocardless/banks/sandboxfinance_sfin0000.js +28 -0
- package/src/app-gocardless/banks/seb_kort_bank_ab.js +58 -0
- package/src/app-gocardless/banks/seb_privat.js +29 -0
- package/src/app-gocardless/banks/sparnord_spnodk22.js +24 -0
- package/src/app-gocardless/banks/spk_karlsruhe_karsde66.js +61 -0
- package/src/app-gocardless/banks/spk_marburg_biedenkopf_heladef1mar.js +30 -0
- package/src/app-gocardless/banks/spk_worms_alzey_ried_malade51wor.js +19 -0
- package/src/app-gocardless/banks/ssk_dusseldorf_dussdeddxxx.js +50 -0
- package/src/app-gocardless/banks/swedbank_habalv22.js +47 -0
- package/src/app-gocardless/banks/tests/abanca_caglesmm.spec.js +21 -0
- package/src/app-gocardless/banks/tests/abnamro_abnanl2a.spec.js +61 -0
- package/src/app-gocardless/banks/tests/bancsabadell_bsabesbbb.spec.js +53 -0
- package/src/app-gocardless/banks/tests/belfius_gkccbebb.spec.js +22 -0
- package/src/app-gocardless/banks/tests/cbc_cregbebb.spec.js +34 -0
- package/src/app-gocardless/banks/tests/commerzbank_cobadeff.spec.js +110 -0
- package/src/app-gocardless/banks/tests/easybank_bawaatww.spec.js +54 -0
- package/src/app-gocardless/banks/tests/fortuneo_ftnofrp1xxx.spec.js +206 -0
- package/src/app-gocardless/banks/tests/ing_ingddeff.spec.js +302 -0
- package/src/app-gocardless/banks/tests/ing_pl_ingbplpw.spec.js +202 -0
- package/src/app-gocardless/banks/tests/integration_bank.spec.js +158 -0
- package/src/app-gocardless/banks/tests/kbc_kredbebb.spec.js +38 -0
- package/src/app-gocardless/banks/tests/lhv-lhvbee22.spec.js +68 -0
- package/src/app-gocardless/banks/tests/mbank_retail_brexplpw.spec.js +171 -0
- package/src/app-gocardless/banks/tests/nationwide_naiagb21.spec.js +105 -0
- package/src/app-gocardless/banks/tests/nbg_ethngraaxxx.spec.js +48 -0
- package/src/app-gocardless/banks/tests/revolut_revolt21.spec.js +42 -0
- package/src/app-gocardless/banks/tests/sandboxfinance_sfin0000.spec.js +133 -0
- package/src/app-gocardless/banks/tests/spk_marburg_biedenkopf_heladef1mar.spec.js +256 -0
- package/src/app-gocardless/banks/tests/ssk_dusseldorf_dussdeddxxx.spec.js +102 -0
- package/src/app-gocardless/banks/tests/swedbank_habalv22.spec.js +57 -0
- package/src/app-gocardless/banks/tests/virgin_nrnbgb22.spec.js +54 -0
- package/src/app-gocardless/banks/util/extract-payeeName-from-remittanceInfo.js +36 -0
- package/src/app-gocardless/banks/virgin_nrnbgb22.js +39 -0
- package/src/app-gocardless/errors.js +84 -0
- package/src/app-gocardless/gocardless-node.types.ts +497 -0
- package/src/app-gocardless/gocardless.types.ts +93 -0
- package/src/app-gocardless/link.html +18 -0
- package/src/app-gocardless/services/gocardless-service.js +620 -0
- package/src/app-gocardless/services/tests/fixtures.js +181 -0
- package/src/app-gocardless/services/tests/gocardless-service.spec.js +537 -0
- package/src/app-gocardless/tests/bank-factory.spec.js +20 -0
- package/src/app-gocardless/tests/utils.spec.js +162 -0
- package/src/app-gocardless/util/handle-error.js +16 -0
- package/src/app-gocardless/utils.js +45 -0
- package/src/app-openid.js +108 -0
- package/src/app-pluggyai/app-pluggyai.js +215 -0
- package/src/app-pluggyai/pluggyai-service.js +120 -0
- package/src/app-secrets.js +61 -0
- package/src/app-simplefin/app-simplefin.js +418 -0
- package/src/app-sync/errors.js +13 -0
- package/src/app-sync/services/files-service.js +243 -0
- package/src/app-sync/tests/services/files-service.test.js +250 -0
- package/src/app-sync/validation.js +77 -0
- package/src/app-sync.js +391 -0
- package/src/app-sync.test.js +877 -0
- package/src/app.js +145 -0
- package/src/config-types.ts +44 -0
- package/src/db.js +58 -0
- package/src/load-config.js +307 -0
- package/src/migrations.js +36 -0
- package/src/run-migrations.js +8 -0
- package/src/scripts/disable-openid.js +44 -0
- package/src/scripts/enable-openid.js +53 -0
- package/src/scripts/health-check.js +23 -0
- package/src/scripts/reset-password.js +51 -0
- package/src/secrets.test.js +83 -0
- package/src/services/secrets-service.js +94 -0
- package/src/services/user-service.js +272 -0
- package/src/sql/messages.sql +9 -0
- package/src/sync-simple.js +95 -0
- package/src/util/hash.js +5 -0
- package/src/util/middlewares.js +62 -0
- package/src/util/paths.js +13 -0
- package/src/util/payee-name.js +45 -0
- package/src/util/prompt.js +88 -0
- package/src/util/title/index.js +59 -0
- package/src/util/title/lower-case.js +93 -0
- package/src/util/title/specials.js +21 -0
- package/src/util/validate-user.js +68 -0
- package/tsconfig.json +21 -0
|
@@ -0,0 +1,202 @@
|
|
|
1
|
+
import { mockTransactionAmount } from '../../services/tests/fixtures.js';
|
|
2
|
+
import IngPlIngbplpw from '../ing_pl_ingbplpw.js';
|
|
3
|
+
|
|
4
|
+
describe('IngPlIngbplpw', () => {
|
|
5
|
+
describe('#normalizeAccount', () => {
|
|
6
|
+
/** @type {import('../../gocardless.types.js').DetailedAccountWithInstitution} */
|
|
7
|
+
const accountRaw = {
|
|
8
|
+
resourceId: 'PL00000000000000000987654321',
|
|
9
|
+
iban: 'PL00000000000000000987654321',
|
|
10
|
+
currency: 'PLN',
|
|
11
|
+
ownerName: 'John Example',
|
|
12
|
+
product: 'Current Account for Individuals (Retail)',
|
|
13
|
+
bic: 'INGBPLPW',
|
|
14
|
+
ownerAddressUnstructured: [
|
|
15
|
+
'UL. EXAMPLE STREET 10 M.1',
|
|
16
|
+
'00-000 WARSZAWA',
|
|
17
|
+
],
|
|
18
|
+
id: 'd3eccc94-9536-48d3-98be-813f79199ee3',
|
|
19
|
+
created: '2022-07-24T20:45:47.929582Z',
|
|
20
|
+
last_accessed: '2023-01-24T22:12:00.193558Z',
|
|
21
|
+
institution_id: 'ING_PL_INGBPLPW',
|
|
22
|
+
status: 'READY',
|
|
23
|
+
owner_name: '',
|
|
24
|
+
institution: {
|
|
25
|
+
id: 'ING_PL_INGBPLPW',
|
|
26
|
+
name: 'ING',
|
|
27
|
+
bic: 'INGBPLPW',
|
|
28
|
+
transaction_total_days: '365',
|
|
29
|
+
max_access_valid_for_days: '90',
|
|
30
|
+
countries: ['PL'],
|
|
31
|
+
logo: 'https://cdn.nordigen.com/ais/ING_PL_INGBPLPW.png',
|
|
32
|
+
supported_payments: {},
|
|
33
|
+
supported_features: [
|
|
34
|
+
'access_scopes',
|
|
35
|
+
'business_accounts',
|
|
36
|
+
'card_accounts',
|
|
37
|
+
'corporate_accounts',
|
|
38
|
+
'pending_transactions',
|
|
39
|
+
'private_accounts',
|
|
40
|
+
],
|
|
41
|
+
},
|
|
42
|
+
};
|
|
43
|
+
|
|
44
|
+
it('returns normalized account data returned to Frontend', () => {
|
|
45
|
+
const normalizedAccount = IngPlIngbplpw.normalizeAccount(accountRaw);
|
|
46
|
+
expect(normalizedAccount).toMatchInlineSnapshot(`
|
|
47
|
+
{
|
|
48
|
+
"account_id": "d3eccc94-9536-48d3-98be-813f79199ee3",
|
|
49
|
+
"iban": "PL00000000000000000987654321",
|
|
50
|
+
"institution": {
|
|
51
|
+
"bic": "INGBPLPW",
|
|
52
|
+
"countries": [
|
|
53
|
+
"PL",
|
|
54
|
+
],
|
|
55
|
+
"id": "ING_PL_INGBPLPW",
|
|
56
|
+
"logo": "https://cdn.nordigen.com/ais/ING_PL_INGBPLPW.png",
|
|
57
|
+
"max_access_valid_for_days": "90",
|
|
58
|
+
"name": "ING",
|
|
59
|
+
"supported_features": [
|
|
60
|
+
"access_scopes",
|
|
61
|
+
"business_accounts",
|
|
62
|
+
"card_accounts",
|
|
63
|
+
"corporate_accounts",
|
|
64
|
+
"pending_transactions",
|
|
65
|
+
"private_accounts",
|
|
66
|
+
],
|
|
67
|
+
"supported_payments": {},
|
|
68
|
+
"transaction_total_days": "365",
|
|
69
|
+
},
|
|
70
|
+
"mask": "4321",
|
|
71
|
+
"name": "Current Account for Individuals (Retail) (XXX 4321) PLN",
|
|
72
|
+
"official_name": "Current Account for Individuals (Retail)",
|
|
73
|
+
"type": "checking",
|
|
74
|
+
}
|
|
75
|
+
`);
|
|
76
|
+
});
|
|
77
|
+
});
|
|
78
|
+
|
|
79
|
+
describe('#sortTransactions', () => {
|
|
80
|
+
it('sorts transactions by time and sequence from newest to oldest', () => {
|
|
81
|
+
const transactions = [
|
|
82
|
+
{
|
|
83
|
+
transactionId: 'D202301180000003',
|
|
84
|
+
transactionAmount: mockTransactionAmount,
|
|
85
|
+
},
|
|
86
|
+
{
|
|
87
|
+
transactionId: 'D202301180000004',
|
|
88
|
+
transactionAmount: mockTransactionAmount,
|
|
89
|
+
},
|
|
90
|
+
{
|
|
91
|
+
transactionId: 'D202301230000001',
|
|
92
|
+
transactionAmount: mockTransactionAmount,
|
|
93
|
+
},
|
|
94
|
+
{
|
|
95
|
+
transactionId: 'D202301180000002',
|
|
96
|
+
transactionAmount: mockTransactionAmount,
|
|
97
|
+
},
|
|
98
|
+
{
|
|
99
|
+
transactionId: 'D202301200000001',
|
|
100
|
+
transactionAmount: mockTransactionAmount,
|
|
101
|
+
},
|
|
102
|
+
];
|
|
103
|
+
const sortedTransactions = IngPlIngbplpw.sortTransactions(transactions);
|
|
104
|
+
expect(sortedTransactions).toEqual([
|
|
105
|
+
{
|
|
106
|
+
transactionId: 'D202301230000001',
|
|
107
|
+
transactionAmount: mockTransactionAmount,
|
|
108
|
+
},
|
|
109
|
+
{
|
|
110
|
+
transactionId: 'D202301200000001',
|
|
111
|
+
transactionAmount: mockTransactionAmount,
|
|
112
|
+
},
|
|
113
|
+
{
|
|
114
|
+
transactionId: 'D202301180000004',
|
|
115
|
+
transactionAmount: mockTransactionAmount,
|
|
116
|
+
},
|
|
117
|
+
{
|
|
118
|
+
transactionId: 'D202301180000003',
|
|
119
|
+
transactionAmount: mockTransactionAmount,
|
|
120
|
+
},
|
|
121
|
+
{
|
|
122
|
+
transactionId: 'D202301180000002',
|
|
123
|
+
transactionAmount: mockTransactionAmount,
|
|
124
|
+
},
|
|
125
|
+
]);
|
|
126
|
+
});
|
|
127
|
+
|
|
128
|
+
it('handles empty arrays', () => {
|
|
129
|
+
const transactions = [];
|
|
130
|
+
const sortedTransactions = IngPlIngbplpw.sortTransactions(transactions);
|
|
131
|
+
expect(sortedTransactions).toEqual([]);
|
|
132
|
+
});
|
|
133
|
+
|
|
134
|
+
it('returns empty array for undefined input', () => {
|
|
135
|
+
const sortedTransactions = IngPlIngbplpw.sortTransactions(undefined);
|
|
136
|
+
expect(sortedTransactions).toEqual([]);
|
|
137
|
+
});
|
|
138
|
+
});
|
|
139
|
+
|
|
140
|
+
describe('#countStartingBalance', () => {
|
|
141
|
+
it('should calculate the starting balance correctly', () => {
|
|
142
|
+
/** @type {import('../../gocardless-node.types.js').Transaction[]} */
|
|
143
|
+
const sortedTransactions = [
|
|
144
|
+
{
|
|
145
|
+
transactionAmount: { amount: '-100.00', currency: 'USD' },
|
|
146
|
+
balanceAfterTransaction: {
|
|
147
|
+
balanceAmount: { amount: '400.00', currency: 'USD' },
|
|
148
|
+
balanceType: 'interimBooked',
|
|
149
|
+
},
|
|
150
|
+
},
|
|
151
|
+
{
|
|
152
|
+
transactionAmount: { amount: '50.00', currency: 'USD' },
|
|
153
|
+
balanceAfterTransaction: {
|
|
154
|
+
balanceAmount: { amount: '450.00', currency: 'USD' },
|
|
155
|
+
balanceType: 'interimBooked',
|
|
156
|
+
},
|
|
157
|
+
},
|
|
158
|
+
{
|
|
159
|
+
transactionAmount: { amount: '-25.00', currency: 'USD' },
|
|
160
|
+
balanceAfterTransaction: {
|
|
161
|
+
balanceAmount: { amount: '475.00', currency: 'USD' },
|
|
162
|
+
balanceType: 'interimBooked',
|
|
163
|
+
},
|
|
164
|
+
},
|
|
165
|
+
];
|
|
166
|
+
|
|
167
|
+
/** @type {import('../../gocardless-node.types.js').Balance[]} */
|
|
168
|
+
const balances = [
|
|
169
|
+
{
|
|
170
|
+
balanceType: 'interimBooked',
|
|
171
|
+
balanceAmount: { amount: '500.00', currency: 'USD' },
|
|
172
|
+
},
|
|
173
|
+
{
|
|
174
|
+
balanceType: 'closingBooked',
|
|
175
|
+
balanceAmount: { amount: '600.00', currency: 'USD' },
|
|
176
|
+
},
|
|
177
|
+
];
|
|
178
|
+
|
|
179
|
+
const startingBalance = IngPlIngbplpw.calculateStartingBalance(
|
|
180
|
+
sortedTransactions,
|
|
181
|
+
balances,
|
|
182
|
+
);
|
|
183
|
+
|
|
184
|
+
expect(startingBalance).toEqual(50000);
|
|
185
|
+
});
|
|
186
|
+
|
|
187
|
+
it('returns the same balance amount when no transactions', () => {
|
|
188
|
+
const transactions = [];
|
|
189
|
+
|
|
190
|
+
/** @type {import('../../gocardless-node.types.js').Balance[]} */
|
|
191
|
+
const balances = [
|
|
192
|
+
{
|
|
193
|
+
balanceType: 'interimBooked',
|
|
194
|
+
balanceAmount: { amount: '500.00', currency: 'USD' },
|
|
195
|
+
},
|
|
196
|
+
];
|
|
197
|
+
expect(
|
|
198
|
+
IngPlIngbplpw.calculateStartingBalance(transactions, balances),
|
|
199
|
+
).toEqual(50000);
|
|
200
|
+
});
|
|
201
|
+
});
|
|
202
|
+
});
|
|
@@ -0,0 +1,158 @@
|
|
|
1
|
+
import { jest } from '@jest/globals';
|
|
2
|
+
|
|
3
|
+
import {
|
|
4
|
+
mockExtendAccountsAboutInstitutions,
|
|
5
|
+
mockInstitution,
|
|
6
|
+
} from '../../services/tests/fixtures.js';
|
|
7
|
+
import IntegrationBank from '../integration-bank.js';
|
|
8
|
+
|
|
9
|
+
describe('IntegrationBank', () => {
|
|
10
|
+
let consoleSpy;
|
|
11
|
+
|
|
12
|
+
beforeEach(() => {
|
|
13
|
+
consoleSpy = jest.spyOn(console, 'debug');
|
|
14
|
+
});
|
|
15
|
+
|
|
16
|
+
describe('normalizeAccount', () => {
|
|
17
|
+
const account = mockExtendAccountsAboutInstitutions[0];
|
|
18
|
+
|
|
19
|
+
it('should return a normalized account object', () => {
|
|
20
|
+
const normalizedAccount = IntegrationBank.normalizeAccount(account);
|
|
21
|
+
expect(normalizedAccount).toEqual({
|
|
22
|
+
account_id: account.id,
|
|
23
|
+
institution: mockInstitution,
|
|
24
|
+
mask: '4321',
|
|
25
|
+
iban: account.iban,
|
|
26
|
+
name: 'account-example-one (XXX 4321) PLN',
|
|
27
|
+
official_name: 'Savings Account for Individuals (Retail)',
|
|
28
|
+
type: 'checking',
|
|
29
|
+
});
|
|
30
|
+
});
|
|
31
|
+
|
|
32
|
+
it('should return a normalized account object with masked value "0000" when no iban property is provided', () => {
|
|
33
|
+
const normalizedAccount = IntegrationBank.normalizeAccount({
|
|
34
|
+
...account,
|
|
35
|
+
iban: undefined,
|
|
36
|
+
});
|
|
37
|
+
expect(normalizedAccount).toEqual({
|
|
38
|
+
account_id: account.id,
|
|
39
|
+
institution: mockInstitution,
|
|
40
|
+
mask: '0000',
|
|
41
|
+
iban: null,
|
|
42
|
+
name: 'account-example-one PLN',
|
|
43
|
+
official_name: 'Savings Account for Individuals (Retail)',
|
|
44
|
+
type: 'checking',
|
|
45
|
+
});
|
|
46
|
+
});
|
|
47
|
+
|
|
48
|
+
it('normalizeAccount logs available account properties', () => {
|
|
49
|
+
IntegrationBank.normalizeAccount(account);
|
|
50
|
+
expect(consoleSpy).toHaveBeenCalledWith(
|
|
51
|
+
'Available account properties for new institution integration',
|
|
52
|
+
{
|
|
53
|
+
account: JSON.stringify(account),
|
|
54
|
+
},
|
|
55
|
+
);
|
|
56
|
+
});
|
|
57
|
+
});
|
|
58
|
+
|
|
59
|
+
describe('sortTransactions', () => {
|
|
60
|
+
const transactions = [
|
|
61
|
+
{
|
|
62
|
+
date: '2022-01-01',
|
|
63
|
+
bookingDate: '2022-01-01',
|
|
64
|
+
transactionAmount: { amount: '100', currency: 'EUR' },
|
|
65
|
+
},
|
|
66
|
+
{
|
|
67
|
+
date: '2022-01-03',
|
|
68
|
+
bookingDate: '2022-01-03',
|
|
69
|
+
transactionAmount: { amount: '100', currency: 'EUR' },
|
|
70
|
+
},
|
|
71
|
+
{
|
|
72
|
+
date: '2022-01-02',
|
|
73
|
+
bookingDate: '2022-01-02',
|
|
74
|
+
transactionAmount: { amount: '100', currency: 'EUR' },
|
|
75
|
+
},
|
|
76
|
+
];
|
|
77
|
+
const sortedTransactions = [
|
|
78
|
+
{
|
|
79
|
+
date: '2022-01-03',
|
|
80
|
+
bookingDate: '2022-01-03',
|
|
81
|
+
transactionAmount: { amount: '100', currency: 'EUR' },
|
|
82
|
+
},
|
|
83
|
+
{
|
|
84
|
+
date: '2022-01-02',
|
|
85
|
+
bookingDate: '2022-01-02',
|
|
86
|
+
transactionAmount: { amount: '100', currency: 'EUR' },
|
|
87
|
+
},
|
|
88
|
+
{
|
|
89
|
+
date: '2022-01-01',
|
|
90
|
+
bookingDate: '2022-01-01',
|
|
91
|
+
transactionAmount: { amount: '100', currency: 'EUR' },
|
|
92
|
+
},
|
|
93
|
+
];
|
|
94
|
+
|
|
95
|
+
it('should return transactions sorted by bookingDate', () => {
|
|
96
|
+
const sortedTransactions = IntegrationBank.sortTransactions(transactions);
|
|
97
|
+
expect(sortedTransactions).toEqual(sortedTransactions);
|
|
98
|
+
});
|
|
99
|
+
|
|
100
|
+
it('sortTransactions logs available transactions properties', () => {
|
|
101
|
+
IntegrationBank.sortTransactions(transactions);
|
|
102
|
+
expect(consoleSpy).toHaveBeenCalledWith(
|
|
103
|
+
'Available (first 10) transactions properties for new integration of institution in sortTransactions function',
|
|
104
|
+
{ top10Transactions: JSON.stringify(sortedTransactions.slice(0, 10)) },
|
|
105
|
+
);
|
|
106
|
+
});
|
|
107
|
+
});
|
|
108
|
+
|
|
109
|
+
describe('calculateStartingBalance', () => {
|
|
110
|
+
/** @type {import('../../gocardless-node.types.js').Transaction[]} */
|
|
111
|
+
const transactions = [
|
|
112
|
+
{
|
|
113
|
+
bookingDate: '2022-01-01',
|
|
114
|
+
transactionAmount: { amount: '100', currency: 'EUR' },
|
|
115
|
+
},
|
|
116
|
+
{
|
|
117
|
+
bookingDate: '2022-02-01',
|
|
118
|
+
transactionAmount: { amount: '100', currency: 'EUR' },
|
|
119
|
+
},
|
|
120
|
+
{
|
|
121
|
+
bookingDate: '2022-03-01',
|
|
122
|
+
transactionAmount: { amount: '100', currency: 'EUR' },
|
|
123
|
+
},
|
|
124
|
+
];
|
|
125
|
+
|
|
126
|
+
/** @type {import('../../gocardless-node.types.js').Balance[]} */
|
|
127
|
+
const balances = [
|
|
128
|
+
{
|
|
129
|
+
balanceAmount: { amount: '1000.00', currency: 'EUR' },
|
|
130
|
+
balanceType: 'interimBooked',
|
|
131
|
+
},
|
|
132
|
+
];
|
|
133
|
+
|
|
134
|
+
it('should return 0 when no transactions or balances are provided', () => {
|
|
135
|
+
const startingBalance = IntegrationBank.calculateStartingBalance([], []);
|
|
136
|
+
expect(startingBalance).toEqual(0);
|
|
137
|
+
});
|
|
138
|
+
|
|
139
|
+
it('should return 70000 when transactions and balances are provided', () => {
|
|
140
|
+
const startingBalance = IntegrationBank.calculateStartingBalance(
|
|
141
|
+
transactions,
|
|
142
|
+
balances,
|
|
143
|
+
);
|
|
144
|
+
expect(startingBalance).toEqual(70000);
|
|
145
|
+
});
|
|
146
|
+
|
|
147
|
+
it('logs available transactions and balances properties', () => {
|
|
148
|
+
IntegrationBank.calculateStartingBalance(transactions, balances);
|
|
149
|
+
expect(consoleSpy).toHaveBeenCalledWith(
|
|
150
|
+
'Available (first 10) transactions properties for new integration of institution in calculateStartingBalance function',
|
|
151
|
+
{
|
|
152
|
+
balances: JSON.stringify(balances),
|
|
153
|
+
top10SortedTransactions: JSON.stringify(transactions.slice(0, 10)),
|
|
154
|
+
},
|
|
155
|
+
);
|
|
156
|
+
});
|
|
157
|
+
});
|
|
158
|
+
});
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
import KBCkredbebb from '../kbc_kredbebb.js';
|
|
2
|
+
|
|
3
|
+
describe('kbc_kredbebb', () => {
|
|
4
|
+
describe('#normalizeTransaction', () => {
|
|
5
|
+
it('returns the remittanceInformationUnstructured as payeeName when the amount is negative', () => {
|
|
6
|
+
const transaction = {
|
|
7
|
+
remittanceInformationUnstructured:
|
|
8
|
+
'CARREFOUR ST GIL BE1060 BRUXELLES Betaling met Google Pay via Debit Mastercard 28-08-2024 om 19.15 uur 5127 04XX XXXX 1637 5853 98XX XXXX 2266 JOHN SMITH',
|
|
9
|
+
transactionAmount: { amount: '-10.99', currency: 'EUR' },
|
|
10
|
+
date: new Date().toISOString(),
|
|
11
|
+
};
|
|
12
|
+
const normalizedTransaction = KBCkredbebb.normalizeTransaction(
|
|
13
|
+
transaction,
|
|
14
|
+
true,
|
|
15
|
+
);
|
|
16
|
+
expect(normalizedTransaction.payeeName).toEqual(
|
|
17
|
+
'CARREFOUR ST GIL BE1060 BRUXELLES',
|
|
18
|
+
);
|
|
19
|
+
});
|
|
20
|
+
|
|
21
|
+
it('returns the debtorName as payeeName when the amount is positive', () => {
|
|
22
|
+
const transaction = {
|
|
23
|
+
debtorName: 'CARREFOUR ST GIL BE1060 BRUXELLES',
|
|
24
|
+
remittanceInformationUnstructured:
|
|
25
|
+
'CARREFOUR ST GIL BE1060 BRUXELLES Betaling met Google Pay via Debit Mastercard 28-08-2024 om 19.15 uur 5127 04XX XXXX 1637 5853 98XX XXXX 2266 JOHN SMITH',
|
|
26
|
+
transactionAmount: { amount: '10.99', currency: 'EUR' },
|
|
27
|
+
date: new Date().toISOString(),
|
|
28
|
+
};
|
|
29
|
+
const normalizedTransaction = KBCkredbebb.normalizeTransaction(
|
|
30
|
+
transaction,
|
|
31
|
+
true,
|
|
32
|
+
);
|
|
33
|
+
expect(normalizedTransaction.payeeName).toEqual(
|
|
34
|
+
'CARREFOUR ST GIL BE1060 BRUXELLES',
|
|
35
|
+
);
|
|
36
|
+
});
|
|
37
|
+
});
|
|
38
|
+
});
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
import LhvLhvbee22 from '../lhv-lhvbee22.js';
|
|
2
|
+
|
|
3
|
+
describe('#normalizeTransaction', () => {
|
|
4
|
+
const bookedCardTransaction = {
|
|
5
|
+
transactionId: '2025010300000000-1',
|
|
6
|
+
bookingDate: '2025-01-03',
|
|
7
|
+
valueDate: '2025-01-03',
|
|
8
|
+
transactionAmount: {
|
|
9
|
+
amount: '-22.99',
|
|
10
|
+
currency: 'EUR',
|
|
11
|
+
},
|
|
12
|
+
creditorName: null,
|
|
13
|
+
remittanceInformationUnstructured:
|
|
14
|
+
'(..1234) 2025-01-02 09:32 CrustumOU\\Poordi 3\\Tallinn\\10156 ESTEST',
|
|
15
|
+
bankTransactionCode: 'PMNT-CCRD-POSD',
|
|
16
|
+
internalTransactionId: 'fa000f86afb2cc7678bcff0000000000',
|
|
17
|
+
};
|
|
18
|
+
|
|
19
|
+
it('extracts booked card transaction creditor name', () => {
|
|
20
|
+
expect(
|
|
21
|
+
LhvLhvbee22.normalizeTransaction(bookedCardTransaction, true).payeeName,
|
|
22
|
+
).toEqual('CrustumOU');
|
|
23
|
+
});
|
|
24
|
+
|
|
25
|
+
it('extracts booked card transaction date', () => {
|
|
26
|
+
expect(
|
|
27
|
+
LhvLhvbee22.normalizeTransaction(bookedCardTransaction, true).date,
|
|
28
|
+
).toEqual('2025-01-02');
|
|
29
|
+
});
|
|
30
|
+
|
|
31
|
+
it.each([
|
|
32
|
+
['regular text', 'Some info'],
|
|
33
|
+
['partial card text', 'PIRKUMS xxx'],
|
|
34
|
+
['null value', null],
|
|
35
|
+
['invalid date', '(..1234) 2025-13-45 09:32 Merchant\\Address'],
|
|
36
|
+
])('normalizes non-card transaction with %s', (_, remittanceInfo) => {
|
|
37
|
+
const transaction = {
|
|
38
|
+
...bookedCardTransaction,
|
|
39
|
+
remittanceInformationUnstructured: remittanceInfo,
|
|
40
|
+
};
|
|
41
|
+
const normalized = LhvLhvbee22.normalizeTransaction(transaction, true);
|
|
42
|
+
|
|
43
|
+
expect(normalized.date).toEqual('2025-01-03');
|
|
44
|
+
});
|
|
45
|
+
|
|
46
|
+
const pendingCardTransaction = {
|
|
47
|
+
transactionId: '2025010300000000-1',
|
|
48
|
+
valueDate: '2025-01-03',
|
|
49
|
+
transactionAmount: {
|
|
50
|
+
amount: '-22.99',
|
|
51
|
+
currency: 'EUR',
|
|
52
|
+
},
|
|
53
|
+
remittanceInformationUnstructured:
|
|
54
|
+
'(..1234) 2025-01-02 09:32 CrustumOU\\Poordi 3\\Tallinn\\10156 ESTEST',
|
|
55
|
+
};
|
|
56
|
+
|
|
57
|
+
it('extracts pending card transaction creditor name', () => {
|
|
58
|
+
expect(
|
|
59
|
+
LhvLhvbee22.normalizeTransaction(pendingCardTransaction, false).payeeName,
|
|
60
|
+
).toEqual('CrustumOU');
|
|
61
|
+
});
|
|
62
|
+
|
|
63
|
+
it('extracts pending card transaction date', () => {
|
|
64
|
+
expect(
|
|
65
|
+
LhvLhvbee22.normalizeTransaction(pendingCardTransaction, false).date,
|
|
66
|
+
).toEqual('2025-01-03');
|
|
67
|
+
});
|
|
68
|
+
});
|
|
@@ -0,0 +1,171 @@
|
|
|
1
|
+
import MbankRetailBrexplpw from '../mbank_retail_brexplpw.js';
|
|
2
|
+
|
|
3
|
+
describe('MbankRetailBrexplpw', () => {
|
|
4
|
+
describe('#normalizeAccount', () => {
|
|
5
|
+
/** @type {import('../../gocardless.types.js').DetailedAccountWithInstitution} */
|
|
6
|
+
const accountRaw = {
|
|
7
|
+
iban: 'PL00000000000000000987654321',
|
|
8
|
+
currency: 'PLN',
|
|
9
|
+
ownerName: 'John Example',
|
|
10
|
+
displayName: 'EKONTO',
|
|
11
|
+
product: 'RACHUNEK BIEŻĄCY',
|
|
12
|
+
usage: 'PRIV',
|
|
13
|
+
ownerAddressUnstructured: [
|
|
14
|
+
'POL',
|
|
15
|
+
'UL. EXAMPLE STREET 10 M.1',
|
|
16
|
+
'00-000 WARSZAWA',
|
|
17
|
+
],
|
|
18
|
+
id: 'd3eccc94-9536-48d3-98be-813f79199ee3',
|
|
19
|
+
created: '2023-01-18T13:24:55.879512Z',
|
|
20
|
+
last_accessed: null,
|
|
21
|
+
institution_id: 'MBANK_RETAIL_BREXPLPW',
|
|
22
|
+
status: 'READY',
|
|
23
|
+
owner_name: '',
|
|
24
|
+
institution: {
|
|
25
|
+
id: 'MBANK_RETAIL_BREXPLPW',
|
|
26
|
+
name: 'mBank Retail',
|
|
27
|
+
bic: 'BREXPLPW',
|
|
28
|
+
transaction_total_days: '90',
|
|
29
|
+
max_access_valid_for_days: '90',
|
|
30
|
+
countries: ['PL'],
|
|
31
|
+
logo: 'https://cdn.nordigen.com/ais/MBANK_RETAIL_BREXCZPP.png',
|
|
32
|
+
supported_payments: {},
|
|
33
|
+
supported_features: [
|
|
34
|
+
'access_scopes',
|
|
35
|
+
'business_accounts',
|
|
36
|
+
'card_accounts',
|
|
37
|
+
'corporate_accounts',
|
|
38
|
+
'pending_transactions',
|
|
39
|
+
'private_accounts',
|
|
40
|
+
],
|
|
41
|
+
},
|
|
42
|
+
};
|
|
43
|
+
it('returns normalized account data returned to Frontend', () => {
|
|
44
|
+
expect(MbankRetailBrexplpw.normalizeAccount(accountRaw))
|
|
45
|
+
.toMatchInlineSnapshot(`
|
|
46
|
+
{
|
|
47
|
+
"account_id": "d3eccc94-9536-48d3-98be-813f79199ee3",
|
|
48
|
+
"iban": "PL00000000000000000987654321",
|
|
49
|
+
"institution": {
|
|
50
|
+
"bic": "BREXPLPW",
|
|
51
|
+
"countries": [
|
|
52
|
+
"PL",
|
|
53
|
+
],
|
|
54
|
+
"id": "MBANK_RETAIL_BREXPLPW",
|
|
55
|
+
"logo": "https://cdn.nordigen.com/ais/MBANK_RETAIL_BREXCZPP.png",
|
|
56
|
+
"max_access_valid_for_days": "90",
|
|
57
|
+
"name": "mBank Retail",
|
|
58
|
+
"supported_features": [
|
|
59
|
+
"access_scopes",
|
|
60
|
+
"business_accounts",
|
|
61
|
+
"card_accounts",
|
|
62
|
+
"corporate_accounts",
|
|
63
|
+
"pending_transactions",
|
|
64
|
+
"private_accounts",
|
|
65
|
+
],
|
|
66
|
+
"supported_payments": {},
|
|
67
|
+
"transaction_total_days": "90",
|
|
68
|
+
},
|
|
69
|
+
"mask": "4321",
|
|
70
|
+
"name": "EKONTO (XXX 4321) PLN",
|
|
71
|
+
"official_name": "RACHUNEK BIEŻĄCY",
|
|
72
|
+
"type": "checking",
|
|
73
|
+
}
|
|
74
|
+
`);
|
|
75
|
+
});
|
|
76
|
+
});
|
|
77
|
+
|
|
78
|
+
describe('#sortTransactions', () => {
|
|
79
|
+
it('returns transactions from newest to oldest', () => {
|
|
80
|
+
const sortedTransactions = MbankRetailBrexplpw.sortTransactions([
|
|
81
|
+
{
|
|
82
|
+
transactionId: '202212300001',
|
|
83
|
+
transactionAmount: { amount: '100', currency: 'EUR' },
|
|
84
|
+
},
|
|
85
|
+
{
|
|
86
|
+
transactionId: '202212300003',
|
|
87
|
+
transactionAmount: { amount: '100', currency: 'EUR' },
|
|
88
|
+
},
|
|
89
|
+
{
|
|
90
|
+
transactionId: '202212300002',
|
|
91
|
+
transactionAmount: { amount: '100', currency: 'EUR' },
|
|
92
|
+
},
|
|
93
|
+
{
|
|
94
|
+
transactionId: '202212300000',
|
|
95
|
+
transactionAmount: { amount: '100', currency: 'EUR' },
|
|
96
|
+
},
|
|
97
|
+
{
|
|
98
|
+
transactionId: '202112300001',
|
|
99
|
+
transactionAmount: { amount: '100', currency: 'EUR' },
|
|
100
|
+
},
|
|
101
|
+
]);
|
|
102
|
+
|
|
103
|
+
expect(sortedTransactions).toEqual([
|
|
104
|
+
{
|
|
105
|
+
transactionId: '202212300003',
|
|
106
|
+
transactionAmount: { amount: '100', currency: 'EUR' },
|
|
107
|
+
},
|
|
108
|
+
{
|
|
109
|
+
transactionId: '202212300002',
|
|
110
|
+
transactionAmount: { amount: '100', currency: 'EUR' },
|
|
111
|
+
},
|
|
112
|
+
{
|
|
113
|
+
transactionId: '202212300001',
|
|
114
|
+
transactionAmount: { amount: '100', currency: 'EUR' },
|
|
115
|
+
},
|
|
116
|
+
{
|
|
117
|
+
transactionId: '202212300000',
|
|
118
|
+
transactionAmount: { amount: '100', currency: 'EUR' },
|
|
119
|
+
},
|
|
120
|
+
{
|
|
121
|
+
transactionId: '202112300001',
|
|
122
|
+
transactionAmount: { amount: '100', currency: 'EUR' },
|
|
123
|
+
},
|
|
124
|
+
]);
|
|
125
|
+
});
|
|
126
|
+
|
|
127
|
+
it('returns empty array for empty input', () => {
|
|
128
|
+
const sortedTransactions = MbankRetailBrexplpw.sortTransactions([]);
|
|
129
|
+
expect(sortedTransactions).toEqual([]);
|
|
130
|
+
});
|
|
131
|
+
|
|
132
|
+
it('returns empty array for undefined input', () => {
|
|
133
|
+
const sortedTransactions =
|
|
134
|
+
MbankRetailBrexplpw.sortTransactions(undefined);
|
|
135
|
+
expect(sortedTransactions).toEqual([]);
|
|
136
|
+
});
|
|
137
|
+
});
|
|
138
|
+
|
|
139
|
+
describe('#countStartingBalance', () => {
|
|
140
|
+
/** @type {import('../../gocardless-node.types.js').Balance[]} */
|
|
141
|
+
const balances = [
|
|
142
|
+
{
|
|
143
|
+
balanceAmount: { amount: '1000.00', currency: 'PLN' },
|
|
144
|
+
balanceType: 'interimBooked',
|
|
145
|
+
},
|
|
146
|
+
];
|
|
147
|
+
|
|
148
|
+
it('returns the same balance amount when no transactions', () => {
|
|
149
|
+
const transactions = [];
|
|
150
|
+
|
|
151
|
+
expect(
|
|
152
|
+
MbankRetailBrexplpw.calculateStartingBalance(transactions, balances),
|
|
153
|
+
).toEqual(100000);
|
|
154
|
+
});
|
|
155
|
+
|
|
156
|
+
it('returns the balance minus the available transactions', () => {
|
|
157
|
+
const transactions = [
|
|
158
|
+
{
|
|
159
|
+
transactionAmount: { amount: '200.00', currency: 'PLN' },
|
|
160
|
+
},
|
|
161
|
+
{
|
|
162
|
+
transactionAmount: { amount: '300.50', currency: 'PLN' },
|
|
163
|
+
},
|
|
164
|
+
];
|
|
165
|
+
|
|
166
|
+
expect(
|
|
167
|
+
MbankRetailBrexplpw.calculateStartingBalance(transactions, balances),
|
|
168
|
+
).toEqual(49950);
|
|
169
|
+
});
|
|
170
|
+
});
|
|
171
|
+
});
|