-
Notifications
You must be signed in to change notification settings - Fork 1
Expand file tree
/
Copy pathTokenHolderRepositoryCache.spec.ts
More file actions
183 lines (147 loc) · 5.65 KB
/
TokenHolderRepositoryCache.spec.ts
File metadata and controls
183 lines (147 loc) · 5.65 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
import { TokenHolderRepositoryCache } from './TokenHolderRepositoryCache';
import IORedis from 'ioredis';
import { TokenHolderRepository } from './TokenHolderRepository';
import { SupportedChainId } from '@cowprotocol/cow-sdk';
import { NULL_ADDRESS, WETH } from '../../../test/mock';
import { CacheRepositoryRedis } from '../CacheRepository/CacheRepositoryRedis';
const CACHE_VALUE_SECONDS = 10;
const CACHE_NULL_SECONDS = 20;
const wethLowercase = WETH.toLocaleLowerCase();
const chainId = SupportedChainId.MAINNET;
jest.mock('ioredis', () => {
return jest.fn().mockImplementation(() => ({
get: jest.fn(),
set: jest.fn(),
}));
});
describe('TokenHolderRepositoryCache', () => {
let tokenHoldersRepositoryCache: TokenHolderRepositoryCache;
let redisMock: jest.Mocked<IORedis>;
let proxyMock: jest.Mocked<TokenHolderRepository>;
beforeEach(() => {
redisMock = new IORedis() as jest.Mocked<IORedis>;
proxyMock = {
getTopTokenHolders: jest.fn(),
};
const cacheRepository = new CacheRepositoryRedis(redisMock);
tokenHoldersRepositoryCache = new TokenHolderRepositoryCache(
proxyMock,
cacheRepository,
'test-cache',
CACHE_VALUE_SECONDS,
CACHE_NULL_SECONDS
);
});
const HOLDERS_1 = [
{
address: NULL_ADDRESS,
balance: '1',
},
];
const HOLDERS_2 = [
{
address: NULL_ADDRESS,
balance: '2',
},
];
const HOLDERS_1_STRING = JSON.stringify(HOLDERS_1);
const HOLDERS_2_STRING = JSON.stringify(HOLDERS_2);
describe('getTopTokenHolders', () => {
it('should return token holders from cache', async () => {
// GIVEN: Cached value HOLDERS_1
redisMock.get.mockResolvedValue(HOLDERS_1_STRING);
// GIVEN: proxy returns HOLDERS_2
proxyMock.getTopTokenHolders.mockResolvedValue(HOLDERS_2);
// WHEN: Get Top Token Holders
const topTokenHolder =
await tokenHoldersRepositoryCache.getTopTokenHolders(chainId, WETH);
expect(topTokenHolder).toStrictEqual(HOLDERS_1);
expect(proxyMock.getTopTokenHolders).not.toHaveBeenCalled();
});
it('should return NULL from cache', async () => {
// GIVEN: Cached value 'null'
redisMock.get.mockResolvedValue('null');
// GIVEN: proxy returns HOLDERS_2
proxyMock.getTopTokenHolders.mockResolvedValue(HOLDERS_2);
// WHEN: Get Top Token Holders
const topTokenHolder =
await tokenHoldersRepositoryCache.getTopTokenHolders(chainId, WETH);
// THEN: We get the cached value
expect(topTokenHolder).toEqual(null);
expect(proxyMock.getTopTokenHolders).not.toHaveBeenCalled();
});
it('should call the proxy if no cache, then cache the value', async () => {
// GIVEN: The value is not cached
redisMock.get.mockResolvedValue(null);
// GIVEN: proxy returns HOLDERS_2
proxyMock.getTopTokenHolders.mockResolvedValue(HOLDERS_2);
// WHEN: Get Top Token Holders
const topTokenHolder =
await tokenHoldersRepositoryCache.getTopTokenHolders(chainId, WETH);
// THEN: The holders matches the result from the proxy
expect(topTokenHolder).toStrictEqual(HOLDERS_2);
// THEN: The proxy has been called once
expect(proxyMock.getTopTokenHolders).toHaveBeenCalledWith(chainId, WETH);
// THEN: The value returned by the proxy is cached
expect(redisMock.set).toHaveBeenCalledWith(
`repos:test-cache:usd-price:${chainId}:${wethLowercase}`,
HOLDERS_2_STRING,
'EX',
CACHE_VALUE_SECONDS
);
});
it('should call the proxy if no cache, then cache the NULL', async () => {
// GIVEN: The value is not cached
redisMock.get.mockResolvedValue(null);
// GIVEN: proxy returns null
proxyMock.getTopTokenHolders.mockResolvedValue(null);
// WHEN: Get Top Token Holders
const price = await tokenHoldersRepositoryCache.getTopTokenHolders(
chainId,
WETH
);
// THEN: The price matches the result from the proxy
expect(price).toEqual(null);
// THEN: The proxy has been called once
expect(proxyMock.getTopTokenHolders).toHaveBeenCalledWith(chainId, WETH);
// THEN: The value returned by the proxy is cached
expect(redisMock.set).toHaveBeenCalledWith(
`repos:test-cache:usd-price:${chainId}:${wethLowercase}`,
'null',
'EX',
CACHE_NULL_SECONDS
);
});
it('should return the cached value, even if the proxy throws', async () => {
// GIVEN: Cached value HOLDERS_1_STRING
redisMock.get.mockResolvedValue(HOLDERS_1_STRING);
// GIVEN: The proxy throws an awful error
proxyMock.getTopTokenHolders.mockImplementation(() => {
throw new Error('💥 Booom!');
});
// WHEN: Get Top Token Holders
const tokenHolders = await tokenHoldersRepositoryCache.getTopTokenHolders(
chainId,
WETH
);
// THEN: The holders matches the result from the proxy
expect(tokenHolders).toStrictEqual(HOLDERS_1);
expect(proxyMock.getTopTokenHolders).not.toHaveBeenCalled();
});
it('should throw if the proxy throws and there is no cache available', async () => {
// GIVEN: The value is not cached
redisMock.get.mockResolvedValue(null);
// GIVEN: The proxy throws an awful error
proxyMock.getTopTokenHolders.mockImplementation(async () => {
throw new Error('💥 Booom!');
});
// WHEN: Get Top Token Holders
const tokenHolderPromise = tokenHoldersRepositoryCache.getTopTokenHolders(
chainId,
WETH
);
// THEN: The call throws an awful error
expect(tokenHolderPromise).rejects.toThrow('💥 Booom!');
});
});
});