forked from camptocamp/ogc-client
-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathcache.spec.ts
More file actions
189 lines (183 loc) · 6.21 KB
/
cache.spec.ts
File metadata and controls
189 lines (183 loc) · 6.21 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
184
185
186
187
188
189
import {
_resetCache,
clearCache,
getCache,
purgeOutdatedEntries,
readCacheEntry,
setCacheExpiryDuration,
storeCacheEntry,
useCache,
} from './cache.js';
const factory = jest.fn(() => ({ fresh: true }));
let NOW;
Date.now = () => NOW;
describe('cache utils', () => {
beforeEach(() => {
jest.clearAllMocks();
setCacheExpiryDuration(1000);
});
describe('cache API is available', () => {
beforeEach(async () => {
// clear all cache entries
NOW = 50000;
await purgeOutdatedEntries();
// set start time
NOW = 10000;
});
describe('useCache', () => {
describe('when no cache entry is present', () => {
let result;
beforeEach(async () => {
result = await useCache(factory, 'test', 'entry', '01');
});
it('runs the factory function', () => {
expect(factory).toHaveBeenCalledTimes(1);
});
it('returns the produced object', () => {
expect(result).toEqual({ fresh: true });
});
});
describe('when an expired cache entry is present', () => {
let result;
beforeEach(async () => {
await storeCacheEntry({ old: true }, 'test', 'entry', '02');
NOW = 12000;
result = await useCache(factory, 'test', 'entry', '02');
});
it('runs the factory function', () => {
expect(factory).toHaveBeenCalledTimes(1);
});
it('returns the produced object', () => {
expect(result).toEqual({ fresh: true });
});
});
describe('when a valid cache entry is present', () => {
let result;
beforeEach(async () => {
await storeCacheEntry({ old: true }, 'test', 'entry', '03');
NOW = 10800;
result = await useCache(factory, 'test', 'entry', '03');
});
it('does not run the factory function', () => {
expect(factory).not.toHaveBeenCalled();
});
it('returns the cached object', () => {
expect(result).toEqual({ old: true });
});
});
describe('when no cache entry is present but a similar task is already running', () => {
let result, longTask, produced;
beforeEach(async () => {
produced = { long: Math.random() };
longTask = jest.fn(
() =>
new Promise((resolve) => {
setTimeout(() => {
resolve(produced);
}, 10);
})
);
useCache(longTask, 'test', 'entry', '04');
result = await useCache(longTask, 'test', 'entry', '04');
});
it('does not run the factory function again', () => {
expect(longTask).toHaveBeenCalledTimes(1);
});
it('returns the object from the existing run', () => {
expect(result).toBe(produced);
});
});
describe('when an expired cache entry is present (as well as unrelated entries) and a new cache entry is set', () => {
beforeEach(async () => {
await storeCacheEntry({ old: true }, 'test', 'entry', '04');
NOW = 11200;
await storeCacheEntry({ unrelated: true }, 'unrelated');
await useCache(factory, 'test', 'entry', '05');
});
it('deletes the expired cache entry', async () => {
await expect(
readCacheEntry('test', 'entry', '04')
).resolves.toBeNull();
});
it('preserves unrelated entry', async () => {
await expect(readCacheEntry('unrelated')).resolves.toEqual({
unrelated: true,
});
});
});
});
describe('clearCache', () => {
let result;
beforeEach(async () => {
await storeCacheEntry({ old: true }, 'test', 'entry', '03');
NOW = 10800;
await clearCache();
result = await useCache(factory, 'test', 'entry', '03');
});
it('do not use the cached function object', () => {
expect(factory).toHaveBeenCalled();
expect(result).toEqual({ fresh: true });
});
});
});
describe('when the Cache API is not available', () => {
let result;
let originalCache;
beforeEach(async () => {
_resetCache();
originalCache = globalThis.caches;
delete globalThis.caches;
await storeCacheEntry({ old: true }, 'test', 'entry', '06');
result = await useCache(factory, 'test', 'entry', '06');
});
afterEach(() => {
globalThis.caches = originalCache;
});
it('runs the factory function', () => {
expect(factory).toHaveBeenCalledTimes(1);
});
it('does not use cache, does not fail', () => {
expect(result).toEqual({ fresh: true });
});
});
describe('when the Cache API is available but blocked for security reasons', () => {
let result;
let originalFn;
beforeEach(async () => {
_resetCache();
originalFn = globalThis.caches.open;
globalThis.caches.open = () => Promise.reject(new Error('not allowed'));
await storeCacheEntry({ old: true }, 'test', 'entry', '07');
result = await useCache(factory, 'test', 'entry', '07');
});
afterEach(() => {
globalThis.caches.open = originalFn;
});
it('runs the factory function', () => {
expect(factory).toHaveBeenCalledTimes(1);
});
it('does not use cache, does not fail', () => {
expect(result).toEqual({ fresh: true });
});
});
describe('when the Cache API is available but fails on put()', () => {
let result;
beforeEach(async () => {
_resetCache();
const cache = await getCache();
cache.put = () => Promise.reject(new Error('something went wrong'));
await storeCacheEntry({ old: true }, 'test', 'entry', '08');
await storeCacheEntry({ old: true }, 'test', 'entry', '09');
await storeCacheEntry({ old: true }, 'test', 'entry', '10');
result = await useCache(factory, 'test', 'entry', '08');
result = await useCache(factory, 'test', 'entry', '09');
result = await useCache(factory, 'test', 'entry', '10');
});
it('runs the factory function', () => {
expect(factory).toHaveBeenCalledTimes(3);
});
it('does not use cache, does not fail', () => {
expect(result).toEqual({ fresh: true });
});
});
});