hive-stream 3.0.2 → 3.0.4
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/DOCUMENTATION.md +50 -2
- package/README.md +282 -4
- package/dist/adapters/base.adapter.d.ts +5 -0
- package/dist/adapters/base.adapter.js +9 -0
- package/dist/adapters/base.adapter.js.map +1 -1
- package/dist/adapters/mongodb.adapter.d.ts +6 -6
- package/dist/adapters/mongodb.adapter.js +36 -21
- package/dist/adapters/mongodb.adapter.js.map +1 -1
- package/dist/adapters/postgresql.adapter.d.ts +7 -0
- package/dist/adapters/postgresql.adapter.js +46 -19
- package/dist/adapters/postgresql.adapter.js.map +1 -1
- package/dist/adapters/sqlite.adapter.d.ts +4 -0
- package/dist/adapters/sqlite.adapter.js +10 -0
- package/dist/adapters/sqlite.adapter.js.map +1 -1
- package/dist/api.d.ts +13 -3
- package/dist/api.js +96 -62
- package/dist/api.js.map +1 -1
- package/dist/builders.d.ts +176 -0
- package/dist/builders.js +727 -0
- package/dist/builders.js.map +1 -0
- package/dist/config.d.ts +16 -1
- package/dist/config.js +95 -3
- package/dist/config.js.map +1 -1
- package/dist/contracts/auctionhouse.contract.d.ts +4 -0
- package/dist/contracts/auctionhouse.contract.js +234 -0
- package/dist/contracts/auctionhouse.contract.js.map +1 -0
- package/dist/contracts/booking.contract.d.ts +4 -0
- package/dist/contracts/booking.contract.js +225 -0
- package/dist/contracts/booking.contract.js.map +1 -0
- package/dist/contracts/bountyboard.contract.d.ts +4 -0
- package/dist/contracts/bountyboard.contract.js +233 -0
- package/dist/contracts/bountyboard.contract.js.map +1 -0
- package/dist/contracts/bundlemarketplace.contract.d.ts +4 -0
- package/dist/contracts/bundlemarketplace.contract.js +195 -0
- package/dist/contracts/bundlemarketplace.contract.js.map +1 -0
- package/dist/contracts/charitymatch.contract.d.ts +4 -0
- package/dist/contracts/charitymatch.contract.js +172 -0
- package/dist/contracts/charitymatch.contract.js.map +1 -0
- package/dist/contracts/coinflip.contract.js +25 -22
- package/dist/contracts/coinflip.contract.js.map +1 -1
- package/dist/contracts/crowdfund.contract.d.ts +4 -0
- package/dist/contracts/crowdfund.contract.js +290 -0
- package/dist/contracts/crowdfund.contract.js.map +1 -0
- package/dist/contracts/dcabot.contract.d.ts +4 -0
- package/dist/contracts/dcabot.contract.js +217 -0
- package/dist/contracts/dcabot.contract.js.map +1 -0
- package/dist/contracts/dice.contract.js +25 -22
- package/dist/contracts/dice.contract.js.map +1 -1
- package/dist/contracts/domainregistry.contract.d.ts +4 -0
- package/dist/contracts/domainregistry.contract.js +232 -0
- package/dist/contracts/domainregistry.contract.js.map +1 -0
- package/dist/contracts/exchange.contract.js +209 -168
- package/dist/contracts/exchange.contract.js.map +1 -1
- package/dist/contracts/fanclub.contract.d.ts +4 -0
- package/dist/contracts/fanclub.contract.js +193 -0
- package/dist/contracts/fanclub.contract.js.map +1 -0
- package/dist/contracts/giftcard.contract.d.ts +4 -0
- package/dist/contracts/giftcard.contract.js +158 -0
- package/dist/contracts/giftcard.contract.js.map +1 -0
- package/dist/contracts/grantrounds.contract.d.ts +4 -0
- package/dist/contracts/grantrounds.contract.js +265 -0
- package/dist/contracts/grantrounds.contract.js.map +1 -0
- package/dist/contracts/groupbuy.contract.d.ts +4 -0
- package/dist/contracts/groupbuy.contract.js +198 -0
- package/dist/contracts/groupbuy.contract.js.map +1 -0
- package/dist/contracts/helpers.d.ts +66 -0
- package/dist/contracts/helpers.js +166 -0
- package/dist/contracts/helpers.js.map +1 -0
- package/dist/contracts/insurancepool.contract.d.ts +4 -0
- package/dist/contracts/insurancepool.contract.js +281 -0
- package/dist/contracts/insurancepool.contract.js.map +1 -0
- package/dist/contracts/invoice.contract.d.ts +4 -0
- package/dist/contracts/invoice.contract.js +193 -0
- package/dist/contracts/invoice.contract.js.map +1 -0
- package/dist/contracts/launchpad.contract.d.ts +4 -0
- package/dist/contracts/launchpad.contract.js +225 -0
- package/dist/contracts/launchpad.contract.js.map +1 -0
- package/dist/contracts/lotto.contract.js +53 -37
- package/dist/contracts/lotto.contract.js.map +1 -1
- package/dist/contracts/multisigtreasury.contract.d.ts +4 -0
- package/dist/contracts/multisigtreasury.contract.js +245 -0
- package/dist/contracts/multisigtreasury.contract.js.map +1 -0
- package/dist/contracts/nft.contract.d.ts +1 -0
- package/dist/contracts/nft.contract.js +234 -195
- package/dist/contracts/nft.contract.js.map +1 -1
- package/dist/contracts/oraclebounty.contract.d.ts +4 -0
- package/dist/contracts/oraclebounty.contract.js +250 -0
- package/dist/contracts/oraclebounty.contract.js.map +1 -0
- package/dist/contracts/payroll.contract.d.ts +4 -0
- package/dist/contracts/payroll.contract.js +232 -0
- package/dist/contracts/payroll.contract.js.map +1 -0
- package/dist/contracts/paywall.contract.d.ts +4 -0
- package/dist/contracts/paywall.contract.js +185 -0
- package/dist/contracts/paywall.contract.js.map +1 -0
- package/dist/contracts/poll.contract.js +2 -0
- package/dist/contracts/poll.contract.js.map +1 -1
- package/dist/contracts/predictionmarket.contract.d.ts +4 -0
- package/dist/contracts/predictionmarket.contract.js +213 -0
- package/dist/contracts/predictionmarket.contract.js.map +1 -0
- package/dist/contracts/proposaltimelock.contract.d.ts +4 -0
- package/dist/contracts/proposaltimelock.contract.js +250 -0
- package/dist/contracts/proposaltimelock.contract.js.map +1 -0
- package/dist/contracts/questpass.contract.d.ts +4 -0
- package/dist/contracts/questpass.contract.js +214 -0
- package/dist/contracts/questpass.contract.js.map +1 -0
- package/dist/contracts/referral.contract.d.ts +4 -0
- package/dist/contracts/referral.contract.js +238 -0
- package/dist/contracts/referral.contract.js.map +1 -0
- package/dist/contracts/rental.contract.d.ts +4 -0
- package/dist/contracts/rental.contract.js +221 -0
- package/dist/contracts/rental.contract.js.map +1 -0
- package/dist/contracts/revenuesplit.contract.d.ts +4 -0
- package/dist/contracts/revenuesplit.contract.js +211 -0
- package/dist/contracts/revenuesplit.contract.js.map +1 -0
- package/dist/contracts/rps.contract.js +48 -20
- package/dist/contracts/rps.contract.js.map +1 -1
- package/dist/contracts/savings.contract.d.ts +4 -0
- package/dist/contracts/savings.contract.js +208 -0
- package/dist/contracts/savings.contract.js.map +1 -0
- package/dist/contracts/subscription.contract.d.ts +4 -0
- package/dist/contracts/subscription.contract.js +241 -0
- package/dist/contracts/subscription.contract.js.map +1 -0
- package/dist/contracts/sweepstakes.contract.d.ts +4 -0
- package/dist/contracts/sweepstakes.contract.js +209 -0
- package/dist/contracts/sweepstakes.contract.js.map +1 -0
- package/dist/contracts/ticketing.contract.d.ts +4 -0
- package/dist/contracts/ticketing.contract.js +185 -0
- package/dist/contracts/ticketing.contract.js.map +1 -0
- package/dist/contracts/tipjar.contract.js +2 -0
- package/dist/contracts/tipjar.contract.js.map +1 -1
- package/dist/contracts/token.contract.js +135 -125
- package/dist/contracts/token.contract.js.map +1 -1
- package/dist/index.d.ts +40 -0
- package/dist/index.js +72 -1
- package/dist/index.js.map +1 -1
- package/dist/metadata.d.ts +20 -0
- package/dist/metadata.js +320 -1
- package/dist/metadata.js.map +1 -1
- package/dist/providers/block-provider.d.ts +22 -0
- package/dist/providers/block-provider.js +3 -0
- package/dist/providers/block-provider.js.map +1 -0
- package/dist/providers/haf-client.d.ts +30 -0
- package/dist/providers/haf-client.js +119 -0
- package/dist/providers/haf-client.js.map +1 -0
- package/dist/providers/haf-provider.d.ts +49 -0
- package/dist/providers/haf-provider.js +256 -0
- package/dist/providers/haf-provider.js.map +1 -0
- package/dist/providers/hive-provider.d.ts +13 -0
- package/dist/providers/hive-provider.js +25 -0
- package/dist/providers/hive-provider.js.map +1 -0
- package/dist/providers/index.d.ts +4 -0
- package/dist/providers/index.js +21 -0
- package/dist/providers/index.js.map +1 -0
- package/dist/streamer.d.ts +65 -4
- package/dist/streamer.js +768 -72
- package/dist/streamer.js.map +1 -1
- package/dist/types/hive-stream.d.ts +317 -0
- package/dist/utils.d.ts +33 -0
- package/dist/utils.js +198 -2
- package/dist/utils.js.map +1 -1
- package/package.json +16 -1
- package/.claude/settings.local.json +0 -12
- package/.env.example +0 -3
- package/.travis.yml +0 -11
- package/AGENTS.md +0 -35
- package/CLAUDE.md +0 -75
- package/ecosystem.config.js +0 -17
- package/examples/contracts/README.md +0 -8
- package/examples/contracts/exchange.ts +0 -38
- package/examples/contracts/poll.ts +0 -21
- package/examples/contracts/rps.ts +0 -19
- package/examples/contracts/tipjar.ts +0 -19
- package/jest.config.js +0 -9
- package/test-contract-block.md +0 -19
- package/tests/actions.spec.ts +0 -252
- package/tests/adapters/actions-persistence.spec.ts +0 -144
- package/tests/adapters/postgresql.adapter.spec.ts +0 -127
- package/tests/adapters/sqlite.adapter.spec.ts +0 -181
- package/tests/config-input.spec.ts +0 -90
- package/tests/contracts/coinflip.contract.spec.ts +0 -94
- package/tests/contracts/dice.contract.spec.ts +0 -87
- package/tests/contracts/entrants.json +0 -729
- package/tests/contracts/exchange.contract.spec.ts +0 -84
- package/tests/contracts/lotto.contract.spec.ts +0 -59
- package/tests/contracts/nft.contract.spec.ts +0 -948
- package/tests/contracts/token.contract.spec.ts +0 -90
- package/tests/exchanges/coingecko.exchange.spec.ts +0 -169
- package/tests/exchanges/exchange.base.spec.ts +0 -246
- package/tests/helpers/mock-adapter.ts +0 -214
- package/tests/helpers/mock-fetch.ts +0 -165
- package/tests/hive-chain-features.spec.ts +0 -319
- package/tests/hive-rates.spec.ts +0 -443
- package/tests/integration/hive-rates.integration.spec.ts +0 -35
- package/tests/metadata.spec.ts +0 -63
- package/tests/setup.ts +0 -30
- package/tests/streamer-actions.spec.ts +0 -274
- package/tests/streamer.spec.ts +0 -342
- package/tests/types/rates.spec.ts +0 -216
- package/tests/utils.spec.ts +0 -113
- package/tsconfig.build.json +0 -4
- package/tslint.json +0 -21
- package/wallaby.js +0 -26
|
@@ -1,90 +0,0 @@
|
|
|
1
|
-
import { createTokenContract } from '../../src/contracts/token.contract';
|
|
2
|
-
import { Streamer } from '../../src/streamer';
|
|
3
|
-
import { createMockAdapter } from '../helpers/mock-adapter';
|
|
4
|
-
|
|
5
|
-
const createContext = (streamer: Streamer, adapter: any, sender: string) => ({
|
|
6
|
-
trigger: 'custom_json' as const,
|
|
7
|
-
streamer,
|
|
8
|
-
adapter,
|
|
9
|
-
config: streamer['config'],
|
|
10
|
-
block: { number: 123, id: 'block123', previousId: 'prevblock123', time: new Date() },
|
|
11
|
-
transaction: { id: 'txn123' },
|
|
12
|
-
sender,
|
|
13
|
-
customJson: { id: 'hivestream', json: {}, isSignedWithActiveKey: true }
|
|
14
|
-
});
|
|
15
|
-
|
|
16
|
-
describe('TokenContract', () => {
|
|
17
|
-
let streamer: Streamer;
|
|
18
|
-
let adapter: any;
|
|
19
|
-
let contract: ReturnType<typeof createTokenContract>;
|
|
20
|
-
|
|
21
|
-
beforeEach(async () => {
|
|
22
|
-
streamer = new Streamer();
|
|
23
|
-
adapter = createMockAdapter();
|
|
24
|
-
await streamer.registerAdapter(adapter);
|
|
25
|
-
contract = createTokenContract();
|
|
26
|
-
await streamer.registerContract(contract);
|
|
27
|
-
});
|
|
28
|
-
|
|
29
|
-
afterEach(async () => {
|
|
30
|
-
await streamer.stop();
|
|
31
|
-
});
|
|
32
|
-
|
|
33
|
-
test('Creates a token', async () => {
|
|
34
|
-
const ctx = createContext(streamer, adapter, 'alice');
|
|
35
|
-
const payload = {
|
|
36
|
-
symbol: 'TEST',
|
|
37
|
-
name: 'Test Token',
|
|
38
|
-
url: 'https://example.com',
|
|
39
|
-
precision: 3,
|
|
40
|
-
maxSupply: '1000000'
|
|
41
|
-
};
|
|
42
|
-
|
|
43
|
-
await contract.actions.createToken.handler(payload, ctx);
|
|
44
|
-
|
|
45
|
-
expect(adapter.queries.join(' ')).toContain('INSERT INTO tokens');
|
|
46
|
-
expect(adapter.events.length).toBeGreaterThan(0);
|
|
47
|
-
expect(adapter.events[0].action).toBe('createToken');
|
|
48
|
-
});
|
|
49
|
-
|
|
50
|
-
test('Rejects duplicate token symbols', async () => {
|
|
51
|
-
adapter.setTestContext({ existingToken: 'TEST' });
|
|
52
|
-
const ctx = createContext(streamer, adapter, 'alice');
|
|
53
|
-
const payload = {
|
|
54
|
-
symbol: 'TEST',
|
|
55
|
-
name: 'Test Token',
|
|
56
|
-
maxSupply: '1000000'
|
|
57
|
-
};
|
|
58
|
-
|
|
59
|
-
await expect(contract.actions.createToken.handler(payload, ctx))
|
|
60
|
-
.rejects
|
|
61
|
-
.toThrow('Token with symbol TEST already exists');
|
|
62
|
-
});
|
|
63
|
-
|
|
64
|
-
test('Prevents non-creators from issuing tokens', async () => {
|
|
65
|
-
const ctx = createContext(streamer, adapter, 'bob');
|
|
66
|
-
const payload = {
|
|
67
|
-
symbol: 'TEST',
|
|
68
|
-
to: 'carol',
|
|
69
|
-
amount: '100'
|
|
70
|
-
};
|
|
71
|
-
|
|
72
|
-
await expect(contract.actions.issueTokens.handler(payload, ctx))
|
|
73
|
-
.rejects
|
|
74
|
-
.toThrow('Only the token creator can issue new tokens');
|
|
75
|
-
});
|
|
76
|
-
|
|
77
|
-
test('Prevents transfers with insufficient balance', async () => {
|
|
78
|
-
adapter.setTestContext({ insufficientBalance: true });
|
|
79
|
-
const ctx = createContext(streamer, adapter, 'alice');
|
|
80
|
-
const payload = {
|
|
81
|
-
symbol: 'TEST',
|
|
82
|
-
to: 'bob',
|
|
83
|
-
amount: '9999'
|
|
84
|
-
};
|
|
85
|
-
|
|
86
|
-
await expect(contract.actions.transferTokens.handler(payload, ctx))
|
|
87
|
-
.rejects
|
|
88
|
-
.toThrow('Insufficient balance');
|
|
89
|
-
});
|
|
90
|
-
});
|
|
@@ -1,169 +0,0 @@
|
|
|
1
|
-
import { CoinGeckoExchange } from '../../src/exchanges/coingecko';
|
|
2
|
-
import { NetworkError, ValidationError } from '../../src/types/rates';
|
|
3
|
-
import { mockSuccessfulApis, mockNetworkErrors, mockHttpErrors, mockInvalidResponses, cleanupMocks } from '../helpers/mock-fetch';
|
|
4
|
-
|
|
5
|
-
describe('CoinGeckoExchange', () => {
|
|
6
|
-
let exchange: CoinGeckoExchange;
|
|
7
|
-
|
|
8
|
-
beforeEach(() => {
|
|
9
|
-
exchange = new CoinGeckoExchange();
|
|
10
|
-
});
|
|
11
|
-
|
|
12
|
-
afterEach(() => {
|
|
13
|
-
cleanupMocks();
|
|
14
|
-
});
|
|
15
|
-
|
|
16
|
-
describe('fetchRates', () => {
|
|
17
|
-
it('should fetch rates successfully', async () => {
|
|
18
|
-
mockSuccessfulApis();
|
|
19
|
-
|
|
20
|
-
const success = await exchange.fetchRates();
|
|
21
|
-
|
|
22
|
-
expect(success).toBe(true);
|
|
23
|
-
expect(exchange.rateUsdHive).toBe(0.25);
|
|
24
|
-
expect(exchange.rateUsdHbd).toBe(1.00);
|
|
25
|
-
});
|
|
26
|
-
|
|
27
|
-
it('should handle network errors', async () => {
|
|
28
|
-
mockNetworkErrors();
|
|
29
|
-
|
|
30
|
-
await expect(exchange.fetchRates()).rejects.toThrow(NetworkError);
|
|
31
|
-
});
|
|
32
|
-
|
|
33
|
-
it('should handle HTTP errors', async () => {
|
|
34
|
-
mockHttpErrors(500);
|
|
35
|
-
|
|
36
|
-
await expect(exchange.fetchRates()).rejects.toThrow(NetworkError);
|
|
37
|
-
});
|
|
38
|
-
|
|
39
|
-
it('should handle invalid JSON responses', async () => {
|
|
40
|
-
mockInvalidResponses();
|
|
41
|
-
|
|
42
|
-
await expect(exchange.fetchRates()).rejects.toThrow(ValidationError);
|
|
43
|
-
});
|
|
44
|
-
|
|
45
|
-
it('should retry on failure', async () => {
|
|
46
|
-
const retryExchange = new CoinGeckoExchange({ maxRetries: 3, retryDelay: 100 });
|
|
47
|
-
|
|
48
|
-
// Mock first two calls to fail, third to succeed
|
|
49
|
-
let callCount = 0;
|
|
50
|
-
global.fetch = jest.fn().mockImplementation(() => {
|
|
51
|
-
callCount++;
|
|
52
|
-
if (callCount <= 2) {
|
|
53
|
-
return Promise.reject(new Error('Network error'));
|
|
54
|
-
}
|
|
55
|
-
return Promise.resolve({
|
|
56
|
-
ok: true,
|
|
57
|
-
json: () => Promise.resolve({
|
|
58
|
-
hive: { usd: 0.25 },
|
|
59
|
-
'hive_dollar': { usd: 1.00 }
|
|
60
|
-
})
|
|
61
|
-
});
|
|
62
|
-
});
|
|
63
|
-
|
|
64
|
-
const success = await retryExchange.updateRates();
|
|
65
|
-
|
|
66
|
-
expect(success).toBe(true);
|
|
67
|
-
expect(retryExchange.rateUsdHive).toBe(0.25);
|
|
68
|
-
expect(callCount).toBe(3);
|
|
69
|
-
}, 10000); // Increase timeout for retry test
|
|
70
|
-
|
|
71
|
-
it('should respect timeout', async () => {
|
|
72
|
-
const timeoutExchange = new CoinGeckoExchange({ timeout: 100 });
|
|
73
|
-
|
|
74
|
-
// Mock a delayed response
|
|
75
|
-
global.fetch = jest.fn().mockImplementation(() =>
|
|
76
|
-
new Promise((resolve) => {
|
|
77
|
-
setTimeout(() => resolve({
|
|
78
|
-
ok: true,
|
|
79
|
-
json: () => Promise.resolve({ hive: { usd: 0.25 } })
|
|
80
|
-
}), 200); // Longer than timeout
|
|
81
|
-
})
|
|
82
|
-
);
|
|
83
|
-
|
|
84
|
-
await expect(timeoutExchange.fetchRates()).rejects.toThrow();
|
|
85
|
-
});
|
|
86
|
-
|
|
87
|
-
it('should validate rate values', async () => {
|
|
88
|
-
// Mock response with invalid rates
|
|
89
|
-
global.fetch = jest.fn().mockResolvedValue({
|
|
90
|
-
ok: true,
|
|
91
|
-
json: () => Promise.resolve({
|
|
92
|
-
hive: { usd: -1 }, // Invalid negative rate
|
|
93
|
-
'hive_dollar': { usd: 1.00 }
|
|
94
|
-
})
|
|
95
|
-
});
|
|
96
|
-
|
|
97
|
-
await expect(exchange.fetchRates()).rejects.toThrow(ValidationError);
|
|
98
|
-
});
|
|
99
|
-
|
|
100
|
-
it('should handle missing rate data', async () => {
|
|
101
|
-
// Mock response with missing hive data
|
|
102
|
-
global.fetch = jest.fn().mockResolvedValue({
|
|
103
|
-
ok: true,
|
|
104
|
-
json: () => Promise.resolve({
|
|
105
|
-
'hive_dollar': { usd: 1.00 }
|
|
106
|
-
// Missing hive data
|
|
107
|
-
})
|
|
108
|
-
});
|
|
109
|
-
|
|
110
|
-
await expect(exchange.fetchRates()).rejects.toThrow(ValidationError);
|
|
111
|
-
});
|
|
112
|
-
});
|
|
113
|
-
|
|
114
|
-
describe('caching', () => {
|
|
115
|
-
it('should use cached rates when valid', async () => {
|
|
116
|
-
mockSuccessfulApis();
|
|
117
|
-
|
|
118
|
-
// First fetch
|
|
119
|
-
const rates1 = await exchange.updateRates();
|
|
120
|
-
|
|
121
|
-
// Second fetch should use cache
|
|
122
|
-
const rates2 = await exchange.updateRates();
|
|
123
|
-
|
|
124
|
-
expect(rates1).toBe(true);
|
|
125
|
-
expect(rates2).toBe(false);
|
|
126
|
-
expect(global.fetch).toHaveBeenCalledTimes(1);
|
|
127
|
-
});
|
|
128
|
-
|
|
129
|
-
it('should refresh cache when expired', async () => {
|
|
130
|
-
const shortCacheExchange = new CoinGeckoExchange({ cacheDuration: 100 });
|
|
131
|
-
mockSuccessfulApis();
|
|
132
|
-
|
|
133
|
-
// First fetch
|
|
134
|
-
await shortCacheExchange.updateRates();
|
|
135
|
-
|
|
136
|
-
// Wait for cache to expire
|
|
137
|
-
await new Promise(resolve => setTimeout(resolve, 150));
|
|
138
|
-
|
|
139
|
-
// Second fetch should hit API again
|
|
140
|
-
await shortCacheExchange.updateRates();
|
|
141
|
-
|
|
142
|
-
expect(global.fetch).toHaveBeenCalledTimes(2);
|
|
143
|
-
});
|
|
144
|
-
|
|
145
|
-
it('should report cache validity correctly', async () => {
|
|
146
|
-
mockSuccessfulApis();
|
|
147
|
-
|
|
148
|
-
expect(exchange.isCacheValid()).toBe(false);
|
|
149
|
-
|
|
150
|
-
await exchange.updateRates();
|
|
151
|
-
|
|
152
|
-
expect(exchange.isCacheValid()).toBe(true);
|
|
153
|
-
});
|
|
154
|
-
|
|
155
|
-
it('should report last fetch time', async () => {
|
|
156
|
-
mockSuccessfulApis();
|
|
157
|
-
|
|
158
|
-
expect(exchange.getLastFetchTime()).toBeUndefined();
|
|
159
|
-
|
|
160
|
-
const beforeFetch = Date.now();
|
|
161
|
-
await exchange.updateRates();
|
|
162
|
-
const afterFetch = Date.now();
|
|
163
|
-
|
|
164
|
-
const lastFetchTime = exchange.getLastFetchTime();
|
|
165
|
-
expect(lastFetchTime).toBeGreaterThanOrEqual(beforeFetch);
|
|
166
|
-
expect(lastFetchTime).toBeLessThanOrEqual(afterFetch);
|
|
167
|
-
});
|
|
168
|
-
});
|
|
169
|
-
});
|
|
@@ -1,246 +0,0 @@
|
|
|
1
|
-
import { Exchange } from '../../src/exchanges/exchange';
|
|
2
|
-
import { NetworkError, RateConfig } from '../../src/types/rates';
|
|
3
|
-
|
|
4
|
-
// Mock fetch for tests
|
|
5
|
-
const mockFetch = jest.fn() as jest.MockedFunction<typeof fetch>;
|
|
6
|
-
global.fetch = mockFetch;
|
|
7
|
-
|
|
8
|
-
class TestExchange extends Exchange {
|
|
9
|
-
public readonly exchangeId = 'test-exchange';
|
|
10
|
-
public shouldSucceed = true;
|
|
11
|
-
public fetchCallCount = 0;
|
|
12
|
-
|
|
13
|
-
public async fetchRates(): Promise<boolean> {
|
|
14
|
-
this.fetchCallCount++;
|
|
15
|
-
|
|
16
|
-
if (!this.shouldSucceed) {
|
|
17
|
-
throw new Error('Test error');
|
|
18
|
-
}
|
|
19
|
-
|
|
20
|
-
this.rateUsdHive = 0.5;
|
|
21
|
-
this.rateUsdHbd = 1.0;
|
|
22
|
-
return true;
|
|
23
|
-
}
|
|
24
|
-
}
|
|
25
|
-
|
|
26
|
-
describe('Exchange', () => {
|
|
27
|
-
let exchange: TestExchange;
|
|
28
|
-
|
|
29
|
-
beforeEach(() => {
|
|
30
|
-
exchange = new TestExchange({ cacheDuration: 100, maxRetries: 2, retryDelay: 10 });
|
|
31
|
-
mockFetch.mockClear();
|
|
32
|
-
jest.clearAllTimers();
|
|
33
|
-
jest.useFakeTimers();
|
|
34
|
-
});
|
|
35
|
-
|
|
36
|
-
afterEach(() => {
|
|
37
|
-
jest.useRealTimers();
|
|
38
|
-
});
|
|
39
|
-
|
|
40
|
-
describe('constructor', () => {
|
|
41
|
-
it('should use default config when none provided', () => {
|
|
42
|
-
const exchange = new TestExchange();
|
|
43
|
-
expect(exchange.isCacheValid()).toBe(false);
|
|
44
|
-
});
|
|
45
|
-
|
|
46
|
-
it('should accept custom config', () => {
|
|
47
|
-
const config: RateConfig = {
|
|
48
|
-
cacheDuration: 5000,
|
|
49
|
-
maxRetries: 5,
|
|
50
|
-
retryDelay: 2000,
|
|
51
|
-
timeout: 15000
|
|
52
|
-
};
|
|
53
|
-
|
|
54
|
-
const exchange = new TestExchange(config);
|
|
55
|
-
expect(exchange).toBeDefined();
|
|
56
|
-
});
|
|
57
|
-
});
|
|
58
|
-
|
|
59
|
-
describe('updateRates', () => {
|
|
60
|
-
it('should fetch rates successfully', async () => {
|
|
61
|
-
const result = await exchange.updateRates();
|
|
62
|
-
|
|
63
|
-
expect(result).toBe(true);
|
|
64
|
-
expect(exchange.fetchCallCount).toBe(1);
|
|
65
|
-
expect(exchange.rateUsdHive).toBe(0.5);
|
|
66
|
-
expect(exchange.rateUsdHbd).toBe(1.0);
|
|
67
|
-
});
|
|
68
|
-
|
|
69
|
-
it('should return false when cache is valid', async () => {
|
|
70
|
-
// First fetch
|
|
71
|
-
await exchange.updateRates();
|
|
72
|
-
expect(exchange.fetchCallCount).toBe(1);
|
|
73
|
-
|
|
74
|
-
// Second fetch should use cache
|
|
75
|
-
const result = await exchange.updateRates();
|
|
76
|
-
expect(result).toBe(false);
|
|
77
|
-
expect(exchange.fetchCallCount).toBe(1);
|
|
78
|
-
});
|
|
79
|
-
|
|
80
|
-
it('should update when cache expires', async () => {
|
|
81
|
-
// First fetch
|
|
82
|
-
await exchange.updateRates();
|
|
83
|
-
expect(exchange.fetchCallCount).toBe(1);
|
|
84
|
-
|
|
85
|
-
// Advance time beyond cache duration
|
|
86
|
-
jest.advanceTimersByTime(150);
|
|
87
|
-
|
|
88
|
-
// Second fetch should happen
|
|
89
|
-
const result = await exchange.updateRates();
|
|
90
|
-
expect(result).toBe(true);
|
|
91
|
-
expect(exchange.fetchCallCount).toBe(2);
|
|
92
|
-
});
|
|
93
|
-
|
|
94
|
-
it('should retry on failure', async () => {
|
|
95
|
-
let callCount = 0;
|
|
96
|
-
exchange.fetchRates = jest.fn().mockImplementation(() => {
|
|
97
|
-
callCount++;
|
|
98
|
-
if (callCount === 1) {
|
|
99
|
-
throw new Error('Test error');
|
|
100
|
-
}
|
|
101
|
-
exchange.rateUsdHive = 0.5;
|
|
102
|
-
exchange.rateUsdHbd = 1.0;
|
|
103
|
-
return Promise.resolve(true);
|
|
104
|
-
});
|
|
105
|
-
|
|
106
|
-
const resultPromise = exchange.updateRates();
|
|
107
|
-
await jest.runAllTimersAsync();
|
|
108
|
-
const result = await resultPromise;
|
|
109
|
-
expect(result).toBe(true);
|
|
110
|
-
expect(exchange.fetchRates).toHaveBeenCalledTimes(2);
|
|
111
|
-
}, 10000);
|
|
112
|
-
|
|
113
|
-
it('should throw NetworkError after max retries', async () => {
|
|
114
|
-
exchange.shouldSucceed = false;
|
|
115
|
-
|
|
116
|
-
const resultPromise = exchange.updateRates();
|
|
117
|
-
const expectation = expect(resultPromise).rejects.toThrow(NetworkError);
|
|
118
|
-
const messageExpectation = expect(resultPromise).rejects.toThrow('Failed to fetch rates after 2 attempts');
|
|
119
|
-
await jest.runAllTimersAsync();
|
|
120
|
-
await expectation;
|
|
121
|
-
await messageExpectation;
|
|
122
|
-
expect(exchange.fetchCallCount).toBe(2);
|
|
123
|
-
}, 10000);
|
|
124
|
-
});
|
|
125
|
-
|
|
126
|
-
describe('fetchWithTimeout', () => {
|
|
127
|
-
beforeEach(() => {
|
|
128
|
-
// Reset to use real fetch for this test
|
|
129
|
-
(global.fetch as any) = mockFetch;
|
|
130
|
-
});
|
|
131
|
-
|
|
132
|
-
it('should fetch successfully within timeout', async () => {
|
|
133
|
-
mockFetch.mockResolvedValueOnce({
|
|
134
|
-
ok: true,
|
|
135
|
-
json: async () => ({ test: 'data' }),
|
|
136
|
-
} as Response);
|
|
137
|
-
|
|
138
|
-
const response = await exchange['fetchWithTimeout']('https://example.com');
|
|
139
|
-
expect(response.ok).toBe(true);
|
|
140
|
-
});
|
|
141
|
-
|
|
142
|
-
it('should throw NetworkError for HTTP errors', async () => {
|
|
143
|
-
mockFetch.mockResolvedValueOnce({
|
|
144
|
-
ok: false,
|
|
145
|
-
status: 404,
|
|
146
|
-
statusText: 'Not Found',
|
|
147
|
-
} as Response);
|
|
148
|
-
|
|
149
|
-
await expect(exchange['fetchWithTimeout']('https://example.com'))
|
|
150
|
-
.rejects.toThrow(NetworkError);
|
|
151
|
-
});
|
|
152
|
-
|
|
153
|
-
it('should include proper headers', async () => {
|
|
154
|
-
mockFetch.mockResolvedValueOnce({
|
|
155
|
-
ok: true,
|
|
156
|
-
} as Response);
|
|
157
|
-
|
|
158
|
-
await exchange['fetchWithTimeout']('https://example.com');
|
|
159
|
-
|
|
160
|
-
expect(mockFetch).toHaveBeenCalledWith(
|
|
161
|
-
'https://example.com',
|
|
162
|
-
expect.objectContaining({
|
|
163
|
-
headers: {
|
|
164
|
-
'User-Agent': 'hive-stream/3.0.0',
|
|
165
|
-
'Accept': 'application/json',
|
|
166
|
-
},
|
|
167
|
-
})
|
|
168
|
-
);
|
|
169
|
-
});
|
|
170
|
-
|
|
171
|
-
it('should handle timeout', async () => {
|
|
172
|
-
const exchange = new TestExchange({ timeout: 50 });
|
|
173
|
-
|
|
174
|
-
// Mock fetch to never resolve (timeout scenario)
|
|
175
|
-
mockFetch.mockImplementationOnce((_, options) =>
|
|
176
|
-
new Promise((_, reject) => {
|
|
177
|
-
const signal = options?.signal as AbortSignal | undefined;
|
|
178
|
-
if (signal) {
|
|
179
|
-
signal.addEventListener('abort', () => {
|
|
180
|
-
reject(new Error('Aborted'));
|
|
181
|
-
});
|
|
182
|
-
}
|
|
183
|
-
})
|
|
184
|
-
);
|
|
185
|
-
|
|
186
|
-
const timeoutPromise = exchange['fetchWithTimeout']('https://example.com');
|
|
187
|
-
const timeoutExpectation = expect(timeoutPromise).rejects.toThrow();
|
|
188
|
-
await jest.advanceTimersByTimeAsync(60);
|
|
189
|
-
await timeoutExpectation;
|
|
190
|
-
}, 1000);
|
|
191
|
-
});
|
|
192
|
-
|
|
193
|
-
describe('utility methods', () => {
|
|
194
|
-
it('should track last fetch time correctly', async () => {
|
|
195
|
-
expect(exchange.getLastFetchTime()).toBeUndefined();
|
|
196
|
-
|
|
197
|
-
await exchange.updateRates();
|
|
198
|
-
|
|
199
|
-
const fetchTime = exchange.getLastFetchTime();
|
|
200
|
-
expect(fetchTime).toBeDefined();
|
|
201
|
-
expect(typeof fetchTime).toBe('number');
|
|
202
|
-
});
|
|
203
|
-
|
|
204
|
-
it('should report cache validity correctly', async () => {
|
|
205
|
-
expect(exchange.isCacheValid()).toBe(false);
|
|
206
|
-
|
|
207
|
-
await exchange.updateRates();
|
|
208
|
-
expect(exchange.isCacheValid()).toBe(true);
|
|
209
|
-
|
|
210
|
-
jest.advanceTimersByTime(150);
|
|
211
|
-
expect(exchange.isCacheValid()).toBe(false);
|
|
212
|
-
});
|
|
213
|
-
|
|
214
|
-
it('should handle cache check with no previous fetch', () => {
|
|
215
|
-
expect(exchange.isCacheValid()).toBe(false);
|
|
216
|
-
});
|
|
217
|
-
});
|
|
218
|
-
|
|
219
|
-
describe('error handling', () => {
|
|
220
|
-
it('should preserve original error types in retries', async () => {
|
|
221
|
-
let callCount = 0;
|
|
222
|
-
exchange.fetchRates = jest.fn().mockImplementation(() => {
|
|
223
|
-
callCount++;
|
|
224
|
-
if (callCount === 1) {
|
|
225
|
-
throw new NetworkError('First failure', 'test');
|
|
226
|
-
}
|
|
227
|
-
return Promise.resolve(true);
|
|
228
|
-
});
|
|
229
|
-
|
|
230
|
-
const resultPromise = exchange.updateRates();
|
|
231
|
-
await jest.runAllTimersAsync();
|
|
232
|
-
const result = await resultPromise;
|
|
233
|
-
expect(result).toBe(true);
|
|
234
|
-
expect(exchange.fetchRates).toHaveBeenCalledTimes(2);
|
|
235
|
-
}, 10000);
|
|
236
|
-
|
|
237
|
-
it('should handle non-Error rejections', async () => {
|
|
238
|
-
exchange.fetchRates = jest.fn().mockRejectedValue('string error');
|
|
239
|
-
|
|
240
|
-
const resultPromise = exchange.updateRates();
|
|
241
|
-
const expectation = expect(resultPromise).rejects.toThrow(NetworkError);
|
|
242
|
-
await jest.runAllTimersAsync();
|
|
243
|
-
await expectation;
|
|
244
|
-
}, 10000);
|
|
245
|
-
});
|
|
246
|
-
});
|