@accounter/server 0.0.9-alpha-20251217093036-7168648b507d62946aa287af4ea690b73b077b2d → 0.0.9-alpha-20251217131153-65f961a4072436d7f1042ea8ea4d96534cb3650e
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/CHANGELOG.md +16 -8
- package/dist/server/src/modules/charges-matcher/__tests__/auto-match-integration.test.js +45 -122
- package/dist/server/src/modules/charges-matcher/__tests__/auto-match-integration.test.js.map +1 -1
- package/dist/server/src/modules/charges-matcher/__tests__/auto-match.test.js +45 -29
- package/dist/server/src/modules/charges-matcher/__tests__/auto-match.test.js.map +1 -1
- package/dist/server/src/modules/charges-matcher/__tests__/charge-validator.test.js +2 -11
- package/dist/server/src/modules/charges-matcher/__tests__/charge-validator.test.js.map +1 -1
- package/dist/server/src/modules/charges-matcher/__tests__/date-confidence.test.js +25 -0
- package/dist/server/src/modules/charges-matcher/__tests__/date-confidence.test.js.map +1 -1
- package/dist/server/src/modules/charges-matcher/__tests__/document-aggregator.test.js.map +1 -1
- package/dist/server/src/modules/charges-matcher/__tests__/document-amount.test.js +65 -64
- package/dist/server/src/modules/charges-matcher/__tests__/document-amount.test.js.map +1 -1
- package/dist/server/src/modules/charges-matcher/__tests__/match-scorer.test.js +494 -60
- package/dist/server/src/modules/charges-matcher/__tests__/match-scorer.test.js.map +1 -1
- package/dist/server/src/modules/charges-matcher/__tests__/single-match-integration.test.js +34 -98
- package/dist/server/src/modules/charges-matcher/__tests__/single-match-integration.test.js.map +1 -1
- package/dist/server/src/modules/charges-matcher/__tests__/single-match.test.js +79 -59
- package/dist/server/src/modules/charges-matcher/__tests__/single-match.test.js.map +1 -1
- package/dist/server/src/modules/charges-matcher/helpers/charge-validator.helper.js +6 -4
- package/dist/server/src/modules/charges-matcher/helpers/charge-validator.helper.js.map +1 -1
- package/dist/server/src/modules/charges-matcher/helpers/date-confidence.helper.d.ts +9 -2
- package/dist/server/src/modules/charges-matcher/helpers/date-confidence.helper.js +24 -2
- package/dist/server/src/modules/charges-matcher/helpers/date-confidence.helper.js.map +1 -1
- package/dist/server/src/modules/charges-matcher/helpers/document-amount.helper.d.ts +1 -4
- package/dist/server/src/modules/charges-matcher/helpers/document-amount.helper.js +2 -1
- package/dist/server/src/modules/charges-matcher/helpers/document-amount.helper.js.map +1 -1
- package/dist/server/src/modules/charges-matcher/index.d.ts +0 -1
- package/dist/server/src/modules/charges-matcher/index.js +0 -1
- package/dist/server/src/modules/charges-matcher/index.js.map +1 -1
- package/dist/server/src/modules/charges-matcher/providers/auto-match.provider.d.ts +2 -1
- package/dist/server/src/modules/charges-matcher/providers/auto-match.provider.js +2 -2
- package/dist/server/src/modules/charges-matcher/providers/auto-match.provider.js.map +1 -1
- package/dist/server/src/modules/charges-matcher/providers/charges-matcher.provider.js +2 -2
- package/dist/server/src/modules/charges-matcher/providers/charges-matcher.provider.js.map +1 -1
- package/dist/server/src/modules/charges-matcher/providers/document-aggregator.d.ts +4 -5
- package/dist/server/src/modules/charges-matcher/providers/document-aggregator.js +5 -4
- package/dist/server/src/modules/charges-matcher/providers/document-aggregator.js.map +1 -1
- package/dist/server/src/modules/charges-matcher/providers/match-scorer.provider.d.ts +5 -3
- package/dist/server/src/modules/charges-matcher/providers/match-scorer.provider.js +70 -13
- package/dist/server/src/modules/charges-matcher/providers/match-scorer.provider.js.map +1 -1
- package/dist/server/src/modules/charges-matcher/providers/single-match.provider.d.ts +4 -2
- package/dist/server/src/modules/charges-matcher/providers/single-match.provider.js +15 -7
- package/dist/server/src/modules/charges-matcher/providers/single-match.provider.js.map +1 -1
- package/dist/server/src/modules/charges-matcher/providers/transaction-aggregator.d.ts +1 -1
- package/dist/server/src/modules/charges-matcher/types.d.ts +2 -4
- package/dist/server/src/modules/charges-matcher/types.js.map +1 -1
- package/package.json +2 -2
- package/src/modules/charges-matcher/README.md +14 -3
- package/src/modules/charges-matcher/__tests__/auto-match-integration.test.ts +52 -100
- package/src/modules/charges-matcher/__tests__/auto-match.test.ts +51 -29
- package/src/modules/charges-matcher/__tests__/charge-validator.test.ts +2 -13
- package/src/modules/charges-matcher/__tests__/date-confidence.test.ts +29 -0
- package/src/modules/charges-matcher/__tests__/document-aggregator.test.ts +0 -1
- package/src/modules/charges-matcher/__tests__/document-amount.test.ts +66 -65
- package/src/modules/charges-matcher/__tests__/match-scorer.test.ts +552 -60
- package/src/modules/charges-matcher/__tests__/single-match-integration.test.ts +43 -73
- package/src/modules/charges-matcher/__tests__/single-match.test.ts +81 -59
- package/src/modules/charges-matcher/documentation/SPEC.md +276 -4
- package/src/modules/charges-matcher/helpers/charge-validator.helper.ts +7 -5
- package/src/modules/charges-matcher/helpers/date-confidence.helper.ts +32 -2
- package/src/modules/charges-matcher/helpers/document-amount.helper.ts +2 -12
- package/src/modules/charges-matcher/index.ts +0 -1
- package/src/modules/charges-matcher/providers/auto-match.provider.ts +5 -3
- package/src/modules/charges-matcher/providers/charges-matcher.provider.ts +8 -2
- package/src/modules/charges-matcher/providers/document-aggregator.ts +12 -11
- package/src/modules/charges-matcher/providers/match-scorer.provider.ts +97 -17
- package/src/modules/charges-matcher/providers/single-match.provider.ts +21 -8
- package/src/modules/charges-matcher/providers/transaction-aggregator.ts +1 -1
- package/src/modules/charges-matcher/types.ts +2 -5
|
@@ -1,15 +1,37 @@
|
|
|
1
|
-
import { describe, expect, it } from 'vitest';
|
|
1
|
+
import { describe, expect, it, vi } from 'vitest';
|
|
2
|
+
import type { Injector } from 'graphql-modules';
|
|
2
3
|
import type { Document, DocumentCharge, Transaction, TransactionCharge } from '../types.js';
|
|
3
4
|
import {
|
|
4
5
|
findMatches,
|
|
5
6
|
} from '../providers/single-match.provider.js';
|
|
6
7
|
import { createMockTransaction, createMockDocument } from './test-helpers.js';
|
|
7
8
|
|
|
9
|
+
// Mock DI system and ClientsProvider
|
|
10
|
+
vi.mock('../../financial-entities/providers/clients.provider.js', () => ({
|
|
11
|
+
ClientsProvider: class {},
|
|
12
|
+
}));
|
|
13
|
+
|
|
8
14
|
// Test user and business IDs
|
|
9
15
|
const USER_ID = 'user-123';
|
|
10
16
|
const BUSINESS_A = 'business-a';
|
|
11
17
|
const BUSINESS_B = 'business-b';
|
|
12
18
|
|
|
19
|
+
// Create a mock injector for testing
|
|
20
|
+
const createMockInjector = () => ({
|
|
21
|
+
get: vi.fn((token: {name: string}) => {
|
|
22
|
+
if (token.name === 'ClientsProvider')
|
|
23
|
+
return {
|
|
24
|
+
getClientByIdLoader: {
|
|
25
|
+
load: (businessId: string) => {
|
|
26
|
+
const isRegisteredClient = businessId.startsWith('client-');
|
|
27
|
+
return Promise.resolve(isRegisteredClient ? { id: businessId } : null);
|
|
28
|
+
},
|
|
29
|
+
},
|
|
30
|
+
};
|
|
31
|
+
return null;
|
|
32
|
+
}),
|
|
33
|
+
}) as Injector;
|
|
34
|
+
|
|
13
35
|
// Helper to create transaction charge
|
|
14
36
|
function createTxCharge(
|
|
15
37
|
chargeId: string,
|
|
@@ -35,7 +57,7 @@ function createDocCharge(
|
|
|
35
57
|
describe('Single-Match Provider', () => {
|
|
36
58
|
describe('findMatches', () => {
|
|
37
59
|
describe('Valid Transaction Charge → Finds Document Matches', () => {
|
|
38
|
-
it('should find matching document charges for transaction charge', () => {
|
|
60
|
+
it('should find matching document charges for transaction charge', async () => {
|
|
39
61
|
const sourceCharge = createTxCharge('tx-charge-1', [
|
|
40
62
|
createMockTransaction({
|
|
41
63
|
amount: "100",
|
|
@@ -61,7 +83,7 @@ describe('Single-Match Provider', () => {
|
|
|
61
83
|
]),
|
|
62
84
|
];
|
|
63
85
|
|
|
64
|
-
const results = findMatches(sourceCharge, candidateCharges, USER_ID);
|
|
86
|
+
const results = await findMatches(sourceCharge, candidateCharges, USER_ID, createMockInjector());
|
|
65
87
|
|
|
66
88
|
expect(results).toHaveLength(2);
|
|
67
89
|
expect(results[0].chargeId).toBe('doc-charge-1'); // Perfect match
|
|
@@ -70,7 +92,7 @@ describe('Single-Match Provider', () => {
|
|
|
70
92
|
expect(results[1].confidenceScore).toBeLessThan(0.95);
|
|
71
93
|
});
|
|
72
94
|
|
|
73
|
-
it('should exclude transaction candidates when source is transaction', () => {
|
|
95
|
+
it('should exclude transaction candidates when source is transaction', async () => {
|
|
74
96
|
const sourceCharge = createTxCharge('tx-charge-1', [createMockTransaction()]);
|
|
75
97
|
|
|
76
98
|
const candidateCharges = [
|
|
@@ -78,7 +100,7 @@ describe('Single-Match Provider', () => {
|
|
|
78
100
|
createDocCharge('doc-charge-1', [createMockDocument()]), // Should be included
|
|
79
101
|
];
|
|
80
102
|
|
|
81
|
-
const results = findMatches(sourceCharge, candidateCharges, USER_ID);
|
|
103
|
+
const results = await findMatches(sourceCharge, candidateCharges, USER_ID, createMockInjector());
|
|
82
104
|
|
|
83
105
|
expect(results).toHaveLength(1);
|
|
84
106
|
expect(results[0].chargeId).toBe('doc-charge-1');
|
|
@@ -86,7 +108,7 @@ describe('Single-Match Provider', () => {
|
|
|
86
108
|
});
|
|
87
109
|
|
|
88
110
|
describe('Valid Document Charge → Finds Transaction Matches', () => {
|
|
89
|
-
it('should find matching transaction charges for document charge', () => {
|
|
111
|
+
it('should find matching transaction charges for document charge', async () => {
|
|
90
112
|
const sourceCharge = createDocCharge('doc-charge-1', [
|
|
91
113
|
createMockDocument({
|
|
92
114
|
total_amount: 100,
|
|
@@ -112,14 +134,14 @@ describe('Single-Match Provider', () => {
|
|
|
112
134
|
]),
|
|
113
135
|
];
|
|
114
136
|
|
|
115
|
-
const results = findMatches(sourceCharge, candidateCharges, USER_ID);
|
|
137
|
+
const results = await findMatches(sourceCharge, candidateCharges, USER_ID, createMockInjector());
|
|
116
138
|
|
|
117
139
|
expect(results).toHaveLength(2);
|
|
118
140
|
expect(results[0].chargeId).toBe('tx-charge-1'); // Perfect match
|
|
119
141
|
expect(results[1].chargeId).toBe('tx-charge-2'); // Partial match
|
|
120
142
|
});
|
|
121
143
|
|
|
122
|
-
it('should exclude document candidates when source is document', () => {
|
|
144
|
+
it('should exclude document candidates when source is document', async () => {
|
|
123
145
|
const sourceCharge = createDocCharge('doc-charge-1', [createMockDocument()]);
|
|
124
146
|
|
|
125
147
|
const candidateCharges = [
|
|
@@ -127,7 +149,7 @@ describe('Single-Match Provider', () => {
|
|
|
127
149
|
createTxCharge('tx-charge-1', [createMockTransaction()]), // Should be included
|
|
128
150
|
];
|
|
129
151
|
|
|
130
|
-
const results = findMatches(sourceCharge, candidateCharges, USER_ID);
|
|
152
|
+
const results = await findMatches(sourceCharge, candidateCharges, USER_ID, createMockInjector());
|
|
131
153
|
|
|
132
154
|
expect(results).toHaveLength(1);
|
|
133
155
|
expect(results[0].chargeId).toBe('tx-charge-1');
|
|
@@ -135,55 +157,55 @@ describe('Single-Match Provider', () => {
|
|
|
135
157
|
});
|
|
136
158
|
|
|
137
159
|
describe('Matched Charge Input → Throws Error', () => {
|
|
138
|
-
it('should throw error if source has both transactions and documents', () => {
|
|
160
|
+
it('should throw error if source has both transactions and documents', async () => {
|
|
139
161
|
const matchedCharge = {
|
|
140
162
|
chargeId: 'matched-charge',
|
|
141
163
|
transactions: [createMockTransaction()],
|
|
142
164
|
documents: [createMockDocument()],
|
|
143
165
|
} as any;
|
|
144
166
|
|
|
145
|
-
expect(
|
|
167
|
+
await expect(findMatches(matchedCharge, [], USER_ID, createMockInjector())).rejects.toThrow(
|
|
146
168
|
/already matched.*both transactions and documents/,
|
|
147
169
|
);
|
|
148
170
|
});
|
|
149
171
|
|
|
150
|
-
it('should throw error if source has neither transactions nor documents', () => {
|
|
172
|
+
it('should throw error if source has neither transactions nor documents', async () => {
|
|
151
173
|
const emptyCharge = {
|
|
152
174
|
chargeId: 'empty-charge',
|
|
153
175
|
transactions: [],
|
|
154
176
|
documents: [],
|
|
155
177
|
} as any;
|
|
156
178
|
|
|
157
|
-
expect(
|
|
179
|
+
await expect(findMatches(emptyCharge, [], USER_ID, createMockInjector())).rejects.toThrow(
|
|
158
180
|
/no transactions or documents/,
|
|
159
181
|
);
|
|
160
182
|
});
|
|
161
183
|
});
|
|
162
184
|
|
|
163
185
|
describe('Multiple Currencies in Source → Error Propagates', () => {
|
|
164
|
-
it('should throw error for source with mixed currencies', () => {
|
|
186
|
+
it('should throw error for source with mixed currencies', async () => {
|
|
165
187
|
const mixedCurrencyCharge = createTxCharge('tx-charge-1', [
|
|
166
188
|
createMockTransaction({ currency: 'USD' }),
|
|
167
189
|
createMockTransaction({ currency: 'EUR' }),
|
|
168
190
|
]);
|
|
169
191
|
|
|
170
|
-
expect(
|
|
192
|
+
await expect(findMatches(mixedCurrencyCharge, [], USER_ID, createMockInjector())).rejects.toThrow(
|
|
171
193
|
/failed validation.*multiple currencies/,
|
|
172
194
|
);
|
|
173
195
|
});
|
|
174
196
|
|
|
175
|
-
it('should throw error for source with multiple business IDs', () => {
|
|
197
|
+
it('should throw error for source with multiple business IDs', async () => {
|
|
176
198
|
const mixedBusinessCharge = createTxCharge('tx-charge-1', [
|
|
177
199
|
createMockTransaction({ business_id: BUSINESS_A }),
|
|
178
200
|
createMockTransaction({ business_id: BUSINESS_B }),
|
|
179
201
|
]);
|
|
180
202
|
|
|
181
|
-
expect(
|
|
203
|
+
await expect(findMatches(mixedBusinessCharge, [], USER_ID, createMockInjector())).rejects.toThrow(
|
|
182
204
|
/failed validation.*multiple business/,
|
|
183
205
|
);
|
|
184
206
|
});
|
|
185
207
|
|
|
186
|
-
it('should throw error for source document with invalid business extraction', () => {
|
|
208
|
+
it('should throw error for source document with invalid business extraction', async () => {
|
|
187
209
|
const invalidDocCharge = createDocCharge('doc-charge-1', [
|
|
188
210
|
createMockDocument({
|
|
189
211
|
creditor_id: 'other-user',
|
|
@@ -191,20 +213,20 @@ describe('Single-Match Provider', () => {
|
|
|
191
213
|
}),
|
|
192
214
|
]);
|
|
193
215
|
|
|
194
|
-
expect(
|
|
216
|
+
await expect(findMatches(invalidDocCharge, [], USER_ID, createMockInjector())).rejects.toThrow(/failed validation/);
|
|
195
217
|
});
|
|
196
218
|
});
|
|
197
219
|
|
|
198
220
|
describe('No Candidates Found → Empty Array', () => {
|
|
199
|
-
it('should return empty array when no candidates provided', () => {
|
|
221
|
+
it('should return empty array when no candidates provided', async () => {
|
|
200
222
|
const sourceCharge = createTxCharge('tx-charge-1', [createMockTransaction()]);
|
|
201
223
|
|
|
202
|
-
const results = findMatches(sourceCharge, [], USER_ID);
|
|
224
|
+
const results = await findMatches(sourceCharge, [], USER_ID, createMockInjector());
|
|
203
225
|
|
|
204
226
|
expect(results).toEqual([]);
|
|
205
227
|
});
|
|
206
228
|
|
|
207
|
-
it('should return empty array when all candidates are same type', () => {
|
|
229
|
+
it('should return empty array when all candidates are same type', async () => {
|
|
208
230
|
const sourceCharge = createTxCharge('tx-charge-1', [createMockTransaction()]);
|
|
209
231
|
|
|
210
232
|
const candidateCharges = [
|
|
@@ -212,12 +234,12 @@ describe('Single-Match Provider', () => {
|
|
|
212
234
|
createTxCharge('tx-charge-3', [createMockTransaction()]),
|
|
213
235
|
];
|
|
214
236
|
|
|
215
|
-
const results = findMatches(sourceCharge, candidateCharges, USER_ID);
|
|
237
|
+
const results = await findMatches(sourceCharge, candidateCharges, USER_ID, createMockInjector());
|
|
216
238
|
|
|
217
239
|
expect(results).toEqual([]);
|
|
218
240
|
});
|
|
219
241
|
|
|
220
|
-
it('should return empty array when all candidates outside date window', () => {
|
|
242
|
+
it('should return empty array when all candidates outside date window', async () => {
|
|
221
243
|
const sourceCharge = createTxCharge('tx-charge-1', [
|
|
222
244
|
createMockTransaction({ event_date: new Date('2024-01-15') }),
|
|
223
245
|
]);
|
|
@@ -231,12 +253,12 @@ describe('Single-Match Provider', () => {
|
|
|
231
253
|
]),
|
|
232
254
|
];
|
|
233
255
|
|
|
234
|
-
const results = findMatches(sourceCharge, candidateCharges, USER_ID);
|
|
256
|
+
const results = await findMatches(sourceCharge, candidateCharges, USER_ID, createMockInjector());
|
|
235
257
|
|
|
236
258
|
expect(results).toEqual([]);
|
|
237
259
|
});
|
|
238
260
|
|
|
239
|
-
it('should return empty array when all candidates fail scoring', () => {
|
|
261
|
+
it('should return empty array when all candidates fail scoring', async () => {
|
|
240
262
|
const sourceCharge = createTxCharge('tx-charge-1', [
|
|
241
263
|
createMockTransaction({ currency: 'USD' }),
|
|
242
264
|
]);
|
|
@@ -249,14 +271,14 @@ describe('Single-Match Provider', () => {
|
|
|
249
271
|
]),
|
|
250
272
|
];
|
|
251
273
|
|
|
252
|
-
const results = findMatches(sourceCharge, candidateCharges, USER_ID);
|
|
274
|
+
const results = await findMatches(sourceCharge, candidateCharges, USER_ID, createMockInjector());
|
|
253
275
|
|
|
254
276
|
expect(results).toEqual([]);
|
|
255
277
|
});
|
|
256
278
|
});
|
|
257
279
|
|
|
258
280
|
describe('Candidate Count Handling', () => {
|
|
259
|
-
it('should return all candidates when fewer than 5', () => {
|
|
281
|
+
it('should return all candidates when fewer than 5', async () => {
|
|
260
282
|
const sourceCharge = createTxCharge('tx-charge-1', [createMockTransaction()]);
|
|
261
283
|
|
|
262
284
|
const candidateCharges = [
|
|
@@ -265,12 +287,12 @@ describe('Single-Match Provider', () => {
|
|
|
265
287
|
createDocCharge('doc-charge-3', [createMockDocument()]),
|
|
266
288
|
];
|
|
267
289
|
|
|
268
|
-
const results = findMatches(sourceCharge, candidateCharges, USER_ID);
|
|
290
|
+
const results = await findMatches(sourceCharge, candidateCharges, USER_ID, createMockInjector());
|
|
269
291
|
|
|
270
292
|
expect(results).toHaveLength(3);
|
|
271
293
|
});
|
|
272
294
|
|
|
273
|
-
it('should return top 5 when more than 5 candidates', () => {
|
|
295
|
+
it('should return top 5 when more than 5 candidates', async () => {
|
|
274
296
|
const sourceCharge = createTxCharge('tx-charge-1', [
|
|
275
297
|
createMockTransaction({ amount: "100" }),
|
|
276
298
|
]);
|
|
@@ -285,13 +307,13 @@ describe('Single-Match Provider', () => {
|
|
|
285
307
|
createDocCharge('doc-charge-7', [createMockDocument({ total_amount: 106 })]),
|
|
286
308
|
];
|
|
287
309
|
|
|
288
|
-
const results = findMatches(sourceCharge, candidateCharges, USER_ID);
|
|
310
|
+
const results = await findMatches(sourceCharge, candidateCharges, USER_ID, createMockInjector());
|
|
289
311
|
|
|
290
312
|
expect(results).toHaveLength(5);
|
|
291
313
|
expect(results[0].chargeId).toBe('doc-charge-1'); // Best match
|
|
292
314
|
});
|
|
293
315
|
|
|
294
|
-
it('should respect custom maxMatches option', () => {
|
|
316
|
+
it('should respect custom maxMatches option', async () => {
|
|
295
317
|
const sourceCharge = createTxCharge('tx-charge-1', [createMockTransaction()]);
|
|
296
318
|
|
|
297
319
|
const candidateCharges = [
|
|
@@ -302,14 +324,14 @@ describe('Single-Match Provider', () => {
|
|
|
302
324
|
createDocCharge('doc-charge-5', [createMockDocument()]),
|
|
303
325
|
];
|
|
304
326
|
|
|
305
|
-
const results = findMatches(sourceCharge, candidateCharges, USER_ID, { maxMatches: 3 });
|
|
327
|
+
const results = await findMatches(sourceCharge, candidateCharges, USER_ID, createMockInjector(), { maxMatches: 3 });
|
|
306
328
|
|
|
307
329
|
expect(results).toHaveLength(3);
|
|
308
330
|
});
|
|
309
331
|
});
|
|
310
332
|
|
|
311
333
|
describe('Tie-Breaking by Date Proximity', () => {
|
|
312
|
-
it('should use date proximity as tie-breaker when confidence scores equal', () => {
|
|
334
|
+
it('should use date proximity as tie-breaker when confidence scores equal', async () => {
|
|
313
335
|
const sourceCharge = createTxCharge('tx-charge-1', [
|
|
314
336
|
createMockTransaction({
|
|
315
337
|
amount: "100",
|
|
@@ -339,14 +361,14 @@ describe('Single-Match Provider', () => {
|
|
|
339
361
|
]),
|
|
340
362
|
];
|
|
341
363
|
|
|
342
|
-
const results = findMatches(sourceCharge, candidateCharges, USER_ID);
|
|
364
|
+
const results = await findMatches(sourceCharge, candidateCharges, USER_ID, createMockInjector());
|
|
343
365
|
|
|
344
366
|
expect(results[0].chargeId).toBe('doc-charge-close'); // Closest date wins
|
|
345
367
|
expect(results[1].chargeId).toBe('doc-charge-medium');
|
|
346
368
|
expect(results[2].chargeId).toBe('doc-charge-far');
|
|
347
369
|
});
|
|
348
370
|
|
|
349
|
-
it('should include dateProximity in results', () => {
|
|
371
|
+
it('should include dateProximity in results', async () => {
|
|
350
372
|
const sourceCharge = createTxCharge('tx-charge-1', [
|
|
351
373
|
createMockTransaction({ event_date: new Date('2024-01-15') }),
|
|
352
374
|
]);
|
|
@@ -357,14 +379,14 @@ describe('Single-Match Provider', () => {
|
|
|
357
379
|
]),
|
|
358
380
|
];
|
|
359
381
|
|
|
360
|
-
const results = findMatches(sourceCharge, candidateCharges, USER_ID);
|
|
382
|
+
const results = await findMatches(sourceCharge, candidateCharges, USER_ID, createMockInjector());
|
|
361
383
|
|
|
362
384
|
expect(results[0].dateProximity).toBe(1);
|
|
363
385
|
});
|
|
364
386
|
});
|
|
365
387
|
|
|
366
388
|
describe('Date Window Filtering', () => {
|
|
367
|
-
it('should filter candidates outside 12-month window by default', () => {
|
|
389
|
+
it('should filter candidates outside 12-month window by default', async () => {
|
|
368
390
|
const sourceCharge = createTxCharge('tx-charge-1', [
|
|
369
391
|
createMockTransaction({ event_date: new Date('2024-01-15') }),
|
|
370
392
|
]);
|
|
@@ -378,13 +400,13 @@ describe('Single-Match Provider', () => {
|
|
|
378
400
|
]),
|
|
379
401
|
];
|
|
380
402
|
|
|
381
|
-
const results = findMatches(sourceCharge, candidateCharges, USER_ID);
|
|
403
|
+
const results = await findMatches(sourceCharge, candidateCharges, USER_ID, createMockInjector());
|
|
382
404
|
|
|
383
405
|
expect(results).toHaveLength(1);
|
|
384
406
|
expect(results[0].chargeId).toBe('doc-inside');
|
|
385
407
|
});
|
|
386
408
|
|
|
387
|
-
it('should respect custom dateWindowMonths option', () => {
|
|
409
|
+
it('should respect custom dateWindowMonths option', async () => {
|
|
388
410
|
const sourceCharge = createTxCharge('tx-charge-1', [
|
|
389
411
|
createMockTransaction({ event_date: new Date('2024-01-15') }),
|
|
390
412
|
]);
|
|
@@ -398,7 +420,7 @@ describe('Single-Match Provider', () => {
|
|
|
398
420
|
]),
|
|
399
421
|
];
|
|
400
422
|
|
|
401
|
-
const results = findMatches(sourceCharge, candidateCharges, USER_ID, {
|
|
423
|
+
const results = await findMatches(sourceCharge, candidateCharges, USER_ID, createMockInjector(), {
|
|
402
424
|
dateWindowMonths: 3,
|
|
403
425
|
});
|
|
404
426
|
|
|
@@ -406,7 +428,7 @@ describe('Single-Match Provider', () => {
|
|
|
406
428
|
expect(results[0].chargeId).toBe('doc-inside');
|
|
407
429
|
});
|
|
408
430
|
|
|
409
|
-
it('should include candidates on exact window boundary', () => {
|
|
431
|
+
it('should include candidates on exact window boundary', async () => {
|
|
410
432
|
const sourceCharge = createTxCharge('tx-charge-1', [
|
|
411
433
|
createMockTransaction({ event_date: new Date('2024-01-15') }),
|
|
412
434
|
]);
|
|
@@ -417,14 +439,14 @@ describe('Single-Match Provider', () => {
|
|
|
417
439
|
]),
|
|
418
440
|
];
|
|
419
441
|
|
|
420
|
-
const results = findMatches(sourceCharge, candidateCharges, USER_ID);
|
|
442
|
+
const results = await findMatches(sourceCharge, candidateCharges, USER_ID, createMockInjector());
|
|
421
443
|
|
|
422
444
|
expect(results).toHaveLength(1);
|
|
423
445
|
});
|
|
424
446
|
});
|
|
425
447
|
|
|
426
448
|
describe('Fee Transactions Excluded', () => {
|
|
427
|
-
it('should handle source with fee transactions via aggregator', () => {
|
|
449
|
+
it('should handle source with fee transactions via aggregator', async () => {
|
|
428
450
|
const sourceCharge = createTxCharge('tx-charge-1', [
|
|
429
451
|
createMockTransaction({ amount: "100", is_fee: false }),
|
|
430
452
|
createMockTransaction({ amount: "5", is_fee: true }), // Should be excluded by aggregator
|
|
@@ -434,40 +456,40 @@ describe('Single-Match Provider', () => {
|
|
|
434
456
|
createDocCharge('doc-charge-1', [createMockDocument({ total_amount: 100 })]),
|
|
435
457
|
];
|
|
436
458
|
|
|
437
|
-
const results = findMatches(sourceCharge, candidateCharges, USER_ID);
|
|
459
|
+
const results = await findMatches(sourceCharge, candidateCharges, USER_ID, createMockInjector());
|
|
438
460
|
|
|
439
461
|
expect(results).toHaveLength(1);
|
|
440
462
|
expect(results[0].confidenceScore).toBeGreaterThan(0.95); // Matches 100, not 105
|
|
441
463
|
});
|
|
442
464
|
|
|
443
|
-
it('should throw error if all source transactions are fees', () => {
|
|
465
|
+
it('should throw error if all source transactions are fees', async () => {
|
|
444
466
|
const allFeesCharge = createTxCharge('tx-charge-1', [
|
|
445
467
|
createMockTransaction({ is_fee: true }),
|
|
446
468
|
createMockTransaction({ is_fee: true }),
|
|
447
469
|
]);
|
|
448
470
|
|
|
449
|
-
expect(
|
|
471
|
+
await expect(findMatches(allFeesCharge, [], USER_ID, createMockInjector())).rejects.toThrow(
|
|
450
472
|
/all transactions are marked as fees/,
|
|
451
473
|
);
|
|
452
474
|
});
|
|
453
475
|
});
|
|
454
476
|
|
|
455
477
|
describe('Same ChargeId → Throws Error', () => {
|
|
456
|
-
it('should throw error when candidate has same chargeId as source', () => {
|
|
478
|
+
it('should throw error when candidate has same chargeId as source', async () => {
|
|
457
479
|
const sourceCharge = createTxCharge('same-charge-id', [createMockTransaction()]);
|
|
458
480
|
|
|
459
481
|
const candidateCharges = [
|
|
460
482
|
createDocCharge('same-charge-id', [createMockDocument()]), // Same ID!
|
|
461
483
|
];
|
|
462
484
|
|
|
463
|
-
expect(
|
|
485
|
+
await expect(findMatches(sourceCharge, candidateCharges, USER_ID, createMockInjector())).rejects.toThrow(
|
|
464
486
|
/same ID as source charge/,
|
|
465
487
|
);
|
|
466
488
|
});
|
|
467
489
|
});
|
|
468
490
|
|
|
469
491
|
describe('Various Confidence Levels', () => {
|
|
470
|
-
it('should rank matches by confidence level correctly', () => {
|
|
492
|
+
it('should rank matches by confidence level correctly', async () => {
|
|
471
493
|
const sourceCharge = createTxCharge('tx-charge-1', [
|
|
472
494
|
createMockTransaction({
|
|
473
495
|
amount: "100",
|
|
@@ -520,7 +542,7 @@ describe('Single-Match Provider', () => {
|
|
|
520
542
|
]),
|
|
521
543
|
];
|
|
522
544
|
|
|
523
|
-
const results = findMatches(sourceCharge, candidateCharges, USER_ID);
|
|
545
|
+
const results = await findMatches(sourceCharge, candidateCharges, USER_ID, createMockInjector());
|
|
524
546
|
|
|
525
547
|
expect(results).toHaveLength(4);
|
|
526
548
|
expect(results[0].chargeId).toBe('perfect');
|
|
@@ -530,14 +552,14 @@ describe('Single-Match Provider', () => {
|
|
|
530
552
|
expect(results[3].chargeId).toBe('poor');
|
|
531
553
|
});
|
|
532
554
|
|
|
533
|
-
it('should include component scores in results', () => {
|
|
555
|
+
it('should include component scores in results', async () => {
|
|
534
556
|
const sourceCharge = createTxCharge('tx-charge-1', [createMockTransaction()]);
|
|
535
557
|
|
|
536
558
|
const candidateCharges = [
|
|
537
559
|
createDocCharge('doc-charge-1', [createMockDocument()]),
|
|
538
560
|
];
|
|
539
561
|
|
|
540
|
-
const results = findMatches(sourceCharge, candidateCharges, USER_ID);
|
|
562
|
+
const results = await findMatches(sourceCharge, candidateCharges, USER_ID, createMockInjector());
|
|
541
563
|
|
|
542
564
|
expect(results[0].components).toBeDefined();
|
|
543
565
|
expect(results[0].components.amount).toBeGreaterThanOrEqual(0);
|
|
@@ -548,7 +570,7 @@ describe('Single-Match Provider', () => {
|
|
|
548
570
|
});
|
|
549
571
|
|
|
550
572
|
describe('Edge Cases', () => {
|
|
551
|
-
it('should handle multiple transactions in source charge', () => {
|
|
573
|
+
it('should handle multiple transactions in source charge', async () => {
|
|
552
574
|
const sourceCharge = createTxCharge('tx-charge-1', [
|
|
553
575
|
createMockTransaction({ amount: "50" }),
|
|
554
576
|
createMockTransaction({ amount: "50" }),
|
|
@@ -558,13 +580,13 @@ describe('Single-Match Provider', () => {
|
|
|
558
580
|
createDocCharge('doc-charge-1', [createMockDocument({ total_amount: 100 })]),
|
|
559
581
|
];
|
|
560
582
|
|
|
561
|
-
const results = findMatches(sourceCharge, candidateCharges, USER_ID);
|
|
583
|
+
const results = await findMatches(sourceCharge, candidateCharges, USER_ID, createMockInjector());
|
|
562
584
|
|
|
563
585
|
expect(results).toHaveLength(1);
|
|
564
586
|
expect(results[0].confidenceScore).toBeGreaterThan(0.95); // 50+50 = 100
|
|
565
587
|
});
|
|
566
588
|
|
|
567
|
-
it('should handle multiple documents in candidate charge', () => {
|
|
589
|
+
it('should handle multiple documents in candidate charge', async () => {
|
|
568
590
|
const sourceCharge = createTxCharge('tx-charge-1', [
|
|
569
591
|
createMockTransaction({ amount: "100" }),
|
|
570
592
|
]);
|
|
@@ -576,13 +598,13 @@ describe('Single-Match Provider', () => {
|
|
|
576
598
|
]),
|
|
577
599
|
];
|
|
578
600
|
|
|
579
|
-
const results = findMatches(sourceCharge, candidateCharges, USER_ID);
|
|
601
|
+
const results = await findMatches(sourceCharge, candidateCharges, USER_ID, createMockInjector());
|
|
580
602
|
|
|
581
603
|
expect(results).toHaveLength(1);
|
|
582
604
|
expect(results[0].confidenceScore).toBeGreaterThan(0.95); // 60+40 = 100
|
|
583
605
|
});
|
|
584
606
|
|
|
585
|
-
it('should handle negative amounts (credit transactions)', () => {
|
|
607
|
+
it('should handle negative amounts (credit transactions)', async () => {
|
|
586
608
|
const sourceCharge = createTxCharge('tx-charge-1', [
|
|
587
609
|
createMockTransaction({ amount: "-100" }),
|
|
588
610
|
]);
|
|
@@ -598,7 +620,7 @@ describe('Single-Match Provider', () => {
|
|
|
598
620
|
]),
|
|
599
621
|
];
|
|
600
622
|
|
|
601
|
-
const results = findMatches(sourceCharge, candidateCharges, USER_ID);
|
|
623
|
+
const results = await findMatches(sourceCharge, candidateCharges, USER_ID, createMockInjector());
|
|
602
624
|
|
|
603
625
|
expect(results).toHaveLength(1);
|
|
604
626
|
expect(results[0].confidenceScore).toBeGreaterThan(0.9);
|