-
Notifications
You must be signed in to change notification settings - Fork 57
/
InMemoryTransactionTracker.test.ts
136 lines (121 loc) · 5.14 KB
/
InMemoryTransactionTracker.test.ts
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
import { CardanoSerializationLib, CSL } from '@cardano-sdk/core';
import { flushPromises } from '@cardano-sdk/util-dev';
import { dummyLogger } from 'ts-log';
import { InMemoryTransactionTracker } from '../src/InMemoryTransactionTracker';
import { TransactionFailure } from '../src/Transaction/TransactionError';
import { providerStub, ProviderStub, queryTransactionsResult } from './ProviderStub';
const realSetImmediate = global.setImmediate;
jest.useFakeTimers();
describe('InMemoryTransactionTracker', () => {
const POLL_INTERVAL = 1000;
const MAX_TIP_FAILURES = 2;
let ledgerTipSlot: number;
let provider: ProviderStub;
let txTracker: InMemoryTransactionTracker;
let hash_transaction: jest.Mock;
const flushRequests = async (times: number) => {
for (let i = 0; i < times; i++) {
await jest.advanceTimersByTime(POLL_INTERVAL);
await flushPromises(realSetImmediate);
}
};
const mockHashTransactionReturn = (resultHash: string) => {
hash_transaction.mockReturnValue({
to_bytes() {
return Buffer.from(resultHash);
}
});
};
beforeEach(async () => {
provider = providerStub();
provider.queryTransactionsByHashes.mockReturnValue([queryTransactionsResult[0]]);
hash_transaction = jest.fn();
mockHashTransactionReturn('some-hash');
txTracker = new InMemoryTransactionTracker({
provider,
csl: { hash_transaction } as unknown as CardanoSerializationLib,
logger: dummyLogger,
pollInterval: POLL_INTERVAL,
maxTipFailures: MAX_TIP_FAILURES
});
ledgerTipSlot = (await provider.ledgerTip()).slot;
});
describe('trackTransaction', () => {
let onTransaction: jest.Mock;
beforeEach(() => {
onTransaction = jest.fn();
txTracker.on('transaction', onTransaction);
});
it('invalid transaction (no ttl)', async () => {
await expect(() =>
txTracker.trackTransaction({
body: () => ({
ttl: () => void 0
})
} as unknown as CSL.Transaction)
).rejects.toThrowError(TransactionFailure.CannotTrack);
});
describe('valid transaction', () => {
let transaction: CSL.Transaction;
beforeEach(async () => {
transaction = {
body: () => ({
ttl: () => ledgerTipSlot
})
} as unknown as CSL.Transaction;
});
describe('ledger tip fetch error', () => {
it('Ignores up to maxTipFailures (2) tip fetch errors', async () => {
provider.queryTransactionsByHashes.mockResolvedValueOnce([]);
provider.queryTransactionsByHashes.mockResolvedValueOnce([]);
provider.ledgerTip.mockRejectedValueOnce(new Error('error1'));
provider.ledgerTip.mockRejectedValueOnce(new Error('error2'));
const promise = txTracker.trackTransaction(transaction);
await flushRequests(3);
await promise;
expect(provider.ledgerTip).toBeCalledTimes(3);
expect(provider.queryTransactionsByHashes).toBeCalledTimes(3);
});
// it('Throws on >maxTipFailures (3) tip fetch errors', async () => {
// provider.queryTransactionsByHashes.mockResolvedValueOnce([]);
// provider.queryTransactionsByHashes.mockResolvedValueOnce([]);
// provider.queryTransactionsByHashes.mockResolvedValueOnce([]);
// provider.ledgerTip.mockRejectedValueOnce(new Error('error1'));
// provider.ledgerTip.mockRejectedValueOnce(new Error('error2'));
// provider.ledgerTip.mockRejectedValueOnce(new Error('error3'));
// await expect(async () => {
// const promise = txTracker.trackTransaction(transaction);
// await flushRequests(3);
// return promise;
// }).rejects.toThrowError(TransactionFailure.CannotTrack);
// });
});
it('polls provider at "pollInterval" until it returns the transaction', async () => {
provider.queryTransactionsByHashes.mockResolvedValueOnce([]);
provider.queryTransactionsByHashes.mockResolvedValueOnce([]);
const promise = txTracker.trackTransaction(transaction);
await flushRequests(3 + 1); // 3 actually needed + 1 to assert that we're no longer polling
await promise;
expect(provider.queryTransactionsByHashes).toBeCalledTimes(3);
});
it('emits "transaction" event for tracked transactions, returns promise unique per pending tx', async () => {
const promise1 = txTracker.trackTransaction(transaction);
const promise2 = txTracker.trackTransaction(transaction);
mockHashTransactionReturn('other-hash');
const promise3 = txTracker.trackTransaction(transaction);
jest.runAllTimers();
await promise1;
await promise2;
await promise3;
expect(provider.queryTransactionsByHashes).toBeCalledTimes(2);
expect(onTransaction).toBeCalledTimes(2);
// assert it clears cache
const promise4 = txTracker.trackTransaction(transaction);
jest.runAllTimers();
await promise4;
expect(provider.queryTransactionsByHashes).toBeCalledTimes(3);
expect(onTransaction).toBeCalledTimes(3);
});
});
});
});