hive-stream 2.0.6 → 3.0.1

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 (123) hide show
  1. package/.claude/settings.local.json +12 -0
  2. package/.env.example +2 -2
  3. package/.travis.yml +11 -11
  4. package/AGENTS.md +35 -0
  5. package/CHANGELOG.md +166 -0
  6. package/CLAUDE.md +75 -0
  7. package/DOCUMENTATION.md +380 -0
  8. package/LICENSE +21 -21
  9. package/README.md +429 -238
  10. package/dist/actions.d.ts +41 -10
  11. package/dist/actions.js +126 -23
  12. package/dist/actions.js.map +1 -1
  13. package/dist/adapters/base.adapter.d.ts +43 -25
  14. package/dist/adapters/base.adapter.js +79 -49
  15. package/dist/adapters/base.adapter.js.map +1 -1
  16. package/dist/adapters/mongodb.adapter.d.ts +44 -37
  17. package/dist/adapters/mongodb.adapter.js +363 -158
  18. package/dist/adapters/mongodb.adapter.js.map +1 -1
  19. package/dist/adapters/postgresql.adapter.d.ts +66 -0
  20. package/dist/adapters/postgresql.adapter.js +598 -0
  21. package/dist/adapters/postgresql.adapter.js.map +1 -0
  22. package/dist/adapters/sqlite.adapter.d.ts +57 -41
  23. package/dist/adapters/sqlite.adapter.js +561 -397
  24. package/dist/adapters/sqlite.adapter.js.map +1 -1
  25. package/dist/api.d.ts +6 -6
  26. package/dist/api.js +181 -55
  27. package/dist/api.js.map +1 -1
  28. package/dist/config.d.ts +19 -16
  29. package/dist/config.js +21 -18
  30. package/dist/config.js.map +1 -1
  31. package/dist/contracts/coinflip.contract.d.ts +9 -14
  32. package/dist/contracts/coinflip.contract.js +232 -94
  33. package/dist/contracts/coinflip.contract.js.map +1 -1
  34. package/dist/contracts/contract.d.ts +3 -0
  35. package/dist/contracts/contract.js +26 -0
  36. package/dist/contracts/contract.js.map +1 -0
  37. package/dist/contracts/dice.contract.d.ts +10 -29
  38. package/dist/contracts/dice.contract.js +217 -155
  39. package/dist/contracts/dice.contract.js.map +1 -1
  40. package/dist/contracts/exchange.contract.d.ts +11 -0
  41. package/dist/contracts/exchange.contract.js +492 -0
  42. package/dist/contracts/exchange.contract.js.map +1 -0
  43. package/dist/contracts/lotto.contract.d.ts +16 -20
  44. package/dist/contracts/lotto.contract.js +238 -246
  45. package/dist/contracts/lotto.contract.js.map +1 -1
  46. package/dist/contracts/nft.contract.d.ts +28 -0
  47. package/dist/contracts/nft.contract.js +598 -0
  48. package/dist/contracts/nft.contract.js.map +1 -0
  49. package/dist/contracts/poll.contract.d.ts +4 -0
  50. package/dist/contracts/poll.contract.js +105 -0
  51. package/dist/contracts/poll.contract.js.map +1 -0
  52. package/dist/contracts/rps.contract.d.ts +9 -0
  53. package/dist/contracts/rps.contract.js +217 -0
  54. package/dist/contracts/rps.contract.js.map +1 -0
  55. package/dist/contracts/tipjar.contract.d.ts +4 -0
  56. package/dist/contracts/tipjar.contract.js +60 -0
  57. package/dist/contracts/tipjar.contract.js.map +1 -0
  58. package/dist/contracts/token.contract.d.ts +4 -0
  59. package/dist/contracts/token.contract.js +311 -0
  60. package/dist/contracts/token.contract.js.map +1 -0
  61. package/dist/exchanges/bittrex.d.ts +6 -6
  62. package/dist/exchanges/bittrex.js +34 -34
  63. package/dist/exchanges/coingecko.d.ts +11 -0
  64. package/dist/exchanges/coingecko.js +57 -0
  65. package/dist/exchanges/coingecko.js.map +1 -0
  66. package/dist/exchanges/exchange.d.ts +16 -9
  67. package/dist/exchanges/exchange.js +80 -26
  68. package/dist/exchanges/exchange.js.map +1 -1
  69. package/dist/hive-rates.d.ts +34 -9
  70. package/dist/hive-rates.js +208 -75
  71. package/dist/hive-rates.js.map +1 -1
  72. package/dist/index.d.ts +19 -11
  73. package/dist/index.js +47 -32
  74. package/dist/index.js.map +1 -1
  75. package/dist/streamer.d.ts +233 -93
  76. package/dist/streamer.js +1063 -545
  77. package/dist/streamer.js.map +1 -1
  78. package/dist/test.d.ts +1 -1
  79. package/dist/test.js +24 -25
  80. package/dist/test.js.map +1 -1
  81. package/dist/types/hive-stream.d.ts +106 -6
  82. package/dist/types/hive-stream.js +2 -2
  83. package/dist/types/rates.d.ts +47 -0
  84. package/dist/types/rates.js +29 -0
  85. package/dist/types/rates.js.map +1 -0
  86. package/dist/utils.d.ts +334 -27
  87. package/dist/utils.js +960 -261
  88. package/dist/utils.js.map +1 -1
  89. package/ecosystem.config.js +17 -17
  90. package/examples/contracts/README.md +8 -0
  91. package/examples/contracts/exchange.ts +38 -0
  92. package/examples/contracts/poll.ts +21 -0
  93. package/examples/contracts/rps.ts +19 -0
  94. package/examples/contracts/tipjar.ts +19 -0
  95. package/jest.config.js +8 -8
  96. package/package.json +54 -48
  97. package/test-contract-block.md +18 -18
  98. package/tests/actions.spec.ts +252 -0
  99. package/tests/adapters/actions-persistence.spec.ts +144 -0
  100. package/tests/adapters/postgresql.adapter.spec.ts +127 -0
  101. package/tests/adapters/sqlite.adapter.spec.ts +180 -42
  102. package/tests/contracts/coinflip.contract.spec.ts +94 -132
  103. package/tests/contracts/dice.contract.spec.ts +87 -160
  104. package/tests/contracts/entrants.json +728 -728
  105. package/tests/contracts/exchange.contract.spec.ts +84 -0
  106. package/tests/contracts/lotto.contract.spec.ts +59 -324
  107. package/tests/contracts/nft.contract.spec.ts +948 -0
  108. package/tests/contracts/token.contract.spec.ts +90 -0
  109. package/tests/exchanges/coingecko.exchange.spec.ts +169 -0
  110. package/tests/exchanges/exchange.base.spec.ts +246 -0
  111. package/tests/helpers/mock-adapter.ts +214 -0
  112. package/tests/helpers/mock-fetch.ts +165 -0
  113. package/tests/hive-chain-features.spec.ts +238 -0
  114. package/tests/hive-rates.spec.ts +443 -0
  115. package/tests/integration/hive-rates.integration.spec.ts +35 -0
  116. package/tests/setup.ts +29 -18
  117. package/tests/streamer-actions.spec.ts +274 -0
  118. package/tests/streamer.spec.ts +342 -152
  119. package/tests/types/rates.spec.ts +216 -0
  120. package/tests/utils.spec.ts +113 -95
  121. package/tsconfig.build.json +3 -22
  122. package/tslint.json +20 -20
  123. package/wallaby.js +26 -26
