hive-stream 3.0.0 → 3.0.2

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 (107) hide show
  1. package/AGENTS.md +35 -0
  2. package/DOCUMENTATION.md +396 -0
  3. package/README.md +160 -39
  4. package/dist/actions.d.ts +3 -3
  5. package/dist/actions.js +7 -7
  6. package/dist/actions.js.map +1 -1
  7. package/dist/adapters/base.adapter.d.ts +19 -1
  8. package/dist/adapters/base.adapter.js +16 -0
  9. package/dist/adapters/base.adapter.js.map +1 -1
  10. package/dist/adapters/mongodb.adapter.d.ts +5 -11
  11. package/dist/adapters/mongodb.adapter.js +10 -10
  12. package/dist/adapters/mongodb.adapter.js.map +1 -1
  13. package/dist/adapters/postgresql.adapter.d.ts +17 -0
  14. package/dist/adapters/postgresql.adapter.js +99 -8
  15. package/dist/adapters/postgresql.adapter.js.map +1 -1
  16. package/dist/adapters/sqlite.adapter.d.ts +17 -0
  17. package/dist/adapters/sqlite.adapter.js +99 -8
  18. package/dist/adapters/sqlite.adapter.js.map +1 -1
  19. package/dist/api.js +86 -0
  20. package/dist/api.js.map +1 -1
  21. package/dist/config.d.ts +26 -0
  22. package/dist/config.js +76 -4
  23. package/dist/config.js.map +1 -1
  24. package/dist/contracts/coinflip.contract.d.ts +8 -26
  25. package/dist/contracts/coinflip.contract.js +123 -144
  26. package/dist/contracts/coinflip.contract.js.map +1 -1
  27. package/dist/contracts/contract.d.ts +3 -0
  28. package/dist/contracts/contract.js +26 -0
  29. package/dist/contracts/contract.js.map +1 -0
  30. package/dist/contracts/dice.contract.d.ts +9 -36
  31. package/dist/contracts/dice.contract.js +135 -200
  32. package/dist/contracts/dice.contract.js.map +1 -1
  33. package/dist/contracts/exchange.contract.d.ts +11 -0
  34. package/dist/contracts/exchange.contract.js +492 -0
  35. package/dist/contracts/exchange.contract.js.map +1 -0
  36. package/dist/contracts/lotto.contract.d.ts +15 -19
  37. package/dist/contracts/lotto.contract.js +154 -162
  38. package/dist/contracts/lotto.contract.js.map +1 -1
  39. package/dist/contracts/nft.contract.d.ts +4 -0
  40. package/dist/contracts/nft.contract.js +65 -0
  41. package/dist/contracts/nft.contract.js.map +1 -1
  42. package/dist/contracts/poll.contract.d.ts +4 -0
  43. package/dist/contracts/poll.contract.js +105 -0
  44. package/dist/contracts/poll.contract.js.map +1 -0
  45. package/dist/contracts/rps.contract.d.ts +9 -0
  46. package/dist/contracts/rps.contract.js +217 -0
  47. package/dist/contracts/rps.contract.js.map +1 -0
  48. package/dist/contracts/tipjar.contract.d.ts +4 -0
  49. package/dist/contracts/tipjar.contract.js +60 -0
  50. package/dist/contracts/tipjar.contract.js.map +1 -0
  51. package/dist/contracts/token.contract.d.ts +3 -17
  52. package/dist/contracts/token.contract.js +128 -80
  53. package/dist/contracts/token.contract.js.map +1 -1
  54. package/dist/exchanges/coingecko.d.ts +7 -1
  55. package/dist/exchanges/coingecko.js +38 -21
  56. package/dist/exchanges/coingecko.js.map +1 -1
  57. package/dist/exchanges/exchange.d.ts +15 -8
  58. package/dist/exchanges/exchange.js +65 -11
  59. package/dist/exchanges/exchange.js.map +1 -1
  60. package/dist/hive-rates.d.ts +29 -4
  61. package/dist/hive-rates.js +179 -92
  62. package/dist/hive-rates.js.map +1 -1
  63. package/dist/index.d.ts +11 -3
  64. package/dist/index.js +19 -4
  65. package/dist/index.js.map +1 -1
  66. package/dist/metadata.d.ts +63 -0
  67. package/dist/metadata.js +407 -0
  68. package/dist/metadata.js.map +1 -0
  69. package/dist/streamer.d.ts +104 -11
  70. package/dist/streamer.js +415 -143
  71. package/dist/streamer.js.map +1 -1
  72. package/dist/test.js +11 -12
  73. package/dist/test.js.map +1 -1
  74. package/dist/types/hive-stream.d.ts +85 -14
  75. package/dist/types/rates.d.ts +47 -0
  76. package/dist/types/rates.js +29 -0
  77. package/dist/types/rates.js.map +1 -0
  78. package/dist/utils.d.ts +318 -11
  79. package/dist/utils.js +804 -115
  80. package/dist/utils.js.map +1 -1
  81. package/examples/contracts/README.md +8 -0
  82. package/examples/contracts/exchange.ts +38 -0
  83. package/examples/contracts/poll.ts +21 -0
  84. package/examples/contracts/rps.ts +19 -0
  85. package/examples/contracts/tipjar.ts +19 -0
  86. package/package.json +20 -19
  87. package/test-contract-block.md +3 -3
  88. package/tests/actions.spec.ts +7 -7
  89. package/tests/adapters/actions-persistence.spec.ts +4 -4
  90. package/tests/adapters/sqlite.adapter.spec.ts +2 -2
  91. package/tests/config-input.spec.ts +90 -0
  92. package/tests/contracts/coinflip.contract.spec.ts +26 -154
  93. package/tests/contracts/dice.contract.spec.ts +24 -140
  94. package/tests/contracts/exchange.contract.spec.ts +84 -0
  95. package/tests/contracts/lotto.contract.spec.ts +30 -295
  96. package/tests/contracts/token.contract.spec.ts +72 -316
  97. package/tests/exchanges/coingecko.exchange.spec.ts +169 -0
  98. package/tests/exchanges/exchange.base.spec.ts +246 -0
  99. package/tests/helpers/mock-fetch.ts +165 -0
  100. package/tests/hive-chain-features.spec.ts +319 -0
  101. package/tests/hive-rates.spec.ts +443 -0
  102. package/tests/integration/hive-rates.integration.spec.ts +35 -0
  103. package/tests/metadata.spec.ts +63 -0
  104. package/tests/streamer-actions.spec.ts +29 -18
  105. package/tests/streamer.spec.ts +142 -49
  106. package/tests/types/rates.spec.ts +216 -0
  107. package/tests/utils.spec.ts +27 -6
