hive-stream 2.0.5 → 3.0.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.
Files changed (89) hide show
  1. package/.claude/settings.local.json +12 -0
  2. package/.env.example +2 -2
  3. package/.travis.yml +11 -11
  4. package/CHANGELOG.md +166 -0
  5. package/CLAUDE.md +75 -0
  6. package/LICENSE +21 -21
  7. package/README.md +338 -238
  8. package/dist/actions.d.ts +41 -10
  9. package/dist/actions.js +126 -23
  10. package/dist/actions.js.map +1 -1
  11. package/dist/adapters/base.adapter.d.ts +25 -25
  12. package/dist/adapters/base.adapter.js +63 -49
  13. package/dist/adapters/base.adapter.js.map +1 -1
  14. package/dist/adapters/mongodb.adapter.d.ts +50 -37
  15. package/dist/adapters/mongodb.adapter.js +363 -158
  16. package/dist/adapters/mongodb.adapter.js.map +1 -1
  17. package/dist/adapters/postgresql.adapter.d.ts +49 -0
  18. package/dist/adapters/postgresql.adapter.js +507 -0
  19. package/dist/adapters/postgresql.adapter.js.map +1 -0
  20. package/dist/adapters/sqlite.adapter.d.ts +40 -41
  21. package/dist/adapters/sqlite.adapter.js +470 -397
  22. package/dist/adapters/sqlite.adapter.js.map +1 -1
  23. package/dist/api.d.ts +6 -6
  24. package/dist/api.js +95 -55
  25. package/dist/api.js.map +1 -1
  26. package/dist/config.d.ts +16 -16
  27. package/dist/config.js +18 -18
  28. package/dist/config.js.map +1 -1
  29. package/dist/contracts/coinflip.contract.d.ts +27 -14
  30. package/dist/contracts/coinflip.contract.js +253 -94
  31. package/dist/contracts/coinflip.contract.js.map +1 -1
  32. package/dist/contracts/dice.contract.d.ts +37 -29
  33. package/dist/contracts/dice.contract.js +282 -155
  34. package/dist/contracts/dice.contract.js.map +1 -1
  35. package/dist/contracts/lotto.contract.d.ts +20 -20
  36. package/dist/contracts/lotto.contract.js +246 -246
  37. package/dist/contracts/nft.contract.d.ts +24 -0
  38. package/dist/contracts/nft.contract.js +533 -0
  39. package/dist/contracts/nft.contract.js.map +1 -0
  40. package/dist/contracts/token.contract.d.ts +18 -0
  41. package/dist/contracts/token.contract.js +263 -0
  42. package/dist/contracts/token.contract.js.map +1 -0
  43. package/dist/exchanges/bittrex.d.ts +6 -6
  44. package/dist/exchanges/bittrex.js +34 -34
  45. package/dist/exchanges/coingecko.d.ts +5 -0
  46. package/dist/exchanges/coingecko.js +40 -0
  47. package/dist/exchanges/coingecko.js.map +1 -0
  48. package/dist/exchanges/exchange.d.ts +9 -9
  49. package/dist/exchanges/exchange.js +26 -26
  50. package/dist/hive-rates.d.ts +9 -9
  51. package/dist/hive-rates.js +121 -75
  52. package/dist/hive-rates.js.map +1 -1
  53. package/dist/index.d.ts +12 -11
  54. package/dist/index.js +33 -32
  55. package/dist/index.js.map +1 -1
  56. package/dist/streamer.d.ts +140 -93
  57. package/dist/streamer.js +793 -545
  58. package/dist/streamer.js.map +1 -1
  59. package/dist/test.d.ts +1 -1
  60. package/dist/test.js +25 -25
  61. package/dist/test.js.map +1 -1
  62. package/dist/types/hive-stream.d.ts +35 -6
  63. package/dist/types/hive-stream.js +2 -2
  64. package/dist/utils.d.ts +27 -27
  65. package/dist/utils.js +271 -261
  66. package/dist/utils.js.map +1 -1
  67. package/ecosystem.config.js +17 -17
  68. package/jest.config.js +8 -8
  69. package/package.json +53 -48
  70. package/test-contract-block.md +18 -18
  71. package/tests/actions.spec.ts +252 -0
  72. package/tests/adapters/actions-persistence.spec.ts +144 -0
  73. package/tests/adapters/postgresql.adapter.spec.ts +127 -0
  74. package/tests/adapters/sqlite.adapter.spec.ts +180 -42
  75. package/tests/contracts/coinflip.contract.spec.ts +221 -131
  76. package/tests/contracts/dice.contract.spec.ts +202 -159
  77. package/tests/contracts/entrants.json +728 -728
  78. package/tests/contracts/lotto.contract.spec.ts +323 -323
  79. package/tests/contracts/nft.contract.spec.ts +948 -0
  80. package/tests/contracts/token.contract.spec.ts +334 -0
  81. package/tests/helpers/mock-adapter.ts +214 -0
  82. package/tests/setup.ts +29 -18
  83. package/tests/streamer-actions.spec.ts +263 -0
  84. package/tests/streamer.spec.ts +248 -151
  85. package/tests/utils.spec.ts +91 -94
  86. package/tsconfig.build.json +3 -22
  87. package/tslint.json +20 -20
  88. package/wallaby.js +26 -26
  89. package/.env +0 -3