@@ -1,152 +1,342 @@
1
- import { TimeAction } from '../src/actions';
2
- import { Streamer } from '../src/streamer';
3
-
4
- describe('Streamer', () => {
5
- let sut: Streamer;
6
-
7
- beforeEach(() => {
8
- sut = new Streamer({
9
- JSON_ID: 'testing'
10
- });
11
- });
12
-
13
- afterEach(() => {
14
- sut.stop();
15
- });
16
-
17
- describe('Adapters', () => {
18
- test('Registers adapter and calls the create lifecycle method', () => {
19
- const adapter = {
20
- create: jest.fn().mockResolvedValue(true),
21
- destroy: jest.fn()
22
- };
23
-
24
- sut.registerAdapter(adapter);
25
-
26
- expect(adapter.create).toBeCalled();
27
- });
28
- });
29
-
30
- describe('Actions', () => {
31
- test('Registers a new action', async () => {
32
- const adapter = {
33
- create: jest.fn().mockResolvedValue(true),
34
- destroy: jest.fn(),
35
- loadActions: jest.fn().mockResolvedValue([])
36
- };
37
-
38
- sut.registerAdapter(adapter);
39
-
40
- const action = new TimeAction('1m', 'testoneminute', 'testcontract', 'testmethod');
41
-
42
- await sut.registerAction(action);
43
-
44
- const foundAction = sut['actions'].find(a => a.id === 'testoneminute');
45
-
46
- expect(foundAction).not.toBeUndefined();
47
- });
48
-
49
- test('Does not allow duplicate actions of the same id', async () => {
50
- const adapter = {
51
- create: jest.fn().mockResolvedValue(true),
52
- destroy: jest.fn(),
53
- loadActions: jest.fn().mockResolvedValue([])
54
- };
55
-
56
- sut.registerAdapter(adapter);
57
-
58
- const action = new TimeAction('1m', 'testoneminute', 'testcontract', 'testmethod');
59
- const action2 = new TimeAction('1m', 'testoneminute', 'testcontract', 'testmethod');
60
-
61
- await sut.registerAction(action);
62
- await sut.registerAction(action2);
63
-
64
- expect(sut['actions'].length).toStrictEqual(1);
65
- });
66
-
67
- test('Registers actions loaded from adapter loadActions call', async () => {
68
- const adapter = {
69
- create: jest.fn().mockResolvedValue(true),
70
- destroy: jest.fn(),
71
- loadActions: jest.fn().mockResolvedValue([new TimeAction('1m', 'testoneminute', 'testcontract', 'testmethod')])
72
- };
73
-
74
- sut.registerAdapter(adapter);
75
-
76
- const action = new TimeAction('1h', 'testonehour', 'testcontract', 'testmethod');
77
-
78
- await sut.registerAction(action);
79
-
80
- const foundAction = sut['actions'].find(a => a.id === 'testoneminute');
81
-
82
- expect(foundAction).not.toBeUndefined();
83
- });
84
- });
85
-
86
- describe('Contracts', () => {
87
- test('Should register a new contract', () => {
88
- const contract = {
89
- myMethod: jest.fn()
90
- };
91
-
92
- sut.registerContract('testcontract', contract);
93
-
94
- expect(contract['_instance']).toBeInstanceOf(Streamer);
95
- expect(sut['contracts'].length).toStrictEqual(1);
96
- });
97
-
98
- test('Should register a new contract and call its create method', () => {
99
- const contract = {
100
- create: jest.fn(),
101
- myMethod: jest.fn()
102
- };
103
-
104
- sut.registerContract('testcontract', contract);
105
-
106
- expect(contract.create).toBeCalled();
107
- expect(contract['_instance']).toBeInstanceOf(Streamer);
108
- expect(sut['contracts'].length).toStrictEqual(1);
109
- });
110
-
111
- test('Should unregister a registered contract', () => {
112
- const contract = {
113
- myMethod: jest.fn()
114
- };
115
-
116
- sut.registerContract('testcontract', contract);
117
- sut.unregisterContract('testcontract');
118
-
119
- expect(sut['contracts'].length).toStrictEqual(0);
120
- });
121
-
122
- test('Should unregister a registered contract and call its destroy method', () => {
123
- const contract = {
124
- destroy: jest.fn(),
125
- myMethod: jest.fn()
126
- };
127
-
128
- sut.registerContract('testcontract', contract);
129
- sut.unregisterContract('testcontract');
130
-
131
- expect(contract.destroy).toBeCalled();
132
- expect(sut['contracts'].length).toStrictEqual(0);
133
- });
134
- });
135
-
136
- test('Start method should resume from previous block number', async () => {
137
- const adapter = {
138
- loadState: jest.fn().mockResolvedValue({ lastBlockNumber: 509992 })
139
- };
140
-
141
- sut.registerAdapter(adapter);
142
-
143
- jest.spyOn(sut as any, 'getBlock').mockImplementation(() => true);
144
- jest.spyOn(sut as any, 'getLatestBlock').mockImplementation(() => true);
145
-
146
- await sut.start();
147
-
148
- expect(sut['lastBlockNumber']).toStrictEqual(509992);
149
-
150
- sut.stop();
151
- });
152
- });
1
+ import { TimeAction } from '../src/actions';
2
+ import { Streamer } from '../src/streamer';
3
+ import { action as contractAction, defineContract } from '../src/contracts/contract';
4
+ import { createMockAdapter } from './helpers/mock-adapter';
5
+
6
+ describe('Streamer', () => {
7
+ let sut: Streamer;
8
+
9
+ beforeEach(async () => {
10
+ sut = new Streamer({
11
+ JSON_ID: 'testing'
12
+ });
13
+
14
+ await sut.registerAdapter(createMockAdapter());
15
+ });
16
+
17
+ afterEach(async () => {
18
+ await sut.stop();
19
+ });
20
+
21
+ describe('Adapters', () => {
22
+ test('Registers adapter and calls the create lifecycle method', async () => {
23
+ const adapter = {
24
+ create: jest.fn().mockResolvedValue(true),
25
+ destroy: jest.fn(),
26
+ loadActions: jest.fn().mockResolvedValue([]),
27
+ loadState: jest.fn().mockResolvedValue(null),
28
+ saveState: jest.fn().mockResolvedValue(true),
29
+ processBlock: jest.fn(),
30
+ processOperation: jest.fn(),
31
+ processTransfer: jest.fn(),
32
+ processCustomJson: jest.fn(),
33
+ find: jest.fn(),
34
+ findOne: jest.fn(),
35
+ insert: jest.fn(),
36
+ replace: jest.fn(),
37
+ addEvent: jest.fn(),
38
+ client: null,
39
+ db: null
40
+ } as any;
41
+
42
+ await sut.registerAdapter(adapter);
43
+
44
+ expect(adapter.create).toHaveBeenCalled();
45
+ });
46
+ });
47
+
48
+ describe('Actions', () => {
49
+ test('Registers a new action', async () => {
50
+ const mockContract = defineContract({
51
+ name: 'testcontract',
52
+ actions: {
53
+ testmethod: contractAction(jest.fn(), { trigger: 'time' })
54
+ }
55
+ });
56
+
57
+ await sut.registerContract(mockContract);
58
+
59
+ const adapter = {
60
+ create: jest.fn().mockResolvedValue(true),
61
+ destroy: jest.fn(),
62
+ loadActions: jest.fn().mockResolvedValue([]),
63
+ loadState: jest.fn().mockResolvedValue(null),
64
+ saveState: jest.fn().mockResolvedValue(true),
65
+ processBlock: jest.fn(),
66
+ processOperation: jest.fn(),
67
+ processTransfer: jest.fn(),
68
+ processCustomJson: jest.fn(),
69
+ find: jest.fn(),
70
+ findOne: jest.fn(),
71
+ insert: jest.fn(),
72
+ replace: jest.fn(),
73
+ client: null,
74
+ db: null
75
+ } as any;
76
+
77
+ await sut.registerAdapter(adapter);
78
+
79
+ const action = new TimeAction('1m', 'testoneminute', 'testcontract', 'testmethod');
80
+
81
+ await sut.registerAction(action);
82
+
83
+ const foundAction = sut['actions'].find(a => a.id === 'testoneminute');
84
+
85
+ expect(foundAction).not.toBeUndefined();
86
+ });
87
+
88
+ test('Does not allow duplicate actions of the same id', async () => {
89
+ const mockContract = defineContract({
90
+ name: 'testcontract',
91
+ actions: {
92
+ testmethod: contractAction(jest.fn(), { trigger: 'time' })
93
+ }
94
+ });
95
+
96
+ await sut.registerContract(mockContract);
97
+
98
+ const adapter = {
99
+ create: jest.fn().mockResolvedValue(true),
100
+ destroy: jest.fn(),
101
+ loadActions: jest.fn().mockResolvedValue([]),
102
+ loadState: jest.fn().mockResolvedValue(null),
103
+ saveState: jest.fn().mockResolvedValue(true),
104
+ processBlock: jest.fn(),
105
+ processOperation: jest.fn(),
106
+ processTransfer: jest.fn(),
107
+ processCustomJson: jest.fn(),
108
+ find: jest.fn(),
109
+ findOne: jest.fn(),
110
+ insert: jest.fn(),
111
+ replace: jest.fn(),
112
+ client: null,
113
+ db: null
114
+ } as any;
115
+
116
+ await sut.registerAdapter(adapter);
117
+
118
+ const action = new TimeAction('1m', 'testoneminute', 'testcontract', 'testmethod');
119
+ const action2 = new TimeAction('1m', 'testoneminute', 'testcontract', 'testmethod');
120
+
121
+ await sut.registerAction(action);
122
+ await sut.registerAction(action2);
123
+
124
+ expect(sut['actions'].length).toStrictEqual(1);
125
+ });
126
+
127
+ test('Registers actions loaded from adapter loadActions call', async () => {
128
+ const mockContract = defineContract({
129
+ name: 'testcontract',
130
+ actions: {
131
+ testmethod: contractAction(jest.fn(), { trigger: 'time' })
132
+ }
133
+ });
134
+
135
+ await sut.registerContract(mockContract);
136
+
137
+ const adapter = {
138
+ create: jest.fn().mockResolvedValue(true),
139
+ destroy: jest.fn(),
140
+ loadActions: jest.fn().mockResolvedValue([{
141
+ timeValue: '1m',
142
+ id: 'testoneminute',
143
+ contractName: 'testcontract',
144
+ contractAction: 'testmethod',
145
+ payload: {},
146
+ date: new Date().toISOString(),
147
+ enabled: true,
148
+ executionCount: 0
149
+ }]),
150
+ loadState: jest.fn().mockResolvedValue(null),
151
+ saveState: jest.fn().mockResolvedValue(true),
152
+ processBlock: jest.fn(),
153
+ processOperation: jest.fn(),
154
+ processTransfer: jest.fn(),
155
+ processCustomJson: jest.fn(),
156
+ find: jest.fn(),
157
+ findOne: jest.fn(),
158
+ insert: jest.fn(),
159
+ replace: jest.fn(),
160
+ client: null,
161
+ db: null
162
+ } as any;
163
+
164
+ await sut.registerAdapter(adapter);
165
+
166
+ const action = new TimeAction('1h', 'testonehour', 'testcontract', 'testmethod');
167
+
168
+ await sut.registerAction(action);
169
+
170
+ const foundAction = sut['actions'].find(a => a.id === 'testoneminute');
171
+
172
+ expect(foundAction).not.toBeUndefined();
173
+ });
174
+ });
175
+
176
+ describe('Contracts', () => {
177
+ test('Should register a new contract', async () => {
178
+ const contract = defineContract({
179
+ name: 'testcontract',
180
+ actions: {
181
+ myMethod: contractAction(jest.fn())
182
+ }
183
+ });
184
+
185
+ await sut.registerContract(contract);
186
+
187
+ expect(sut['contracts'].length).toStrictEqual(1);
188
+ });
189
+
190
+ test('Should register a new contract and call its create hook', async () => {
191
+ const createHook = jest.fn();
192
+ const contract = defineContract({
193
+ name: 'testcontract',
194
+ hooks: {
195
+ create: createHook
196
+ },
197
+ actions: {
198
+ myMethod: contractAction(jest.fn())
199
+ }
200
+ });
201
+
202
+ await sut.registerContract(contract);
203
+
204
+ expect(createHook).toHaveBeenCalled();
205
+ expect(sut['contracts'].length).toStrictEqual(1);
206
+ });
207
+
208
+ test('Should unregister a registered contract', async () => {
209
+ const contract = defineContract({
210
+ name: 'testcontract',
211
+ actions: {
212
+ myMethod: contractAction(jest.fn())
213
+ }
214
+ });
215
+
216
+ await sut.registerContract(contract);
217
+ await sut.unregisterContract('testcontract');
218
+
219
+ expect(sut['contracts'].length).toStrictEqual(0);
220
+ });
221
+
222
+ test('Should unregister a registered contract and call its destroy hook', async () => {
223
+ const destroyHook = jest.fn();
224
+ const contract = defineContract({
225
+ name: 'testcontract',
226
+ hooks: {
227
+ destroy: destroyHook
228
+ },
229
+ actions: {
230
+ myMethod: contractAction(jest.fn())
231
+ }
232
+ });
233
+
234
+ await sut.registerContract(contract);
235
+ await sut.unregisterContract('testcontract');
236
+
237
+ expect(destroyHook).toHaveBeenCalled();
238
+ expect(sut['contracts'].length).toStrictEqual(0);
239
+ });
240
+ });
241
+
242
+ test('Start method should resume from previous block number', async () => {
243
+ // Override config to not have a preset LAST_BLOCK_NUMBER
244
+ sut.setConfig({ LAST_BLOCK_NUMBER: 0 });
245
+
246
+ const adapter = {
247
+ create: jest.fn().mockResolvedValue(true),
248
+ destroy: jest.fn(),
249
+ loadActions: jest.fn().mockResolvedValue([]),
250
+ loadState: jest.fn().mockResolvedValue({ lastBlockNumber: 509992 }),
251
+ saveState: jest.fn().mockResolvedValue(true),
252
+ processBlock: jest.fn(),
253
+ processOperation: jest.fn(),
254
+ processTransfer: jest.fn(),
255
+ processCustomJson: jest.fn(),
256
+ find: jest.fn(),
257
+ findOne: jest.fn(),
258
+ insert: jest.fn(),
259
+ replace: jest.fn(),
260
+ client: null,
261
+ db: null
262
+ } as any;
263
+
264
+ await sut.registerAdapter(adapter);
265
+
266
+ jest.spyOn(sut as any, 'getBlock').mockImplementation(() => true);
267
+ jest.spyOn(sut as any, 'getLatestBlock').mockImplementation(() => true);
268
+
269
+ await sut.start();
270
+
271
+ expect(sut['lastBlockNumber']).toStrictEqual(509992);
272
+
273
+ sut.stop();
274
+ });
275
+
276
+ test('Start method should respect RESUME_FROM_STATE false', async () => {
277
+ sut.setConfig({ LAST_BLOCK_NUMBER: 123, RESUME_FROM_STATE: false });
278
+
279
+ const adapter = {
280
+ create: jest.fn().mockResolvedValue(true),
281
+ destroy: jest.fn(),
282
+ loadActions: jest.fn().mockResolvedValue([]),
283
+ loadState: jest.fn().mockResolvedValue({ lastBlockNumber: 999 }),
284
+ saveState: jest.fn().mockResolvedValue(true),
285
+ processBlock: jest.fn(),
286
+ processOperation: jest.fn(),
287
+ processTransfer: jest.fn(),
288
+ processCustomJson: jest.fn(),
289
+ find: jest.fn(),
290
+ findOne: jest.fn(),
291
+ insert: jest.fn(),
292
+ replace: jest.fn(),
293
+ client: null,
294
+ db: null
295
+ } as any;
296
+
297
+ await sut.registerAdapter(adapter);
298
+
299
+ jest.spyOn(sut as any, 'getBlock').mockImplementation(() => true);
300
+ jest.spyOn(sut as any, 'getLatestBlock').mockImplementation(() => true);
301
+
302
+ await sut.start();
303
+
304
+ expect(sut['lastBlockNumber']).toStrictEqual(123);
305
+
306
+ sut.stop();
307
+ });
308
+
309
+ test('getBlock should process multiple blocks per batch when behind', async () => {
310
+ const adapter = createMockAdapter();
311
+ await sut.registerAdapter(adapter);
312
+
313
+ sut.setConfig({ CATCH_UP_BATCH_SIZE: 3, BLOCK_CHECK_INTERVAL: 1, DEBUG_MODE: false });
314
+ sut['lastBlockNumber'] = 10;
315
+
316
+ const mockBlock = {
317
+ block_id: 'block-id',
318
+ previous: 'prev-id',
319
+ transaction_ids: ['trx-1'],
320
+ timestamp: '2023-01-01T00:00:00',
321
+ transactions: []
322
+ };
323
+
324
+ sut['client'] = {
325
+ database: {
326
+ getDynamicGlobalProperties: jest.fn().mockResolvedValue({
327
+ head_block_number: 20,
328
+ time: '2023-01-01T00:00:00'
329
+ }),
330
+ getBlock: jest.fn().mockResolvedValue(mockBlock)
331
+ }
332
+ } as any;
333
+
334
+ const loadBlockSpy = jest.spyOn(sut as any, 'loadBlock');
335
+
336
+ await (sut as any).getBlock();
337
+ clearTimeout(sut['blockNumberTimeout']);
338
+
339
+ expect(loadBlockSpy).toHaveBeenCalledTimes(3);
340
+ expect(sut['lastBlockNumber']).toStrictEqual(13);
341
+ });
342
+ });