@openora/core 0.4.1-canary.93 → 0.4.1-canary.95
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/dist/.tsbuildinfo +1 -1
- package/dist/audit/plugin.d.ts.map +1 -1
- package/dist/audit/plugin.js +13 -0
- package/dist/audit/plugin.js.map +1 -1
- package/dist/contracts/adapters/audit.d.ts +1 -1
- package/dist/contracts/adapters/audit.d.ts.map +1 -1
- package/dist/contracts/adapters/audit.js.map +1 -1
- package/dist/contracts/schemas/events.d.ts +5 -0
- package/dist/contracts/schemas/events.d.ts.map +1 -1
- package/dist/contracts/schemas/events.js +9 -0
- package/dist/contracts/schemas/events.js.map +1 -1
- package/dist/testing/mock.d.ts +6 -1
- package/dist/testing/mock.d.ts.map +1 -1
- package/dist/testing/mock.js +9 -0
- package/dist/testing/mock.js.map +1 -1
- package/dist/wallet/__tests__/custody-sweep-gate.test.d.ts +2 -0
- package/dist/wallet/__tests__/custody-sweep-gate.test.d.ts.map +1 -0
- package/dist/wallet/__tests__/custody-sweep-gate.test.js +70 -0
- package/dist/wallet/__tests__/custody-sweep-gate.test.js.map +1 -0
- package/dist/wallet/__tests__/custody-sweep.service.int.test.d.ts +2 -0
- package/dist/wallet/__tests__/custody-sweep.service.int.test.d.ts.map +1 -0
- package/dist/wallet/__tests__/custody-sweep.service.int.test.js +246 -0
- package/dist/wallet/__tests__/custody-sweep.service.int.test.js.map +1 -0
- package/dist/wallet/__tests__/reconciliation-diff.test.d.ts +2 -0
- package/dist/wallet/__tests__/reconciliation-diff.test.d.ts.map +1 -0
- package/dist/wallet/__tests__/reconciliation-diff.test.js +49 -0
- package/dist/wallet/__tests__/reconciliation-diff.test.js.map +1 -0
- package/dist/wallet/__tests__/reconciliation.service.int.test.d.ts +2 -0
- package/dist/wallet/__tests__/reconciliation.service.int.test.d.ts.map +1 -0
- package/dist/wallet/__tests__/reconciliation.service.int.test.js +395 -0
- package/dist/wallet/__tests__/reconciliation.service.int.test.js.map +1 -0
- package/dist/wallet/__tests__/wallet-asset.router.int.test.js +12 -2
- package/dist/wallet/__tests__/wallet-asset.router.int.test.js.map +1 -1
- package/dist/wallet/__tests__/wallet-auto-rule.router.int.test.js +12 -2
- package/dist/wallet/__tests__/wallet-auto-rule.router.int.test.js.map +1 -1
- package/dist/wallet/__tests__/wallet-auto-withdrawal-config.router.int.test.js +12 -2
- package/dist/wallet/__tests__/wallet-auto-withdrawal-config.router.int.test.js.map +1 -1
- package/dist/wallet/__tests__/wallet-custody-routes.router.test.d.ts +2 -0
- package/dist/wallet/__tests__/wallet-custody-routes.router.test.d.ts.map +1 -0
- package/dist/wallet/__tests__/wallet-custody-routes.router.test.js +128 -0
- package/dist/wallet/__tests__/wallet-custody-routes.router.test.js.map +1 -0
- package/dist/wallet/__tests__/wallet-webhook.router.int.test.js +32 -4
- package/dist/wallet/__tests__/wallet-webhook.router.int.test.js.map +1 -1
- package/dist/wallet/__tests__/wallet.router.int.test.js +12 -2
- package/dist/wallet/__tests__/wallet.router.int.test.js.map +1 -1
- package/dist/wallet/__tests__/wallet.service.int.test.js +34 -11
- package/dist/wallet/__tests__/wallet.service.int.test.js.map +1 -1
- package/dist/wallet/contract/index.d.ts.map +1 -1
- package/dist/wallet/contract/index.js +8 -8
- package/dist/wallet/contract/index.js.map +1 -1
- package/dist/wallet/plugin.d.ts.map +1 -1
- package/dist/wallet/plugin.js +91 -17
- package/dist/wallet/plugin.js.map +1 -1
- package/dist/wallet/router/index.d.ts +19 -2
- package/dist/wallet/router/index.d.ts.map +1 -1
- package/dist/wallet/router/index.js +32 -17
- package/dist/wallet/router/index.js.map +1 -1
- package/dist/wallet/service/custody-sweep.service.d.ts +77 -0
- package/dist/wallet/service/custody-sweep.service.d.ts.map +1 -0
- package/dist/wallet/service/custody-sweep.service.js +354 -0
- package/dist/wallet/service/custody-sweep.service.js.map +1 -0
- package/dist/wallet/service/reconciliation-finding.service.d.ts +39 -0
- package/dist/wallet/service/reconciliation-finding.service.d.ts.map +1 -0
- package/dist/wallet/service/reconciliation-finding.service.js +36 -0
- package/dist/wallet/service/reconciliation-finding.service.js.map +1 -0
- package/dist/wallet/service/reconciliation.service.d.ts +112 -0
- package/dist/wallet/service/reconciliation.service.d.ts.map +1 -0
- package/dist/wallet/service/reconciliation.service.js +509 -0
- package/dist/wallet/service/reconciliation.service.js.map +1 -0
- package/dist/wallet/service/wallet.service.d.ts +7 -2
- package/dist/wallet/service/wallet.service.d.ts.map +1 -1
- package/dist/wallet/service/wallet.service.js +45 -3
- package/dist/wallet/service/wallet.service.js.map +1 -1
- package/package.json +1 -1
- package/dist/wallet/__tests__/wallet-custody-stubs.router.test.d.ts +0 -2
- package/dist/wallet/__tests__/wallet-custody-stubs.router.test.d.ts.map +0 -1
- package/dist/wallet/__tests__/wallet-custody-stubs.router.test.js +0 -80
- package/dist/wallet/__tests__/wallet-custody-stubs.router.test.js.map +0 -1
|
@@ -0,0 +1,246 @@
|
|
|
1
|
+
import { randomUUID } from 'node:crypto';
|
|
2
|
+
import { describe, it, expect, vi, beforeAll, afterAll, beforeEach } from 'vitest';
|
|
3
|
+
import { eq, sql } from 'drizzle-orm';
|
|
4
|
+
import { createTestDb } from '@openora/core/testing';
|
|
5
|
+
import { definePlatformConfig, } from '@openora/core/contracts';
|
|
6
|
+
import { makeAuditWriter, makePaymentProviderRegistry, mock } from '../../testing/mock.js';
|
|
7
|
+
import { migrate } from '../migrate.js';
|
|
8
|
+
import { wallet, walletAsset, walletBalance, walletCustodySweep, walletJobRun, walletReconciliationFinding, walletTransaction, } from '../schema/index.js';
|
|
9
|
+
import { CustodySweepService, CUSTODY_SWEEP_JOB_NAME } from '../service/custody-sweep.service.js';
|
|
10
|
+
let db;
|
|
11
|
+
beforeAll(async () => {
|
|
12
|
+
db = await createTestDb([migrate]);
|
|
13
|
+
});
|
|
14
|
+
afterAll(async () => {
|
|
15
|
+
await db.drop();
|
|
16
|
+
});
|
|
17
|
+
beforeEach(async () => {
|
|
18
|
+
await db.drizzle.db.execute(sql `TRUNCATE ${walletReconciliationFinding}, ${walletCustodySweep}, ${walletJobRun}, ${walletAsset}, ${walletTransaction}, ${walletBalance}, ${wallet} RESTART IDENTITY CASCADE`);
|
|
19
|
+
});
|
|
20
|
+
const platformConfig = (overrides = {}) => definePlatformConfig({
|
|
21
|
+
wallet: {
|
|
22
|
+
sweep: {
|
|
23
|
+
cron: '*/15 * * * *',
|
|
24
|
+
feeMultiple: '1',
|
|
25
|
+
batchSize: 200,
|
|
26
|
+
concurrency: 4,
|
|
27
|
+
unknownAfterMinutes: 60,
|
|
28
|
+
staleRunAfterMinutes: 30,
|
|
29
|
+
...overrides,
|
|
30
|
+
},
|
|
31
|
+
},
|
|
32
|
+
});
|
|
33
|
+
async function seedAsset(overrides = {}) {
|
|
34
|
+
await db.drizzle.db.insert(walletAsset).values({
|
|
35
|
+
currency: 'USDT',
|
|
36
|
+
network: 'TRC20',
|
|
37
|
+
providerAssetId: 'usdt-trc20',
|
|
38
|
+
minDeposit: '1',
|
|
39
|
+
minWithdrawal: '1',
|
|
40
|
+
withdrawalFee: '0.1',
|
|
41
|
+
...overrides,
|
|
42
|
+
});
|
|
43
|
+
}
|
|
44
|
+
function makeBalance(overrides = {}) {
|
|
45
|
+
return {
|
|
46
|
+
userId: randomUUID(),
|
|
47
|
+
currency: 'USDT',
|
|
48
|
+
network: 'TRC20',
|
|
49
|
+
amount: '100',
|
|
50
|
+
estimatedFee: '1',
|
|
51
|
+
...overrides,
|
|
52
|
+
};
|
|
53
|
+
}
|
|
54
|
+
function serviceWith(adapter, config = platformConfig()) {
|
|
55
|
+
const paymentProviders = makePaymentProviderRegistry({ adapter });
|
|
56
|
+
const audit = makeAuditWriter();
|
|
57
|
+
const service = new CustodySweepService({
|
|
58
|
+
drizzle: db.drizzle,
|
|
59
|
+
paymentProviders,
|
|
60
|
+
audit,
|
|
61
|
+
platformConfig: config,
|
|
62
|
+
});
|
|
63
|
+
return { service, audit };
|
|
64
|
+
}
|
|
65
|
+
async function sweepRows(userId) {
|
|
66
|
+
return db.drizzle.db
|
|
67
|
+
.select()
|
|
68
|
+
.from(walletCustodySweep)
|
|
69
|
+
.where(eq(walletCustodySweep.userId, userId));
|
|
70
|
+
}
|
|
71
|
+
describe('CustodySweepService (real PG)', () => {
|
|
72
|
+
it('no-ops without touching the database when sweep policy is unconfigured', async () => {
|
|
73
|
+
const listSweepableBalances = vi.fn().mockResolvedValue([makeBalance()]);
|
|
74
|
+
const { service } = serviceWith(mock({ listSweepableBalances, sweepToPool: vi.fn() }), definePlatformConfig({}));
|
|
75
|
+
const result = await service.runCycle();
|
|
76
|
+
expect(result).toBeNull();
|
|
77
|
+
expect(listSweepableBalances).not.toHaveBeenCalled();
|
|
78
|
+
const runs = await db.drizzle.db.select().from(walletJobRun);
|
|
79
|
+
expect(runs).toHaveLength(0);
|
|
80
|
+
});
|
|
81
|
+
it('a second cycle does not re-sweep an in-flight balance', async () => {
|
|
82
|
+
await seedAsset();
|
|
83
|
+
const b = makeBalance();
|
|
84
|
+
const listSweepableBalances = vi.fn().mockResolvedValue([b]);
|
|
85
|
+
const sweepToPool = vi
|
|
86
|
+
.fn()
|
|
87
|
+
.mockResolvedValue({ externalId: 'vendor-ref-1', poolRef: 'pool-players-1' });
|
|
88
|
+
const { service } = serviceWith(mock({ listSweepableBalances, sweepToPool }));
|
|
89
|
+
const first = await service.runCycle();
|
|
90
|
+
expect(first?.summary.swept).toBe(1);
|
|
91
|
+
const second = await service.runCycle();
|
|
92
|
+
expect(second?.summary.swept).toBe(0);
|
|
93
|
+
expect(second?.summary.inFlight).toBe(1);
|
|
94
|
+
expect(sweepToPool).toHaveBeenCalledTimes(1);
|
|
95
|
+
const rows = await sweepRows(b.userId);
|
|
96
|
+
expect(rows).toHaveLength(1);
|
|
97
|
+
expect(rows[0]?.status).toBe('processing');
|
|
98
|
+
// Which pool received the funds, not merely that a transfer happened - the
|
|
99
|
+
// separation of player money from operator money has to be evidenced.
|
|
100
|
+
expect(rows[0]?.poolRef).toBe('pool-players-1');
|
|
101
|
+
});
|
|
102
|
+
it('sweepToPool throws -> the guard is still held and the next cycle does not re-sweep', async () => {
|
|
103
|
+
await seedAsset();
|
|
104
|
+
const b = makeBalance();
|
|
105
|
+
const listSweepableBalances = vi.fn().mockResolvedValue([b]);
|
|
106
|
+
const sweepToPool = vi.fn().mockRejectedValue(new Error('vendor timed out'));
|
|
107
|
+
const { service } = serviceWith(mock({ listSweepableBalances, sweepToPool }));
|
|
108
|
+
const first = await service.runCycle();
|
|
109
|
+
expect(first?.summary.unknown).toBe(1);
|
|
110
|
+
expect(first?.summary.swept).toBe(0);
|
|
111
|
+
const rowsAfterFirst = await sweepRows(b.userId);
|
|
112
|
+
expect(rowsAfterFirst).toHaveLength(1);
|
|
113
|
+
expect(rowsAfterFirst[0]?.status).toBe('unknown');
|
|
114
|
+
const second = await service.runCycle();
|
|
115
|
+
expect(second?.summary.inFlight).toBe(1);
|
|
116
|
+
expect(second?.summary.swept).toBe(0);
|
|
117
|
+
// Still exactly the one row from the first cycle - the throw never released the guard.
|
|
118
|
+
expect(sweepToPool).toHaveBeenCalledTimes(1);
|
|
119
|
+
const rowsAfterSecond = await sweepRows(b.userId);
|
|
120
|
+
expect(rowsAfterSecond).toHaveLength(1);
|
|
121
|
+
expect(rowsAfterSecond[0]?.status).toBe('unknown');
|
|
122
|
+
});
|
|
123
|
+
it('a swept balance changes no wallet_balance row and creates no wallet_transaction', async () => {
|
|
124
|
+
await seedAsset();
|
|
125
|
+
const b = makeBalance();
|
|
126
|
+
const adapter = mock({
|
|
127
|
+
listSweepableBalances: vi.fn().mockResolvedValue([b]),
|
|
128
|
+
sweepToPool: vi.fn().mockResolvedValue({ externalId: 'vendor-ref-2' }),
|
|
129
|
+
});
|
|
130
|
+
const { service } = serviceWith(adapter);
|
|
131
|
+
const result = await service.runCycle();
|
|
132
|
+
expect(result?.summary.swept).toBe(1);
|
|
133
|
+
const balances = await db.drizzle.db.select().from(walletBalance);
|
|
134
|
+
const transactions = await db.drizzle.db.select().from(walletTransaction);
|
|
135
|
+
expect(balances).toHaveLength(0);
|
|
136
|
+
expect(transactions).toHaveLength(0);
|
|
137
|
+
});
|
|
138
|
+
it('two cycles started concurrently: the second returns immediately and listSweepableBalances is called exactly once', async () => {
|
|
139
|
+
await seedAsset();
|
|
140
|
+
// The vendor call has to be slow enough to hold the first cycle's claim open while
|
|
141
|
+
// the second one attempts its own. Resolved immediately, a cycle can finish - and
|
|
142
|
+
// clear its claim - before the sibling's insert is even issued, at which point both
|
|
143
|
+
// legitimately succeed as two SEQUENTIAL runs and the test fails without anything
|
|
144
|
+
// being wrong. That is a race in the test, not in the claim.
|
|
145
|
+
const balances = [makeBalance()];
|
|
146
|
+
const listSweepableBalances = vi.fn(() => new Promise((resolve) => setTimeout(() => resolve(balances), 100)));
|
|
147
|
+
const sweepToPool = vi.fn().mockResolvedValue({ externalId: randomUUID() });
|
|
148
|
+
const { service } = serviceWith(mock({ listSweepableBalances, sweepToPool }));
|
|
149
|
+
const [a, b] = await Promise.all([service.runCycle(), service.runCycle()]);
|
|
150
|
+
const results = [a, b];
|
|
151
|
+
const claimed = results.filter((r) => r !== null);
|
|
152
|
+
const skipped = results.filter((r) => r === null);
|
|
153
|
+
expect(claimed).toHaveLength(1);
|
|
154
|
+
expect(skipped).toHaveLength(1);
|
|
155
|
+
expect(listSweepableBalances).toHaveBeenCalledTimes(1);
|
|
156
|
+
});
|
|
157
|
+
it('a missing wallet_asset row produces an unconfigured_asset finding', async () => {
|
|
158
|
+
// Deliberately no seedAsset() call - (currency, network) is unconfigured.
|
|
159
|
+
const b = makeBalance({ currency: 'DOGE', network: 'MAINNET' });
|
|
160
|
+
const listSweepableBalances = vi.fn().mockResolvedValue([b]);
|
|
161
|
+
const sweepToPool = vi.fn().mockResolvedValue({ externalId: 'never-called' });
|
|
162
|
+
const { service } = serviceWith(mock({ listSweepableBalances, sweepToPool }));
|
|
163
|
+
const result = await service.runCycle();
|
|
164
|
+
expect(result?.summary.swept).toBe(0);
|
|
165
|
+
expect(result?.summary.skippedDust).toBe(0);
|
|
166
|
+
const findings = await db.drizzle.db.select().from(walletReconciliationFinding);
|
|
167
|
+
expect(findings).toHaveLength(1);
|
|
168
|
+
expect(findings[0]).toMatchObject({
|
|
169
|
+
kind: 'unconfigured_asset',
|
|
170
|
+
currency: 'DOGE',
|
|
171
|
+
network: 'MAINNET',
|
|
172
|
+
amount: '100.000000000000000000',
|
|
173
|
+
runId: result?.runId,
|
|
174
|
+
});
|
|
175
|
+
const sweeps = await sweepRows(b.userId);
|
|
176
|
+
expect(sweeps).toHaveLength(0);
|
|
177
|
+
});
|
|
178
|
+
it('writes exactly one audit entry per cycle regardless of how many balances were swept', async () => {
|
|
179
|
+
await seedAsset();
|
|
180
|
+
const balances = [makeBalance(), makeBalance(), makeBalance()];
|
|
181
|
+
const adapter = mock({
|
|
182
|
+
listSweepableBalances: vi.fn().mockResolvedValue(balances),
|
|
183
|
+
sweepToPool: vi.fn().mockImplementation(async () => ({ externalId: randomUUID() })),
|
|
184
|
+
});
|
|
185
|
+
const { service, audit } = serviceWith(adapter);
|
|
186
|
+
const result = await service.runCycle();
|
|
187
|
+
expect(result?.summary.swept).toBe(3);
|
|
188
|
+
expect(audit.recordInTransaction).toHaveBeenCalledTimes(1);
|
|
189
|
+
const [, entry] = audit.recordInTransaction.mock.calls[0];
|
|
190
|
+
expect(entry.action).toBe('wallet.custody.sweep_cycle');
|
|
191
|
+
expect(entry.after).toMatchObject({ runId: result?.runId, swept: 3 });
|
|
192
|
+
});
|
|
193
|
+
it('a repeated unconfigured asset files exactly one finding, not one per cycle', async () => {
|
|
194
|
+
// Without a stable dedup key this condition - which recurs every single tick until
|
|
195
|
+
// an operator configures the asset - files a fresh finding per cron tick and pins
|
|
196
|
+
// the reconciliation alert threshold permanently over the line.
|
|
197
|
+
const b = makeBalance({ currency: 'DOGE', network: 'MAINNET' });
|
|
198
|
+
const listSweepableBalances = vi.fn().mockResolvedValue([b]);
|
|
199
|
+
const sweepToPool = vi.fn();
|
|
200
|
+
const { service } = serviceWith(mock({ listSweepableBalances, sweepToPool }));
|
|
201
|
+
await service.runCycle();
|
|
202
|
+
await service.runCycle();
|
|
203
|
+
const findings = await db.drizzle.db.select().from(walletReconciliationFinding);
|
|
204
|
+
expect(findings).toHaveLength(1);
|
|
205
|
+
});
|
|
206
|
+
it('a throwing adapter fails the run and frees the claim for the next cycle', async () => {
|
|
207
|
+
await seedAsset();
|
|
208
|
+
const listSweepableBalances = vi
|
|
209
|
+
.fn()
|
|
210
|
+
.mockRejectedValueOnce(new Error('vendor 503'))
|
|
211
|
+
.mockResolvedValue([]);
|
|
212
|
+
const { service } = serviceWith(mock({ listSweepableBalances, sweepToPool: vi.fn() }));
|
|
213
|
+
await expect(service.runCycle()).rejects.toThrow('vendor 503');
|
|
214
|
+
const [failed] = await db.drizzle.db
|
|
215
|
+
.select()
|
|
216
|
+
.from(walletJobRun)
|
|
217
|
+
.where(eq(walletJobRun.status, 'failed'));
|
|
218
|
+
expect(failed?.finishedAt).not.toBeNull();
|
|
219
|
+
// The whole point: a vendor blip must not hold the single live-run slot until the
|
|
220
|
+
// staleness window elapses.
|
|
221
|
+
const second = await service.runCycle();
|
|
222
|
+
expect(second).not.toBeNull();
|
|
223
|
+
});
|
|
224
|
+
it('takes over a run whose startedAt is older than the staleness threshold', async () => {
|
|
225
|
+
const staleRunId = randomUUID();
|
|
226
|
+
await db.drizzle.db.insert(walletJobRun).values({
|
|
227
|
+
jobName: CUSTODY_SWEEP_JOB_NAME,
|
|
228
|
+
runId: staleRunId,
|
|
229
|
+
startedAt: new Date(Date.now() - 2 * 60_000),
|
|
230
|
+
});
|
|
231
|
+
const listSweepableBalances = vi.fn().mockResolvedValue([]);
|
|
232
|
+
const { service } = serviceWith(mock({ listSweepableBalances, sweepToPool: vi.fn() }), platformConfig({ staleRunAfterMinutes: 1 }));
|
|
233
|
+
const result = await service.runCycle();
|
|
234
|
+
expect(result).not.toBeNull();
|
|
235
|
+
expect(result?.runId).not.toBe(staleRunId);
|
|
236
|
+
const runs = await db.drizzle.db
|
|
237
|
+
.select()
|
|
238
|
+
.from(walletJobRun)
|
|
239
|
+
.where(eq(walletJobRun.jobName, CUSTODY_SWEEP_JOB_NAME));
|
|
240
|
+
const stale = runs.find((r) => r.runId === staleRunId);
|
|
241
|
+
const fresh = runs.find((r) => r.runId === result?.runId);
|
|
242
|
+
expect(stale?.status).toBe('abandoned');
|
|
243
|
+
expect(fresh?.status).toBe('completed');
|
|
244
|
+
});
|
|
245
|
+
});
|
|
246
|
+
//# sourceMappingURL=custody-sweep.service.int.test.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"custody-sweep.service.int.test.js","sourceRoot":"","sources":["../../../src/wallet/__tests__/custody-sweep.service.int.test.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"reconciliation-diff.test.d.ts","sourceRoot":"","sources":["../../../src/wallet/__tests__/reconciliation-diff.test.ts"],"names":[],"mappings":""}
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
import { describe, it, expect } from 'vitest';
|
|
2
|
+
import { diffDeposit } from '../service/reconciliation.service.js';
|
|
3
|
+
const depositEvent = (over = {}) => ({
|
|
4
|
+
kind: 'deposit',
|
|
5
|
+
address: 'bc1qxyz',
|
|
6
|
+
amount: '1',
|
|
7
|
+
currency: 'BTC',
|
|
8
|
+
txHash: '0xabc',
|
|
9
|
+
externalId: 'vendor-ext-1',
|
|
10
|
+
...over,
|
|
11
|
+
});
|
|
12
|
+
const ledgerRow = (over = {}) => ({
|
|
13
|
+
id: 'tx-1',
|
|
14
|
+
currency: 'BTC',
|
|
15
|
+
amount: '1',
|
|
16
|
+
network: null,
|
|
17
|
+
...over,
|
|
18
|
+
});
|
|
19
|
+
describe('diffDeposit', () => {
|
|
20
|
+
it('flags missing_deposit when no ledger row matches the vendor event', () => {
|
|
21
|
+
expect(diffDeposit(depositEvent(), undefined)).toBe('missing_deposit');
|
|
22
|
+
});
|
|
23
|
+
it('flags currency_mismatch when the ledger row settled in a different currency', () => {
|
|
24
|
+
const event = depositEvent({ currency: 'ETH' });
|
|
25
|
+
const tx = ledgerRow({ currency: 'BTC' });
|
|
26
|
+
expect(diffDeposit(event, tx)).toBe('currency_mismatch');
|
|
27
|
+
});
|
|
28
|
+
it('flags currency_mismatch case-insensitively as a genuine mismatch, not a false positive', () => {
|
|
29
|
+
const event = depositEvent({ currency: 'btc' });
|
|
30
|
+
const tx = ledgerRow({ currency: 'BTC' });
|
|
31
|
+
expect(diffDeposit(event, tx)).toBeNull();
|
|
32
|
+
});
|
|
33
|
+
it('flags amount_mismatch when the currencies agree but the amounts differ', () => {
|
|
34
|
+
const event = depositEvent({ amount: '2' });
|
|
35
|
+
const tx = ledgerRow({ amount: '1' });
|
|
36
|
+
expect(diffDeposit(event, tx)).toBe('amount_mismatch');
|
|
37
|
+
});
|
|
38
|
+
it('never routes an amount compare through float - "1" and "1.00" reconcile exactly', () => {
|
|
39
|
+
const event = depositEvent({ amount: '1.00' });
|
|
40
|
+
const tx = ledgerRow({ amount: '1' });
|
|
41
|
+
expect(diffDeposit(event, tx)).toBeNull();
|
|
42
|
+
});
|
|
43
|
+
it('reconciles (returns null) when currency and amount both match', () => {
|
|
44
|
+
const event = depositEvent();
|
|
45
|
+
const tx = ledgerRow();
|
|
46
|
+
expect(diffDeposit(event, tx)).toBeNull();
|
|
47
|
+
});
|
|
48
|
+
});
|
|
49
|
+
//# sourceMappingURL=reconciliation-diff.test.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"reconciliation-diff.test.js","sourceRoot":"","sources":["../../../src/wallet/__tests__/reconciliation-diff.test.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,QAAQ,EAAE,EAAE,EAAE,MAAM,EAAE,MAAM,QAAQ,CAAC;AAC9C,OAAO,EAAE,WAAW,EAAE,MAAM,sCAAsC,CAAC;AAEnE,MAAM,YAAY,GAAG,CAAC,OAAmD,EAAE,EAAE,EAAE,CAAC,CAAC;IAC/E,IAAI,EAAE,SAAkB;IACxB,OAAO,EAAE,SAAS;IAClB,MAAM,EAAE,GAAG;IACX,QAAQ,EAAE,KAAK;IACf,MAAM,EAAE,OAAO;IACf,UAAU,EAAE,cAAc;IAC1B,GAAG,IAAI;CACR,CAAC,CAAC;AAEH,MAAM,SAAS,GAAG,CAChB,OAA0F,EAAE,EAC5F,EAAE,CAAC,CAAC;IACJ,EAAE,EAAE,MAAM;IACV,QAAQ,EAAE,KAAK;IACf,MAAM,EAAE,GAAG;IACX,OAAO,EAAE,IAAI;IACb,GAAG,IAAI;CACR,CAAC,CAAC;AAEH,QAAQ,CAAC,aAAa,EAAE,GAAG,EAAE;IAC3B,EAAE,CAAC,mEAAmE,EAAE,GAAG,EAAE;QAC3E,MAAM,CAAC,WAAW,CAAC,YAAY,EAAE,EAAE,SAAS,CAAC,CAAC,CAAC,IAAI,CAAC,iBAAiB,CAAC,CAAC;IACzE,CAAC,CAAC,CAAC;IAEH,EAAE,CAAC,6EAA6E,EAAE,GAAG,EAAE;QACrF,MAAM,KAAK,GAAG,YAAY,CAAC,EAAE,QAAQ,EAAE,KAAK,EAAE,CAAC,CAAC;QAChD,MAAM,EAAE,GAAG,SAAS,CAAC,EAAE,QAAQ,EAAE,KAAK,EAAE,CAAC,CAAC;QAE1C,MAAM,CAAC,WAAW,CAAC,KAAK,EAAE,EAAE,CAAC,CAAC,CAAC,IAAI,CAAC,mBAAmB,CAAC,CAAC;IAC3D,CAAC,CAAC,CAAC;IAEH,EAAE,CAAC,wFAAwF,EAAE,GAAG,EAAE;QAChG,MAAM,KAAK,GAAG,YAAY,CAAC,EAAE,QAAQ,EAAE,KAAK,EAAE,CAAC,CAAC;QAChD,MAAM,EAAE,GAAG,SAAS,CAAC,EAAE,QAAQ,EAAE,KAAK,EAAE,CAAC,CAAC;QAE1C,MAAM,CAAC,WAAW,CAAC,KAAK,EAAE,EAAE,CAAC,CAAC,CAAC,QAAQ,EAAE,CAAC;IAC5C,CAAC,CAAC,CAAC;IAEH,EAAE,CAAC,wEAAwE,EAAE,GAAG,EAAE;QAChF,MAAM,KAAK,GAAG,YAAY,CAAC,EAAE,MAAM,EAAE,GAAG,EAAE,CAAC,CAAC;QAC5C,MAAM,EAAE,GAAG,SAAS,CAAC,EAAE,MAAM,EAAE,GAAG,EAAE,CAAC,CAAC;QAEtC,MAAM,CAAC,WAAW,CAAC,KAAK,EAAE,EAAE,CAAC,CAAC,CAAC,IAAI,CAAC,iBAAiB,CAAC,CAAC;IACzD,CAAC,CAAC,CAAC;IAEH,EAAE,CAAC,iFAAiF,EAAE,GAAG,EAAE;QACzF,MAAM,KAAK,GAAG,YAAY,CAAC,EAAE,MAAM,EAAE,MAAM,EAAE,CAAC,CAAC;QAC/C,MAAM,EAAE,GAAG,SAAS,CAAC,EAAE,MAAM,EAAE,GAAG,EAAE,CAAC,CAAC;QAEtC,MAAM,CAAC,WAAW,CAAC,KAAK,EAAE,EAAE,CAAC,CAAC,CAAC,QAAQ,EAAE,CAAC;IAC5C,CAAC,CAAC,CAAC;IAEH,EAAE,CAAC,+DAA+D,EAAE,GAAG,EAAE;QACvE,MAAM,KAAK,GAAG,YAAY,EAAE,CAAC;QAC7B,MAAM,EAAE,GAAG,SAAS,EAAE,CAAC;QAEvB,MAAM,CAAC,WAAW,CAAC,KAAK,EAAE,EAAE,CAAC,CAAC,CAAC,QAAQ,EAAE,CAAC;IAC5C,CAAC,CAAC,CAAC;AACL,CAAC,CAAC,CAAC"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"reconciliation.service.int.test.d.ts","sourceRoot":"","sources":["../../../src/wallet/__tests__/reconciliation.service.int.test.ts"],"names":[],"mappings":""}
|