Skip to content
File

Blob: src/workerd/api/tests/memory-cache-test.js

javascript248 lines
1// Copyright (c) 2024 Cloudflare, Inc.
2// Licensed under the Apache 2.0 license found in the LICENSE file or at:
3// https://opensource.org/licenses/Apache-2.0
4import { strictEqual } from 'node:assert';
5 
6export const basic = {
7 async test(ctrl, env) {
8 let fallbackCalled = 0;
9 const value = await env.CACHE.read('foo', async (key) => {
10 strictEqual(key, 'foo');
11 fallbackCalled++;
12 return { value: 'bar' };
13 });
14 const value2 = await env.CACHE.read('foo', async (key) => {
15 throw new Error('should not be called');
16 });
17 strictEqual(value, 'bar');
18 strictEqual(value, value2);
19 strictEqual(fallbackCalled, 1);
20 },
21};
22 
23export const keysTooLarge = {
24 async test(ctrl, env) {
25 // Keys that are fewer than 2048 bytes should work.
26 for (const key of ['', 'abc', 'a'.repeat(2048), 'ä'.repeat(1024)]) {
27 const val = await env.CACHE.read(key, async (k) => {
28 strictEqual(k, key);
29 return { value: `value for ${k}` };
30 });
31 strictEqual(val, `value for ${key}`);
32 }
33 
34 // Keys longer that 2048 bytes should reject.
35 for (const key of ['a'.repeat(2049), 'ä'.repeat(1025)]) {
36 await env.CACHE.read(key, async (k) => {
37 throw new Error('should not be called');
38 }).then(
39 () => {
40 throw new Error('should have rejected');
41 },
42 (err) => {
43 strictEqual(err.message, 'Key too large.');
44 }
45 );
46 }
47 },
48};
49 
50export const valueNotSerializable = {
51 async test(ctrl, env) {
52 try {
53 await env.CACHE.read('foo', (key) => {
54 return { value: Symbol('foo') };
55 });
56 throw new Error('should have failed');
57 } catch (err) {
58 strictEqual(err.message, 'failed to serialize symbol');
59 }
60 
61 try {
62 await env.CACHE.read('foo', (key) => {
63 return { value: () => {} };
64 });
65 throw new Error('should have failed');
66 } catch (err) {
67 strictEqual(err.message, 'failed to serialize function');
68 }
69 },
70};
71 
72export const evictionsHappen = {
73 async test(ctrl, env) {
74 // Our cache has a max of two items, let's fill it...
75 await env.CACHE2.read('foo', async (key) => {
76 return { value: 'foo' };
77 });
78 await env.CACHE2.read('bar', async (key) => {
79 return { value: 'bar' };
80 });
81 // Nothing should be evicted at this point.
82 // Let's make foo our most recently accessed item.
83 strictEqual(await env.CACHE2.read('bar'), 'bar');
84 strictEqual(await env.CACHE2.read('foo'), 'foo');
85 // Now, let's add a third item, baz.
86 await env.CACHE2.read('baz', async (key) => {
87 return { value: 'baz' };
88 });
89 // At this point, 'bar' should have been evicted.
90 strictEqual(await env.CACHE2.read('bar'), undefined);
91 },
92};
93 
94export const evictionsHappenValueSize = {
95 async test(ctrl, env) {
96 strictEqual(
97 await env.CACHE3.read('foo', async (key) => {
98 return { value: 'a'.repeat(495) };
99 }),
100 'a'.repeat(495)
101 );
102 
103 strictEqual(await env.CACHE3.read('foo'), 'a'.repeat(495));
104 
105 strictEqual(
106 await env.CACHE3.read('bar', async (key) => {
107 return { value: 'a'.repeat(500) };
108 }),
109 'a'.repeat(500)
110 );
111 
112 strictEqual(await env.CACHE3.read('bar'), undefined);
113 
114 for (let n = 0; n < 6; n++) {
115 await env.CACHE3.read(`${n}`, async (key) => {
116 return { value: 'a'.repeat(100) };
117 });
118 strictEqual(await env.CACHE3.read(`${n}`), 'a'.repeat(100));
119 }
120 strictEqual(await env.CACHE3.read('bar'), undefined);
121 },
122};
123 
124export const concurrentReads = {
125 async test(ctrl, env) {
126 const promises = [];
127 promises.push(
128 env.CACHE.read('qux', () => {
129 return { value: 'qux' };
130 })
131 );
132 promises.push(
133 env.CACHE.read('qux', () => {
134 throw new Error('should not have been called');
135 })
136 );
137 const results = await Promise.allSettled(promises);
138 strictEqual(results[0].value, 'qux');
139 strictEqual(results[1].value, 'qux');
140 },
141};
142 
143export const delayedFallback = {
144 async test(ctrl, env) {
145 const foo = await env.CACHE.read('123', async () => {
146 await scheduler.wait(100);
147 return { value: 123 };
148 });
149 strictEqual(foo, 123);
150 },
151};
152 
153export const expiredEviction = {
154 async test(ctrl, env) {
155 const ret = await env.CACHE.read('expires', async () => {
156 return { value: 'foo', expiration: Date.now() + 100 };
157 });
158 strictEqual(ret, 'foo');
159 await scheduler.wait(600);
160 strictEqual(await env.CACHE.read('expires'), undefined);
161 },
162};
163 
164export const fallbackThrows = {
165 async test(ctrl, env) {
166 try {
167 await env.CACHE.read('foo', () => {
168 throw new Error('foo');
169 });
170 throw new Error('should have thrown');
171 } catch (err) {
172 strictEqual(err.message, 'foo');
173 }
174 
175 try {
176 await env.CACHE.read('foo', async () => {
177 throw new Error('foo');
178 });
179 throw new Error('should have thrown');
180 } catch (err) {
181 strictEqual(err.message, 'foo');
182 }
183 
184 try {
185 await env.CACHE.read('foo', () => {
186 return Promise.reject(new Error('foo'));
187 });
188 throw new Error('should have thrown');
189 } catch (err) {
190 strictEqual(err.message, 'foo');
191 }
192 },
193};
194 
195export const fallbackQueueMicrotask = {
196 async test(ctrl, env) {
197 const ret = await env.CACHE.read('microtask', () => {
198 const { promise, resolve } = Promise.withResolvers();
199 globalThis.queueMicrotask(() => {
200 resolve({ value: 'xyz' });
201 });
202 return promise;
203 });
204 strictEqual(ret, 'xyz');
205 },
206};
207 
208export const fallbackChainingOnError = {
209 async test(ctrl, env) {
210 const promises = [];
211 promises.push(
212 env.CACHE.read('fallback', () => {
213 throw new Error('foo');
214 })
215 );
216 promises.push(
217 env.CACHE.read('fallback', () => {
218 return { value: 'bar' };
219 })
220 );
221 const results = await Promise.allSettled(promises);
222 // The first one failed.
223 strictEqual(results[0].reason.message, 'foo');
224 // The second one succeeded.
225 strictEqual(results[1].value, 'bar');
226 },
227};
228 
229export const fallbackNotLocked = {
230 async test(ctrl, env) {
231 // Test that one long running fallback does not block another one.
232 const promises = [];
233 promises.push(
234 env.CACHE.read('aaa', async () => {
235 await scheduler.wait(500);
236 return { value: 'aaa' };
237 })
238 );
239 promises.push(
240 env.CACHE.read('bbb', async () => {
241 return { value: 'bbb' };
242 })
243 );
244 const raced = await Promise.race(promises);
245 strictEqual(raced, 'bbb');
246 },
247};