@@ -1,5 +1,6 @@
1
1
  import { TimeAction } from '../src/actions';
2
2
  import { Streamer } from '../src/streamer';
3
+ import { action as contractAction, defineContract } from '../src/contracts/contract';
3
4
  import { createMockAdapter } from './helpers/mock-adapter';
4
5
 
5
6
  describe('Streamer', () => {
@@ -40,17 +41,20 @@ describe('Streamer', () => {
40
41
 
41
42
  await sut.registerAdapter(adapter);
42
43
 
43
- expect(adapter.create).toBeCalled();
44
+ expect(adapter.create).toHaveBeenCalled();
44
45
  });
45
46
  });
46
47
 
47
48
  describe('Actions', () => {
48
49
  test('Registers a new action', async () => {
49
- const mockContract = {
50
- testmethod: jest.fn()
51
- };
50
+ const mockContract = defineContract({
51
+ name: 'testcontract',
52
+ actions: {
53
+ testmethod: contractAction(jest.fn(), { trigger: 'time' })
54
+ }
55
+ });
52
56
 
53
- sut.registerContract('testcontract', mockContract);
57
+ await sut.registerContract(mockContract);
54
58
 
55
59
  const adapter = {
56
60
  create: jest.fn().mockResolvedValue(true),
@@ -70,7 +74,7 @@ describe('Streamer', () => {
70
74
  db: null
71
75
  } as any;
72
76
 
73
- sut.registerAdapter(adapter);
77
+ await sut.registerAdapter(adapter);
74
78
 
75
79
  const action = new TimeAction('1m', 'testoneminute', 'testcontract', 'testmethod');
76
80
 
@@ -82,11 +86,14 @@ describe('Streamer', () => {
82
86
  });
83
87
 
84
88
  test('Does not allow duplicate actions of the same id', async () => {
85
- const mockContract = {
86
- testmethod: jest.fn()
87
- };
89
+ const mockContract = defineContract({
90
+ name: 'testcontract',
91
+ actions: {
92
+ testmethod: contractAction(jest.fn(), { trigger: 'time' })
93
+ }
94
+ });
88
95
 
89
- sut.registerContract('testcontract', mockContract);
96
+ await sut.registerContract(mockContract);
90
97
 
91
98
  const adapter = {
92
99
  create: jest.fn().mockResolvedValue(true),
@@ -106,7 +113,7 @@ describe('Streamer', () => {
106
113
  db: null
107
114
  } as any;
108
115
 
109
- sut.registerAdapter(adapter);
116
+ await sut.registerAdapter(adapter);
110
117
 
111
118
  const action = new TimeAction('1m', 'testoneminute', 'testcontract', 'testmethod');
112
119
  const action2 = new TimeAction('1m', 'testoneminute', 'testcontract', 'testmethod');
@@ -118,11 +125,14 @@ describe('Streamer', () => {
118
125
  });
119
126
 
120
127
  test('Registers actions loaded from adapter loadActions call', async () => {
121
- const mockContract = {
122
- testmethod: jest.fn()
123
- };
128
+ const mockContract = defineContract({
129
+ name: 'testcontract',
130
+ actions: {
131
+ testmethod: contractAction(jest.fn(), { trigger: 'time' })
132
+ }
133
+ });
124
134
 
125
- sut.registerContract('testcontract', mockContract);
135
+ await sut.registerContract(mockContract);
126
136
 
127
137
  const adapter = {
128
138
  create: jest.fn().mockResolvedValue(true),
@@ -131,7 +141,7 @@ describe('Streamer', () => {
131
141
  timeValue: '1m',
132
142
  id: 'testoneminute',
133
143
  contractName: 'testcontract',
134
- contractMethod: 'testmethod',
144
+ contractAction: 'testmethod',
135
145
  payload: {},
136
146
  date: new Date().toISOString(),
137
147
  enabled: true,
@@ -164,51 +174,67 @@ describe('Streamer', () => {
164
174
  });
165
175
 
166
176
  describe('Contracts', () => {
167
- test('Should register a new contract', () => {
168
- const contract = {
169
- myMethod: jest.fn()
170
- };
177
+ test('Should register a new contract', async () => {
178
+ const contract = defineContract({
179
+ name: 'testcontract',
180
+ actions: {
181
+ myMethod: contractAction(jest.fn())
182
+ }
183
+ });
171
184
 
172
- sut.registerContract('testcontract', contract);
185
+ await sut.registerContract(contract);
173
186
 
174
- expect(contract['_instance']).toBeInstanceOf(Streamer);
175
187
  expect(sut['contracts'].length).toStrictEqual(1);
176
188
  });
177
189
 
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);
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();
188
205
  expect(sut['contracts'].length).toStrictEqual(1);
189
206
  });
190
207
 
191
- test('Should unregister a registered contract', () => {
192
- const contract = {
193
- myMethod: jest.fn()
194
- };
208
+ test('Should unregister a registered contract', async () => {
209
+ const contract = defineContract({
210
+ name: 'testcontract',
211
+ actions: {
212
+ myMethod: contractAction(jest.fn())
213
+ }
214
+ });
195
215
 
196
- sut.registerContract('testcontract', contract);
197
- sut.unregisterContract('testcontract');
216
+ await sut.registerContract(contract);
217
+ await sut.unregisterContract('testcontract');
198
218
 
199
219
  expect(sut['contracts'].length).toStrictEqual(0);
200
220
  });
201
221
 
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();
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();
212
238
  expect(sut['contracts'].length).toStrictEqual(0);
213
239
  });
214
240
  });
@@ -246,4 +272,71 @@ describe('Streamer', () => {
246
272
 
247
273
  sut.stop();
248
274
  });
249
- });
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
+ });
@@ -0,0 +1,216 @@
1
+ import {
2
+ RatesError,
3
+ NetworkError,
4
+ ValidationError,
5
+ ExchangeRates,
6
+ HiveRates,
7
+ CryptoRates,
8
+ ExchangeResponse,
9
+ FiatResponse,
10
+ RateConfig,
11
+ CurrencyPair,
12
+ SupportedCrypto,
13
+ SupportedFiat
14
+ } from '../../src/types/rates';
15
+
16
+ describe('Types and Error Classes', () => {
17
+ describe('RatesError', () => {
18
+ it('should create basic RatesError', () => {
19
+ const error = new RatesError('Test message', 'TEST_CODE');
20
+
21
+ expect(error.message).toBe('Test message');
22
+ expect(error.code).toBe('TEST_CODE');
23
+ expect(error.name).toBe('RatesError');
24
+ expect(error.source).toBeUndefined();
25
+ });
26
+
27
+ it('should create RatesError with source', () => {
28
+ const error = new RatesError('Test message', 'TEST_CODE', 'test-source');
29
+
30
+ expect(error.source).toBe('test-source');
31
+ });
32
+
33
+ it('should be instance of Error', () => {
34
+ const error = new RatesError('Test', 'CODE');
35
+ expect(error).toBeInstanceOf(Error);
36
+ expect(error).toBeInstanceOf(RatesError);
37
+ });
38
+ });
39
+
40
+ describe('NetworkError', () => {
41
+ it('should create NetworkError', () => {
42
+ const error = new NetworkError('Network failed');
43
+
44
+ expect(error.message).toBe('Network failed');
45
+ expect(error.code).toBe('NETWORK_ERROR');
46
+ expect(error.name).toBe('NetworkError');
47
+ });
48
+
49
+ it('should create NetworkError with source', () => {
50
+ const error = new NetworkError('Network failed', 'api-endpoint');
51
+
52
+ expect(error.source).toBe('api-endpoint');
53
+ });
54
+
55
+ it('should be instance of RatesError', () => {
56
+ const error = new NetworkError('Test');
57
+ expect(error).toBeInstanceOf(Error);
58
+ expect(error).toBeInstanceOf(RatesError);
59
+ expect(error).toBeInstanceOf(NetworkError);
60
+ });
61
+ });
62
+
63
+ describe('ValidationError', () => {
64
+ it('should create ValidationError', () => {
65
+ const error = new ValidationError('Invalid data');
66
+
67
+ expect(error.message).toBe('Invalid data');
68
+ expect(error.code).toBe('VALIDATION_ERROR');
69
+ expect(error.name).toBe('ValidationError');
70
+ });
71
+
72
+ it('should create ValidationError with source', () => {
73
+ const error = new ValidationError('Invalid data', 'response-parser');
74
+
75
+ expect(error.source).toBe('response-parser');
76
+ });
77
+
78
+ it('should be instance of RatesError', () => {
79
+ const error = new ValidationError('Test');
80
+ expect(error).toBeInstanceOf(Error);
81
+ expect(error).toBeInstanceOf(RatesError);
82
+ expect(error).toBeInstanceOf(ValidationError);
83
+ });
84
+ });
85
+
86
+ describe('Type Definitions', () => {
87
+ it('should accept valid ExchangeRates', () => {
88
+ const rates: ExchangeRates = {
89
+ EUR: 0.85,
90
+ GBP: 0.73,
91
+ JPY: 110
92
+ };
93
+
94
+ expect(typeof rates.EUR).toBe('number');
95
+ expect(typeof rates.GBP).toBe('number');
96
+ expect(typeof rates.JPY).toBe('number');
97
+ });
98
+
99
+ it('should accept valid HiveRates', () => {
100
+ const rates: HiveRates = {
101
+ 'USD_EUR': 0.85,
102
+ 'EUR_HIVE': 0.425,
103
+ 'GBP_HBD': 0.73
104
+ };
105
+
106
+ expect(typeof rates['USD_EUR']).toBe('number');
107
+ expect(typeof rates['EUR_HIVE']).toBe('number');
108
+ expect(typeof rates['GBP_HBD']).toBe('number');
109
+ });
110
+
111
+ it('should accept valid CryptoRates', () => {
112
+ const rates: CryptoRates = {
113
+ usdHive: 0.5,
114
+ usdHbd: 1.0
115
+ };
116
+
117
+ expect(rates.usdHive).toBe(0.5);
118
+ expect(rates.usdHbd).toBe(1.0);
119
+ });
120
+
121
+ it('should accept valid ExchangeResponse', () => {
122
+ const successResponse: ExchangeResponse = {
123
+ success: true,
124
+ rates: { usdHive: 0.5, usdHbd: 1.0 }
125
+ };
126
+
127
+ const errorResponse: ExchangeResponse = {
128
+ success: false,
129
+ error: 'API failed'
130
+ };
131
+
132
+ expect(successResponse.success).toBe(true);
133
+ expect(successResponse.rates).toBeDefined();
134
+ expect(errorResponse.success).toBe(false);
135
+ expect(errorResponse.error).toBe('API failed');
136
+ });
137
+
138
+ it('should accept valid FiatResponse', () => {
139
+ const successResponse: FiatResponse = {
140
+ success: true,
141
+ rates: { EUR: 0.85, GBP: 0.73 }
142
+ };
143
+
144
+ const errorResponse: FiatResponse = {
145
+ success: false,
146
+ error: 'API failed'
147
+ };
148
+
149
+ expect(successResponse.success).toBe(true);
150
+ expect(successResponse.rates).toBeDefined();
151
+ expect(errorResponse.success).toBe(false);
152
+ expect(errorResponse.error).toBe('API failed');
153
+ });
154
+
155
+ it('should accept valid RateConfig', () => {
156
+ const config: RateConfig = {
157
+ cacheDuration: 3600000,
158
+ maxRetries: 3,
159
+ retryDelay: 1000,
160
+ timeout: 10000
161
+ };
162
+
163
+ expect(config.cacheDuration).toBe(3600000);
164
+ expect(config.maxRetries).toBe(3);
165
+ expect(config.retryDelay).toBe(1000);
166
+ expect(config.timeout).toBe(10000);
167
+ });
168
+
169
+ it('should accept partial RateConfig', () => {
170
+ const config: RateConfig = {
171
+ cacheDuration: 5000
172
+ };
173
+
174
+ expect(config.cacheDuration).toBe(5000);
175
+ expect(config.maxRetries).toBeUndefined();
176
+ });
177
+
178
+ it('should work with CurrencyPair type', () => {
179
+ const pair1: CurrencyPair = 'USD_EUR';
180
+ const pair2: CurrencyPair = 'EUR_HIVE';
181
+ const pair3: CurrencyPair = 'GBP_HBD';
182
+
183
+ expect(pair1).toBe('USD_EUR');
184
+ expect(pair2).toBe('EUR_HIVE');
185
+ expect(pair3).toBe('GBP_HBD');
186
+ });
187
+
188
+ it('should work with SupportedCrypto type', () => {
189
+ const crypto1: SupportedCrypto = 'HIVE';
190
+ const crypto2: SupportedCrypto = 'HBD';
191
+
192
+ expect(crypto1).toBe('HIVE');
193
+ expect(crypto2).toBe('HBD');
194
+ });
195
+
196
+ it('should work with SupportedFiat type', () => {
197
+ const fiat1: SupportedFiat = 'USD';
198
+ const fiat2: SupportedFiat = 'EUR';
199
+ const fiat3: SupportedFiat = 'GBP';
200
+ const fiat4: SupportedFiat = 'JPY';
201
+ const fiat5: SupportedFiat = 'CAD';
202
+ const fiat6: SupportedFiat = 'AUD';
203
+ const fiat7: SupportedFiat = 'CHF';
204
+ const fiat8: SupportedFiat = 'CNY';
205
+
206
+ expect(fiat1).toBe('USD');
207
+ expect(fiat2).toBe('EUR');
208
+ expect(fiat3).toBe('GBP');
209
+ expect(fiat4).toBe('JPY');
210
+ expect(fiat5).toBe('CAD');
211
+ expect(fiat6).toBe('AUD');
212
+ expect(fiat7).toBe('CHF');
213
+ expect(fiat8).toBe('CNY');
214
+ });
215
+ });
216
+ });
@@ -16,7 +16,7 @@ describe('Utils', () => {
16
16
  });
17
17
 
18
18
  test('Invalid numeric values passed', () => {
19
- expect(Utils.roundPrecision('dasd', 3)).toBeNaN();
19
+ expect(Utils.roundPrecision('dasd' as any, 3)).toBeNaN();
20
20
  });
21
21
  });
22
22
 
@@ -76,17 +76,38 @@ describe('Utils', () => {
76
76
 
77
77
  const value = await Utils.convertHiveAmount(amount, fiatSymbol, hiveSymbol);
78
78
 
79
- expect(fetch).toBeCalledWith('https://api.coingecko.com/api/v3/simple/price?ids=hive,hive_dollar&vs_currencies=usd');
80
- expect(fetch).toBeCalledWith('https://cdn.jsdelivr.net/npm/@fawazahmed0/currency-api@latest/v1/currencies/usd.json');
79
+ expect(fetch).toHaveBeenNthCalledWith(
80
+ 1,
81
+ 'https://api.coingecko.com/api/v3/simple/price?ids=hive,hive_dollar&vs_currencies=usd',
82
+ expect.objectContaining({
83
+ headers: expect.objectContaining({
84
+ 'User-Agent': 'hive-stream/3.0.0',
85
+ 'Accept': 'application/json'
86
+ })
87
+ })
88
+ );
89
+ expect(fetch).toHaveBeenNthCalledWith(
90
+ 2,
91
+ 'https://cdn.jsdelivr.net/npm/@fawazahmed0/currency-api@latest/v1/currencies/usd.json',
92
+ expect.objectContaining({
93
+ headers: expect.objectContaining({
94
+ 'User-Agent': 'hive-stream/3.0.0',
95
+ 'Accept': 'application/json',
96
+ 'Cache-Control': 'no-cache'
97
+ })
98
+ })
99
+ );
81
100
 
82
101
  expect(value).toStrictEqual(Number((amount / 0.229951).toFixed(3))); // amount / HIVE price from CoinGecko (fiat to HIVE conversion)
83
102
  });
84
103
  });
85
104
 
86
105
  describe('Get transfer URL', () => {
87
- test('Gets a transfer URL string', () => {
88
- expect(Utils.getTransferUrl('beggars', 'TEST123', '10.000 HIVE', 'http://localhost:5001')).toStrictEqual(`https://hivesigner.com/sign/transfer?to=beggars&memo=TEST123&amount=10.000 HIVE&redirect_uri=http://localhost:5001`);
106
+ test('Gets a transfer URL string with proper URL encoding', () => {
107
+ const result = Utils.getTransferUrl('beggars', 'TEST123', '10.000 HIVE', 'http://localhost:5001');
108
+ const expected = 'https://hivesigner.com/sign/transfer?to=beggars&memo=TEST123&amount=10.000%20HIVE&redirect_uri=http%3A%2F%2Flocalhost%3A5001';
109
+ expect(result).toStrictEqual(expected);
89
110
  });
90
111
  });
91
112
 
92
- });
113
+ });