@@ -0,0 +1,263 @@
1
+ import { TimeAction } from '../src/actions';
2
+ import { Streamer } from '../src/streamer';
3
+
4
+ describe('Streamer Time-based Actions', () => {
5
+ let streamer: Streamer;
6
+ let mockAdapter: any;
7
+ let mockContract: any;
8
+
9
+ beforeEach(async () => {
10
+ mockAdapter = {
11
+ create: jest.fn().mockResolvedValue(true),
12
+ destroy: jest.fn().mockResolvedValue(true),
13
+ loadActions: jest.fn().mockResolvedValue([]),
14
+ loadState: jest.fn().mockResolvedValue({ lastBlockNumber: 0, actions: [] }),
15
+ saveState: jest.fn().mockResolvedValue(true),
16
+ processBlock: jest.fn().mockResolvedValue(true),
17
+ processOperation: jest.fn().mockResolvedValue(true),
18
+ processTransfer: jest.fn().mockResolvedValue(true),
19
+ processCustomJson: jest.fn().mockResolvedValue(true),
20
+ find: jest.fn().mockResolvedValue([]),
21
+ findOne: jest.fn().mockResolvedValue(null),
22
+ insert: jest.fn().mockResolvedValue(true),
23
+ replace: jest.fn().mockResolvedValue(true)
24
+ };
25
+
26
+ mockContract = {
27
+ testMethod: jest.fn(),
28
+ asyncTestMethod: jest.fn().mockResolvedValue(true),
29
+ create: jest.fn(),
30
+ destroy: jest.fn()
31
+ };
32
+
33
+ streamer = new Streamer({
34
+ JSON_ID: 'testing',
35
+ DEBUG_MODE: false
36
+ });
37
+
38
+ await streamer.registerAdapter(mockAdapter);
39
+ streamer.registerContract('testcontract', mockContract);
40
+ });
41
+
42
+ afterEach(async () => {
43
+ await streamer.stop();
44
+ });
45
+
46
+ describe('Action Registration', () => {
47
+ test('Should register a new action successfully', async () => {
48
+ const action = new TimeAction('1m', 'test-action', 'testcontract', 'testMethod');
49
+
50
+ await streamer.registerAction(action);
51
+
52
+ const actions = streamer.getActions();
53
+ expect(actions).toHaveLength(1);
54
+ expect(actions[0].id).toBe('test-action');
55
+ expect(mockAdapter.saveState).toHaveBeenCalled();
56
+ });
57
+
58
+ test('Should not register duplicate action IDs', async () => {
59
+ const action1 = new TimeAction('1m', 'test-action', 'testcontract', 'testMethod');
60
+ const action2 = new TimeAction('5m', 'test-action', 'testcontract', 'testMethod');
61
+
62
+ await streamer.registerAction(action1);
63
+ await streamer.registerAction(action2);
64
+
65
+ const actions = streamer.getActions();
66
+ expect(actions).toHaveLength(1);
67
+ expect(actions[0].timeValue).toBe('1m'); // First one should remain
68
+ });
69
+
70
+ test('Should throw error when registering action for non-existent contract', async () => {
71
+ const action = new TimeAction('1m', 'test-action', 'nonexistent', 'testMethod');
72
+
73
+ await expect(streamer.registerAction(action)).rejects.toThrow(
74
+ 'Contract \'nonexistent\' not found for action \'test-action\''
75
+ );
76
+ });
77
+
78
+ test('Should throw error when registering action for non-existent method', async () => {
79
+ const action = new TimeAction('1m', 'test-action', 'testcontract', 'nonexistentMethod');
80
+
81
+ await expect(streamer.registerAction(action)).rejects.toThrow(
82
+ 'Method \'nonexistentMethod\' not found in contract \'testcontract\' for action \'test-action\''
83
+ );
84
+ });
85
+ });
86
+
87
+ describe('Action Management', () => {
88
+ beforeEach(async () => {
89
+ const action1 = new TimeAction('1m', 'action1', 'testcontract', 'testMethod');
90
+ const action2 = new TimeAction('5m', 'action2', 'testcontract', 'testMethod');
91
+ await streamer.registerAction(action1);
92
+ await streamer.registerAction(action2);
93
+ });
94
+
95
+ test('Should remove action by ID', async () => {
96
+ const result = await streamer.removeAction('action1');
97
+
98
+ expect(result).toBe(true);
99
+ expect(streamer.getActions()).toHaveLength(1);
100
+ expect(streamer.getAction('action1')).toBeUndefined();
101
+ expect(mockAdapter.saveState).toHaveBeenCalled();
102
+ });
103
+
104
+ test('Should return false when removing non-existent action', async () => {
105
+ const result = await streamer.removeAction('nonexistent');
106
+
107
+ expect(result).toBe(false);
108
+ expect(streamer.getActions()).toHaveLength(2);
109
+ });
110
+
111
+ test('Should get action by ID', () => {
112
+ const action = streamer.getAction('action1');
113
+
114
+ expect(action).toBeDefined();
115
+ expect(action?.id).toBe('action1');
116
+ });
117
+
118
+ test('Should return undefined for non-existent action', () => {
119
+ const action = streamer.getAction('nonexistent');
120
+
121
+ expect(action).toBeUndefined();
122
+ });
123
+
124
+ test('Should enable and disable actions', async () => {
125
+ const action = streamer.getAction('action1');
126
+ expect(action?.enabled).toBe(true);
127
+
128
+ const result1 = await streamer.setActionEnabled('action1', false);
129
+ expect(result1).toBe(true);
130
+ expect(action?.enabled).toBe(false);
131
+
132
+ const result2 = await streamer.setActionEnabled('action1', true);
133
+ expect(result2).toBe(true);
134
+ expect(action?.enabled).toBe(true);
135
+ });
136
+
137
+ test('Should reset action date', async () => {
138
+ const action = streamer.getAction('action1');
139
+ const originalDate = action?.date;
140
+
141
+ // Wait a bit to ensure time difference
142
+ await new Promise(resolve => setTimeout(resolve, 10));
143
+
144
+ const result = await streamer.resetAction('action1');
145
+
146
+ expect(result).toBe(true);
147
+ expect(action?.date.getTime()).toBeGreaterThan(originalDate?.getTime() || 0);
148
+ expect(mockAdapter.saveState).toHaveBeenCalled();
149
+ });
150
+ });
151
+
152
+ describe('Action Execution', () => {
153
+ let testAction: TimeAction;
154
+
155
+ beforeEach(async () => {
156
+ testAction = new TimeAction('1m', 'test-action', 'testcontract', 'testMethod', { testData: 'value' });
157
+ await streamer.registerAction(testAction);
158
+
159
+ // Mock the blockchain time to be in the future to trigger execution
160
+ streamer['latestBlockchainTime'] = new Date(Date.now() + 120000); // 2 minutes in future
161
+ });
162
+
163
+ test('Should execute action when time threshold is met', async () => {
164
+ // Set action date to past to trigger execution
165
+ testAction.date = new Date(Date.now() - 120000); // 2 minutes ago
166
+
167
+ await streamer['processActions']();
168
+
169
+ expect(mockContract.testMethod).toHaveBeenCalledWith({ testData: 'value' });
170
+ expect(testAction.executionCount).toBe(1);
171
+ expect(testAction.lastExecution).toBeInstanceOf(Date);
172
+ });
173
+
174
+ test('Should not execute action when time threshold is not met', async () => {
175
+ // Reset the action date to be very recent to ensure it doesn't execute
176
+ testAction.date = new Date(Date.now() - 10000); // Only 10 seconds ago (less than 1 minute)
177
+
178
+ // Also ensure blockchain time is current, not in the future
179
+ streamer['latestBlockchainTime'] = new Date();
180
+
181
+ await streamer['processActions']();
182
+
183
+ expect(mockContract.testMethod).not.toHaveBeenCalled();
184
+ expect(testAction.executionCount).toBe(0);
185
+ });
186
+
187
+ test('Should not execute disabled actions', async () => {
188
+ testAction.date = new Date(Date.now() - 120000); // 2 minutes ago
189
+ testAction.disable();
190
+
191
+ await streamer['processActions']();
192
+
193
+ expect(mockContract.testMethod).not.toHaveBeenCalled();
194
+ });
195
+
196
+ test('Should not execute actions that have reached max executions', async () => {
197
+ testAction.date = new Date(Date.now() - 120000); // 2 minutes ago
198
+ testAction.maxExecutions = 1;
199
+ testAction.executionCount = 1;
200
+
201
+ await streamer['processActions']();
202
+
203
+ expect(mockContract.testMethod).not.toHaveBeenCalled();
204
+ });
205
+
206
+ test('Should handle contract method errors gracefully', async () => {
207
+ mockContract.testMethod.mockImplementation(() => {
208
+ throw new Error('Contract method error');
209
+ });
210
+
211
+ testAction.date = new Date(Date.now() - 120000); // 2 minutes ago
212
+
213
+ // Should not throw, but log error
214
+ await expect(streamer['processActions']()).resolves.toBeUndefined();
215
+
216
+ expect(mockContract.testMethod).toHaveBeenCalled();
217
+ // Action should not increment execution count on error
218
+ expect(testAction.executionCount).toBe(0);
219
+ });
220
+
221
+ test('Should clean up completed actions', async () => {
222
+ testAction.maxExecutions = 1;
223
+ testAction.date = new Date(Date.now() - 120000); // 2 minutes ago
224
+
225
+ await streamer['processActions']();
226
+
227
+ expect(mockContract.testMethod).toHaveBeenCalled();
228
+ expect(streamer.getActions()).toHaveLength(0); // Action should be removed
229
+ });
230
+ });
231
+
232
+ describe('Action Frequencies', () => {
233
+ const testCases = [
234
+ { timeValue: '3s', expectedSeconds: 3 },
235
+ { timeValue: '30s', expectedSeconds: 30 },
236
+ { timeValue: '1m', expectedSeconds: 60 },
237
+ { timeValue: 'minute', expectedSeconds: 60 },
238
+ { timeValue: '15m', expectedSeconds: 900 },
239
+ { timeValue: '1h', expectedSeconds: 3600 },
240
+ { timeValue: 'hourly', expectedSeconds: 3600 },
241
+ { timeValue: '24h', expectedSeconds: 86400 },
242
+ { timeValue: 'daily', expectedSeconds: 86400 },
243
+ { timeValue: 'week', expectedSeconds: 604800 }
244
+ ];
245
+
246
+ testCases.forEach(({ timeValue, expectedSeconds }) => {
247
+ test(`Should execute ${timeValue} action after ${expectedSeconds} seconds`, async () => {
248
+ const action = new TimeAction(timeValue, `test-${timeValue}`, 'testcontract', 'testMethod');
249
+ action.date = new Date(Date.now() - (expectedSeconds * 1000 + 1000)); // Past the threshold
250
+
251
+ await streamer.registerAction(action);
252
+
253
+ // Mock blockchain time
254
+ streamer['latestBlockchainTime'] = new Date();
255
+
256
+ await streamer['processActions']();
257
+
258
+ expect(mockContract.testMethod).toHaveBeenCalled();
259
+ expect(action.executionCount).toBe(1);
260
+ });
261
+ });
262
+ });
263
+ });
@@ -1,152 +1,249 @@
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
- });
1
+ import { TimeAction } from '../src/actions';
2
+ import { Streamer } from '../src/streamer';
3
+ import { createMockAdapter } from './helpers/mock-adapter';
4
+
5
+ describe('Streamer', () => {
6
+ let sut: Streamer;
7
+
8
+ beforeEach(async () => {
9
+ sut = new Streamer({
10
+ JSON_ID: 'testing'
11
+ });
12
+
13
+ await sut.registerAdapter(createMockAdapter());
14
+ });
15
+
16
+ afterEach(async () => {
17
+ await sut.stop();
18
+ });
19
+
20
+ describe('Adapters', () => {
21
+ test('Registers adapter and calls the create lifecycle method', async () => {
22
+ const adapter = {
23
+ create: jest.fn().mockResolvedValue(true),
24
+ destroy: jest.fn(),
25
+ loadActions: jest.fn().mockResolvedValue([]),
26
+ loadState: jest.fn().mockResolvedValue(null),
27
+ saveState: jest.fn().mockResolvedValue(true),
28
+ processBlock: jest.fn(),
29
+ processOperation: jest.fn(),
30
+ processTransfer: jest.fn(),
31
+ processCustomJson: jest.fn(),
32
+ find: jest.fn(),
33
+ findOne: jest.fn(),
34
+ insert: jest.fn(),
35
+ replace: jest.fn(),
36
+ addEvent: jest.fn(),
37
+ client: null,
38
+ db: null
39
+ } as any;
40
+
41
+ await sut.registerAdapter(adapter);
42
+
43
+ expect(adapter.create).toBeCalled();
44
+ });
45
+ });
46
+
47
+ describe('Actions', () => {
48
+ test('Registers a new action', async () => {
49
+ const mockContract = {
50
+ testmethod: jest.fn()
51
+ };
52
+
53
+ sut.registerContract('testcontract', mockContract);
54
+
55
+ const adapter = {
56
+ create: jest.fn().mockResolvedValue(true),
57
+ destroy: jest.fn(),
58
+ loadActions: jest.fn().mockResolvedValue([]),
59
+ loadState: jest.fn().mockResolvedValue(null),
60
+ saveState: jest.fn().mockResolvedValue(true),
61
+ processBlock: jest.fn(),
62
+ processOperation: jest.fn(),
63
+ processTransfer: jest.fn(),
64
+ processCustomJson: jest.fn(),
65
+ find: jest.fn(),
66
+ findOne: jest.fn(),
67
+ insert: jest.fn(),
68
+ replace: jest.fn(),
69
+ client: null,
70
+ db: null
71
+ } as any;
72
+
73
+ sut.registerAdapter(adapter);
74
+
75
+ const action = new TimeAction('1m', 'testoneminute', 'testcontract', 'testmethod');
76
+
77
+ await sut.registerAction(action);
78
+
79
+ const foundAction = sut['actions'].find(a => a.id === 'testoneminute');
80
+
81
+ expect(foundAction).not.toBeUndefined();
82
+ });
83
+
84
+ test('Does not allow duplicate actions of the same id', async () => {
85
+ const mockContract = {
86
+ testmethod: jest.fn()
87
+ };
88
+
89
+ sut.registerContract('testcontract', mockContract);
90
+
91
+ const adapter = {
92
+ create: jest.fn().mockResolvedValue(true),
93
+ destroy: jest.fn(),
94
+ loadActions: jest.fn().mockResolvedValue([]),
95
+ loadState: jest.fn().mockResolvedValue(null),
96
+ saveState: jest.fn().mockResolvedValue(true),
97
+ processBlock: jest.fn(),
98
+ processOperation: jest.fn(),
99
+ processTransfer: jest.fn(),
100
+ processCustomJson: jest.fn(),
101
+ find: jest.fn(),
102
+ findOne: jest.fn(),
103
+ insert: jest.fn(),
104
+ replace: jest.fn(),
105
+ client: null,
106
+ db: null
107
+ } as any;
108
+
109
+ sut.registerAdapter(adapter);
110
+
111
+ const action = new TimeAction('1m', 'testoneminute', 'testcontract', 'testmethod');
112
+ const action2 = new TimeAction('1m', 'testoneminute', 'testcontract', 'testmethod');
113
+
114
+ await sut.registerAction(action);
115
+ await sut.registerAction(action2);
116
+
117
+ expect(sut['actions'].length).toStrictEqual(1);
118
+ });
119
+
120
+ test('Registers actions loaded from adapter loadActions call', async () => {
121
+ const mockContract = {
122
+ testmethod: jest.fn()
123
+ };
124
+
125
+ sut.registerContract('testcontract', mockContract);
126
+
127
+ const adapter = {
128
+ create: jest.fn().mockResolvedValue(true),
129
+ destroy: jest.fn(),
130
+ loadActions: jest.fn().mockResolvedValue([{
131
+ timeValue: '1m',
132
+ id: 'testoneminute',
133
+ contractName: 'testcontract',
134
+ contractMethod: 'testmethod',
135
+ payload: {},
136
+ date: new Date().toISOString(),
137
+ enabled: true,
138
+ executionCount: 0
139
+ }]),
140
+ loadState: jest.fn().mockResolvedValue(null),
141
+ saveState: jest.fn().mockResolvedValue(true),
142
+ processBlock: jest.fn(),
143
+ processOperation: jest.fn(),
144
+ processTransfer: jest.fn(),
145
+ processCustomJson: jest.fn(),
146
+ find: jest.fn(),
147
+ findOne: jest.fn(),
148
+ insert: jest.fn(),
149
+ replace: jest.fn(),
150
+ client: null,
151
+ db: null
152
+ } as any;
153
+
154
+ await sut.registerAdapter(adapter);
155
+
156
+ const action = new TimeAction('1h', 'testonehour', 'testcontract', 'testmethod');
157
+
158
+ await sut.registerAction(action);
159
+
160
+ const foundAction = sut['actions'].find(a => a.id === 'testoneminute');
161
+
162
+ expect(foundAction).not.toBeUndefined();
163
+ });
164
+ });
165
+
166
+ describe('Contracts', () => {
167
+ test('Should register a new contract', () => {
168
+ const contract = {
169
+ myMethod: jest.fn()
170
+ };
171
+
172
+ sut.registerContract('testcontract', contract);
173
+
174
+ expect(contract['_instance']).toBeInstanceOf(Streamer);
175
+ expect(sut['contracts'].length).toStrictEqual(1);
176
+ });
177
+
178
+ test('Should register a new contract and call its create method', () => {
179
+ const contract = {
180
+ create: jest.fn(),
181
+ myMethod: jest.fn()
182
+ };
183
+
184
+ sut.registerContract('testcontract', contract);
185
+
186
+ expect(contract.create).toBeCalled();
187
+ expect(contract['_instance']).toBeInstanceOf(Streamer);
188
+ expect(sut['contracts'].length).toStrictEqual(1);
189
+ });
190
+
191
+ test('Should unregister a registered contract', () => {
192
+ const contract = {
193
+ myMethod: jest.fn()
194
+ };
195
+
196
+ sut.registerContract('testcontract', contract);
197
+ sut.unregisterContract('testcontract');
198
+
199
+ expect(sut['contracts'].length).toStrictEqual(0);
200
+ });
201
+
202
+ test('Should unregister a registered contract and call its destroy method', () => {
203
+ const contract = {
204
+ destroy: jest.fn(),
205
+ myMethod: jest.fn()
206
+ };
207
+
208
+ sut.registerContract('testcontract', contract);
209
+ sut.unregisterContract('testcontract');
210
+
211
+ expect(contract.destroy).toBeCalled();
212
+ expect(sut['contracts'].length).toStrictEqual(0);
213
+ });
214
+ });
215
+
216
+ test('Start method should resume from previous block number', async () => {
217
+ // Override config to not have a preset LAST_BLOCK_NUMBER
218
+ sut.setConfig({ LAST_BLOCK_NUMBER: 0 });
219
+
220
+ const adapter = {
221
+ create: jest.fn().mockResolvedValue(true),
222
+ destroy: jest.fn(),
223
+ loadActions: jest.fn().mockResolvedValue([]),
224
+ loadState: jest.fn().mockResolvedValue({ lastBlockNumber: 509992 }),
225
+ saveState: jest.fn().mockResolvedValue(true),
226
+ processBlock: jest.fn(),
227
+ processOperation: jest.fn(),
228
+ processTransfer: jest.fn(),
229
+ processCustomJson: jest.fn(),
230
+ find: jest.fn(),
231
+ findOne: jest.fn(),
232
+ insert: jest.fn(),
233
+ replace: jest.fn(),
234
+ client: null,
235
+ db: null
236
+ } as any;
237
+
238
+ await sut.registerAdapter(adapter);
239
+
240
+ jest.spyOn(sut as any, 'getBlock').mockImplementation(() => true);
241
+ jest.spyOn(sut as any, 'getLatestBlock').mockImplementation(() => true);
242
+
243
+ await sut.start();
244
+
245
+ expect(sut['lastBlockNumber']).toStrictEqual(509992);
246
+
247
+ sut.stop();
248
+ });
152
249
  });