Skip to content
File

Blob: src/workerd/api/tests/kv-test.js

javascript365 lines
1// Copyright (c) 2023 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
4 
5import assert from 'node:assert';
6import { WorkerEntrypoint } from 'cloudflare:workers';
7export default class KVTest extends WorkerEntrypoint {
8 // Request handler (from `env.NAMESPACE`)
9 async fetch(request, env, ctx) {
10 let result = 'example';
11 const { pathname, searchParams } = new URL(request.url);
12 const queryParameters = Object.fromEntries(searchParams);
13 const headers = new Headers();
14 headers.set('CF-Cache-Status', 'HIT');
15 
16 if (pathname === '/fail-client') {
17 return new Response(null, { status: 404 });
18 } else if (pathname == '/fail-server') {
19 return new Response(null, { status: 500 });
20 } else if (pathname == '/get-json') {
21 result = JSON.stringify({ example: 'values' });
22 } else if (pathname == '/bulk/get') {
23 let r = '';
24 const decoder = new TextDecoder();
25 for await (const chunk of request.body) {
26 r += decoder.decode(chunk, { stream: true });
27 }
28 r += decoder.decode();
29 const parsedBody = JSON.parse(r);
30 const keys = parsedBody.keys;
31 if (keys.length < 1 || keys.length > 100) {
32 return new Response(null, { status: 400 });
33 }
34 result = {};
35 if (parsedBody.type == 'json') {
36 for (const key of keys) {
37 if (key == 'key-not-json') {
38 return new Response(null, { status: 500 });
39 }
40 const val = { example: `values-${key}` };
41 if (parsedBody.withMetadata) {
42 result[key] = { value: val, metadata: 'example-metadata' };
43 } else {
44 result[key] = val;
45 }
46 }
47 } else if (!parsedBody.type || parsedBody.type == 'text') {
48 for (const key of keys) {
49 const val = JSON.stringify({ example: `values-${key}` });
50 if (key == 'not-found') {
51 result[key] = null;
52 } else if (parsedBody.withMetadata) {
53 result[key] = { value: val, metadata: 'example-metadata' };
54 } else {
55 result[key] = val;
56 }
57 }
58 } else {
59 // invalid type requested
60 return new Response(null, { status: 500 });
61 }
62 result = JSON.stringify(result);
63 } else if (
64 pathname === '/foo_with_exp' ||
65 pathname === '/foo_with_expTtl'
66 ) {
67 console.log(pathname, searchParams, request.method, await request.text());
68 result = JSON.stringify({
69 keys: [],
70 });
71 } else if (pathname === '/') {
72 const limit = parseInt(queryParameters.key_count_limit ?? '3', 10);
73 const prefix = queryParameters.prefix;
74 
75 switch (prefix) {
76 case 'te': {
77 // [0, 1, 2, ... limit]
78 const range = [...Array(limit)].keys();
79 const keys = Array.from(range).map((index) => ({
80 name: `test${index}`,
81 metadata: { someMetadataKey: 'someMetadataValue' },
82 }));
83 
84 result = JSON.stringify({
85 keys,
86 list_complete: limit === 100,
87 cursor: limit === 100 ? undefined : '6Ck1la0VxJ0djhidm1MdX2FyD',
88 expiration: 1234,
89 });
90 break;
91 }
92 case 'not-found':
93 result = JSON.stringify({
94 keys: [],
95 list_complete: true,
96 });
97 }
98 } else {
99 // generic success for get key
100 result = 'value-' + pathname.slice(1);
101 }
102 let response = new Response(result, {
103 status: 200,
104 headers: headers,
105 });
106 response.headers.set(
107 'CF-KV-Metadata',
108 '{"someMetadataKey":"someMetadataValue","someUnicodeMeta":"🤓"}'
109 );
110 return response;
111 }
112 
113 async delete(keys) {
114 if (keys === 'error') {
115 // invalid type requested
116 throw new KVInternalError('failed to delete a single key', 'DELETE');
117 }
118 if (Array.isArray(keys) && keys.length > 100) {
119 throw new BadClientRequestError(
120 'You can delete maximum of 100 keys per request',
121 'DELETE'
122 );
123 }
124 if (Array.isArray(keys) && keys.includes('error')) {
125 throw new KVInternalError(
126 'failed to delete a single key from batch',
127 'DELETE'
128 );
129 }
130 // Success otherwise
131 }
132}
133 
134class BadClientRequestError extends Error {
135 constructor(message, operation) {
136 super(`KV ${operation} failed: 400 ${message}`);
137 }
138}
139 
140class KVInternalError extends Error {
141 constructor(message, operation) {
142 super(`KV ${operation} failed: 500 ${message}`);
143 }
144}
145 
146export let getTest = {
147 async test(ctrl, env, ctx) {
148 // Test .get()
149 let response = await env.KV.get('success', {});
150 assert.strictEqual(response, 'value-success');
151 
152 response = await env.KV.get('fail-client');
153 assert.strictEqual(response, null);
154 await assert.rejects(env.KV.get('fail-server'), {
155 message: 'KV GET failed: 500 Internal Server Error',
156 });
157 
158 response = await env.KV.get('get-json');
159 assert.strictEqual(response, JSON.stringify({ example: 'values' }));
160 
161 response = await env.KV.get('get-json', 'json');
162 assert.deepStrictEqual(response, { example: 'values' });
163 
164 response = await env.KV.get('success', 'stream');
165 let result = '';
166 const decoder = new TextDecoder();
167 for await (const chunk of response) {
168 result += decoder.decode(chunk, { stream: true });
169 }
170 result += decoder.decode();
171 assert.strictEqual(result, 'value-success');
172 
173 response = await env.KV.get('success', 'arrayBuffer');
174 assert.strictEqual(new TextDecoder().decode(response), 'value-success');
175 },
176};
177 
178export let getBulkTest = {
179 async test(ctrl, env, ctx) {
180 // // Testing .get bulk
181 let response = await env.KV.get(['key1', 'key"2']);
182 let expected = new Map([
183 ['key1', '{"example":"values-key1"}'],
184 ['key"2', '{"example":"values-key\\"2"}'],
185 ]);
186 assert.deepStrictEqual(response, expected);
187 
188 response = await env.KV.get(['key1', 'key2'], {});
189 expected = new Map([
190 ['key1', '{"example":"values-key1"}'],
191 ['key2', '{"example":"values-key2"}'],
192 ]);
193 assert.deepStrictEqual(response, expected);
194 
195 let fullKeysArray = [];
196 let fullResponse = new Map();
197 for (let i = 0; i < 100; i++) {
198 fullKeysArray.push(`key` + i);
199 fullResponse.set(`key` + i, `{"example":"values-key${i}"}`);
200 }
201 
202 response = await env.KV.get(fullKeysArray, {});
203 assert.deepStrictEqual(response, fullResponse);
204 
205 //sending over 100 keys
206 fullKeysArray.push('key100');
207 await assert.rejects(env.KV.get(fullKeysArray), {
208 message: 'KV GET_BULK failed: 400 Bad Request',
209 });
210 
211 response = await env.KV.get(['key1', 'not-found'], { cacheTtl: 100 });
212 expected = new Map([
213 ['key1', '{"example":"values-key1"}'],
214 ['not-found', null],
215 ]);
216 assert.deepStrictEqual(response, expected);
217 
218 await assert.rejects(env.KV.get([]), {
219 message: 'KV GET_BULK failed: 400 Bad Request',
220 });
221 
222 // // get bulk json
223 response = await env.KV.get(['key1', 'key2'], 'json');
224 expected = new Map([
225 ['key1', { example: 'values-key1' }],
226 ['key2', { example: 'values-key2' }],
227 ]);
228 assert.deepStrictEqual(response, expected);
229 
230 // // get bulk json but it is not json - throws error
231 await assert.rejects(env.KV.get(['key-not-json', 'key2'], 'json'), {
232 message: 'KV GET_BULK failed: 500 Internal Server Error',
233 });
234 
235 // // requested type is invalid for bulk get
236 await assert.rejects(env.KV.get(['key-not-json', 'key2'], 'arrayBuffer'), {
237 message: 'KV GET_BULK failed: 500 Internal Server Error',
238 });
239 
240 await assert.rejects(
241 env.KV.get(['key-not-json', 'key2'], { type: 'banana' }),
242 {
243 message: 'KV GET_BULK failed: 500 Internal Server Error',
244 }
245 );
246 
247 // // get with metadata
248 response = await env.KV.getWithMetadata('key1');
249 expected = {
250 value: 'value-key1',
251 metadata: { someMetadataKey: 'someMetadataValue', someUnicodeMeta: '🤓' },
252 cacheStatus: 'HIT',
253 };
254 assert.deepStrictEqual(response, expected);
255 
256 response = await env.KV.getWithMetadata(['key1']);
257 expected = new Map([
258 [
259 'key1',
260 { metadata: 'example-metadata', value: '{"example":"values-key1"}' },
261 ],
262 ]);
263 assert.deepStrictEqual(response, expected);
264 
265 response = await env.KV.getWithMetadata(['key1'], 'json');
266 expected = new Map([
267 [
268 'key1',
269 { metadata: 'example-metadata', value: { example: 'values-key1' } },
270 ],
271 ]);
272 assert.deepStrictEqual(response, expected);
273 response = await env.KV.getWithMetadata(['key1', 'key2'], 'json');
274 expected = new Map([
275 [
276 'key1',
277 { metadata: 'example-metadata', value: { example: 'values-key1' } },
278 ],
279 [
280 'key2',
281 { metadata: 'example-metadata', value: { example: 'values-key2' } },
282 ],
283 ]);
284 assert.deepStrictEqual(response, expected);
285 },
286};
287 
288export let deleteBulkTest = {
289 async test(ctrl, env, ctx) {
290 // Single key
291 let result = await env.KV.deleteBulk('success');
292 assert.strictEqual(result, undefined);
293 
294 // Failure
295 await assert.rejects(env.KV.deleteBulk('error'), {
296 message: 'KV DELETE failed: 500 failed to delete a single key',
297 });
298 
299 // Multiple keys
300 result = await env.KV.deleteBulk(['key1', 'key2', 'key3']);
301 assert.strictEqual(result, undefined);
302 
303 // Too many keys
304 await assert.rejects(
305 env.KV.deleteBulk(
306 Array.from({ length: 101 }, (_, index) => `key${index + 1}`)
307 ),
308 {
309 message:
310 'KV DELETE failed: 400 You can delete maximum of 100 keys per request',
311 }
312 );
313 
314 // Multiple keys with failure
315 await assert.rejects(env.KV.deleteBulk(['key1', 'error']), {
316 message: 'KV DELETE failed: 500 failed to delete a single key from batch',
317 });
318 },
319};
320 
321export let listTest = {
322 async test(ctrl, env, ctx) {
323 let result = await env.KV.list({ prefix: 'te' });
324 assert.strictEqual(result.keys.length, 3);
325 assert.strictEqual(result.list_complete, false);
326 assert.strictEqual(result.cursor, '6Ck1la0VxJ0djhidm1MdX2FyD');
327 
328 result = await env.KV.list({ prefix: 'te', limit: 100, cursor: '123' });
329 assert.strictEqual(result.keys.length, 100);
330 assert.strictEqual(result.list_complete, true);
331 
332 result = await env.KV.list({ prefix: 'not-found' });
333 assert.deepEqual(result, {
334 keys: [],
335 list_complete: true,
336 cacheStatus: 'HIT',
337 });
338 },
339};
340 
341export let putTest = {
342 async test(ctrl, env, ctx) {
343 await env.KV.put('foo_with_exp', 'bar1', { expiration: 10 });
344 await env.KV.put('foo_with_expTtl', 'bar2 a bit longer value', {
345 expirationTtl: 15,
346 });
347 const buf = new ArrayBuffer(256);
348 await env.KV.put('foo_with_expTtl', buf, { expirationTtl: 15 });
349 // assert.strictEqual(result.keys.length, 3);
350 // assert.strictEqual(result.list_complete, false);
351 // assert.strictEqual(result.cursor, '6Ck1la0VxJ0djhidm1MdX2FyD');
352 
353 // result = await env.KV.list({ prefix: 'te', limit: 100, cursor: '123' });
354 // assert.strictEqual(result.keys.length, 100);
355 // assert.strictEqual(result.list_complete, true);
356 
357 // result = await env.KV.list({ prefix: 'not-found' });
358 // assert.deepEqual(result, {
359 // keys: [],
360 // list_complete: true,
361 // cacheStatus: null,
362 // });
363 },
364};