Mercurial > hg > nginx-tests
comparison js_fetch_objects.t @ 1814:1d88487eafbf
Tests: added js tests for Fetch API objects.
In addition, due to the fact that Headers.prototype.keys() now returns
sorted header names one js_fetch.t test became broken. To fix it
without introducing version check the test was changed so headers
received from upstream are already sorted.
author | Dmitry Volyntsev <xeioex@nginx.com> |
---|---|
date | Tue, 13 Dec 2022 08:50:42 -0800 |
parents | |
children | 1792ba2e8026 |
comparison
equal
deleted
inserted
replaced
1813:0a489a9abc7a | 1814:1d88487eafbf |
---|---|
1 #!/usr/bin/perl | |
2 | |
3 # (C) Dmitry Volyntsev | |
4 # (C) Nginx, Inc. | |
5 | |
6 # Tests for http njs module, fetch objects. | |
7 | |
8 ############################################################################### | |
9 | |
10 use warnings; | |
11 use strict; | |
12 | |
13 use Test::More; | |
14 | |
15 BEGIN { use FindBin; chdir($FindBin::Bin); } | |
16 | |
17 use lib 'lib'; | |
18 use Test::Nginx; | |
19 | |
20 ############################################################################### | |
21 | |
22 select STDERR; $| = 1; | |
23 select STDOUT; $| = 1; | |
24 | |
25 my $t = Test::Nginx->new()->has(qw/http rewrite/) | |
26 ->write_file_expand('nginx.conf', <<'EOF'); | |
27 | |
28 %%TEST_GLOBALS%% | |
29 | |
30 daemon off; | |
31 | |
32 events { | |
33 } | |
34 | |
35 http { | |
36 %%TEST_GLOBALS_HTTP%% | |
37 | |
38 js_import test.js; | |
39 | |
40 server { | |
41 listen 127.0.0.1:8080; | |
42 server_name localhost; | |
43 | |
44 location /njs { | |
45 js_content test.njs; | |
46 } | |
47 | |
48 location /headers { | |
49 js_content test.headers; | |
50 } | |
51 | |
52 location /request { | |
53 js_content test.request; | |
54 } | |
55 | |
56 location /response { | |
57 js_content test.response; | |
58 } | |
59 | |
60 location /fetch { | |
61 js_content test.fetch; | |
62 } | |
63 | |
64 location /method { | |
65 return 200 $request_method; | |
66 } | |
67 | |
68 location /header { | |
69 return 200 $http_a; | |
70 } | |
71 | |
72 location /body { | |
73 js_content test.body; | |
74 } | |
75 } | |
76 } | |
77 | |
78 EOF | |
79 | |
80 my $p0 = port(8080); | |
81 | |
82 $t->write_file('test.js', <<EOF); | |
83 function test_njs(r) { | |
84 r.return(200, njs.version); | |
85 } | |
86 | |
87 function header(r) { | |
88 r.return(200, r.headersIn.a); | |
89 } | |
90 | |
91 function body(r) { | |
92 r.return(201, r.requestText); | |
93 } | |
94 | |
95 async function run(r, tests) { | |
96 var fails = []; | |
97 for (var i = 0; i < tests.length; i++) { | |
98 var v, t = tests[i]; | |
99 | |
100 try { | |
101 v = await t[1](); | |
102 | |
103 } catch (e) { | |
104 v = e.message; | |
105 } | |
106 | |
107 if (v != t[2]) { | |
108 fails.push(`\${t[0]}: got "\${v}" expected: "\${t[2]}"\n`); | |
109 } | |
110 } | |
111 | |
112 r.return(fails.length ? 400 : 200, fails); | |
113 } | |
114 | |
115 async function headers(r) { | |
116 const tests = [ | |
117 ['empty', () => { | |
118 var h = new Headers(); | |
119 return h.get('a'); | |
120 }, null], | |
121 ['normal', () => { | |
122 var h = new Headers({a: 'X', b: 'Z'}); | |
123 return `\${h.get('a')} \${h.get('B')}`; | |
124 }, 'X Z'], | |
125 ['trim value', () => { | |
126 var h = new Headers({a: ' X '}); | |
127 return h.get('a'); | |
128 }, 'X'], | |
129 ['invalid header name', () => { | |
130 const valid = "!#\$\%&'*+-.^_`|~0123456789"; | |
131 | |
132 for (var i = 0; i < 128; i++) { | |
133 var c = String.fromCodePoint(i); | |
134 | |
135 if (valid.indexOf(c) != -1 || /[a-zA-Z]+/.test(c)) { | |
136 continue; | |
137 } | |
138 | |
139 try { | |
140 new Headers([[c, 'a']]); | |
141 throw new Error( | |
142 `header with "\${c}" (\${i}) should throw`); | |
143 | |
144 } catch (e) { | |
145 if (e.message != 'invalid header name') { | |
146 throw e; | |
147 } | |
148 } | |
149 } | |
150 | |
151 return 'OK'; | |
152 | |
153 }, 'OK'], | |
154 ['invalid header value', () => { | |
155 var h = new Headers({A: 'aa\x00a'}); | |
156 }, 'invalid header value'], | |
157 ['forbidden header', () => { | |
158 const forbidden = ['Host', 'Connection', 'Content-length']; | |
159 forbidden.forEach(fh => { | |
160 var headers = {}; | |
161 headers[fh] = 'xxx'; | |
162 headers.foo = 'bar'; | |
163 | |
164 var h = new Headers(headers); | |
165 if (h.get(fh) != 'xxx') { | |
166 throw new Error(`forbidden header \${fh}`); | |
167 } | |
168 | |
169 if (h.get('foo') != 'bar') { | |
170 throw new Error( | |
171 `non forbidden header foo: \${h.get('foo')}`); | |
172 } | |
173 }) | |
174 | |
175 return 'OK'; | |
176 | |
177 }, 'OK'], | |
178 ['combine', () => { | |
179 var h = new Headers({a: 'X', A: 'Z'}); | |
180 return h.get('a'); | |
181 }, 'X, Z'], | |
182 ['combine2', () => { | |
183 var h = new Headers([['A', 'x'], ['a', 'z']]); | |
184 return h.get('a'); | |
185 }, 'x, z'], | |
186 ['combine3', () => { | |
187 var h = new Headers(); | |
188 h.append('a', 'A'); | |
189 h.append('a', 'B'); | |
190 h.append('a', 'C'); | |
191 h.append('a', 'D'); | |
192 h.append('a', 'E'); | |
193 h.append('a', 'F'); | |
194 return h.get('a'); | |
195 }, 'A, B, C, D, E, F'], | |
196 ['getAll', () => { | |
197 var h = new Headers({a: 'X', A: 'Z'}); | |
198 return njs.dump(h.getAll('a')); | |
199 }, "['X','Z']"], | |
200 ['inherit', () => { | |
201 var h = new Headers({a: 'X', b: 'Y'}); | |
202 var h2 = new Headers(h); | |
203 h2.append('c', 'Z'); | |
204 return h2.has('a') && h2.has('B') && h2.has('c'); | |
205 }, true], | |
206 ['delete', () => { | |
207 var h = new Headers({a: 'X', b: 'Z'}); | |
208 h.delete('b'); | |
209 return h.get('a') && !h.get('b'); | |
210 }, true], | |
211 ['forEach', () => { | |
212 var r = []; | |
213 var h = new Headers({a: '0', b: '1', c: '2'}); | |
214 h.delete('b'); | |
215 h.append('z', '3'); | |
216 h.append('a', '4'); | |
217 h.append('q', '5'); | |
218 h.forEach((v, k) => { r.push(`\${v}:\${k}`)}) | |
219 return r.join('|'); | |
220 }, 'a:0, 4|c:2|q:5|z:3'], | |
221 ['set', () => { | |
222 var h = new Headers([['A', 'x'], ['a', 'y'], ['a', 'z']]); | |
223 h.set('a', '#'); | |
224 return h.get('a'); | |
225 }, '#'], | |
226 ]; | |
227 | |
228 run(r, tests); | |
229 } | |
230 | |
231 async function request(r) { | |
232 const tests = [ | |
233 ['empty', () => { | |
234 try { | |
235 new Request(); | |
236 throw new Error(`Request() should throw`); | |
237 | |
238 } catch (e) { | |
239 if (e.message != '1st argument is required') { | |
240 throw e; | |
241 } | |
242 } | |
243 | |
244 return 'OK'; | |
245 | |
246 }, 'OK'], | |
247 ['normal', () => { | |
248 var r = new Request("http://nginx.org", | |
249 {headers: {a: 'X', b: 'Y'}}); | |
250 return `\${r.url}: \${r.method} \${r.headers.a}`; | |
251 }, 'http://nginx.org: GET X'], | |
252 ['url trim', () => { | |
253 var r = new Request("\\x00\\x01\\x02\\x03\\x05\\x06\\x07\\x08" | |
254 + "\\x09\\x0a\\x0b\\x0c\\x0d\\x0e\\x0f" | |
255 + "\\x10\\x11\\x12\\x13\\x14\\x15\\x16" | |
256 + "\\x17\\x18\\x19\\x1a\\x1b\\x1c\\x1d" | |
257 + "\\x1e\\x1f\\x20http://nginx.org\\x00" | |
258 + "\\x01\\x02\\x03\\x05\\x06\\x07\\x08" | |
259 + "\\x09\\x0a\\x0b\\x0c\\x0d\\x0e\\x0f" | |
260 + "\\x10\\x11\\x12\\x13\\x14\\x15\\x16" | |
261 + "\\x17\\x18\\x19\\x1a\\x1b\\x1c\\x1d" | |
262 + "\\x1e\\x1f\\x20"); | |
263 return r.url; | |
264 }, 'http://nginx.org'], | |
265 ['read only', () => { | |
266 var r = new Request("http://nginx.org"); | |
267 | |
268 const props = ['bodyUsed', 'cache', 'credentials', 'headers', | |
269 'method', 'mode', 'url']; | |
270 try { | |
271 props.forEach(prop => { | |
272 r[prop] = 1; | |
273 throw new Error( | |
274 `setting read-only \${prop} should throw`); | |
275 }) | |
276 | |
277 } catch (e) { | |
278 if (!e.message.startsWith('Cannot assign to read-only p')) { | |
279 throw e; | |
280 } | |
281 } | |
282 | |
283 return 'OK'; | |
284 | |
285 }, 'OK'], | |
286 ['cache', () => { | |
287 const props = ['default', 'no-cache', 'no-store', 'reload', | |
288 'force-cache', 'only-if-cached', '#']; | |
289 try { | |
290 props.forEach(cv => { | |
291 var r = new Request("http://nginx.org", {cache: cv}); | |
292 if (r.cache != cv) { | |
293 throw new Error(`r.cache != \${cv}`); | |
294 } | |
295 }) | |
296 | |
297 } catch (e) { | |
298 if (!e.message.startsWith('unknown cache type: #')) { | |
299 throw e; | |
300 } | |
301 } | |
302 | |
303 return 'OK'; | |
304 | |
305 }, 'OK'], | |
306 ['credentials', () => { | |
307 const props = ['omit', 'include', 'same-origin', '#']; | |
308 try { | |
309 props.forEach(cr => { | |
310 var r = new Request("http://nginx.org", | |
311 {credentials: cr}); | |
312 if (r.credentials != cr) { | |
313 throw new Error(`r.credentials != \${cr}`); | |
314 } | |
315 }) | |
316 | |
317 } catch (e) { | |
318 if (!e.message.startsWith('unknown credentials type: #')) { | |
319 throw e; | |
320 } | |
321 } | |
322 | |
323 return 'OK'; | |
324 | |
325 }, 'OK'], | |
326 ['forbidden request header', () => { | |
327 const forbidden = ['Host', 'Connection', 'Content-length']; | |
328 forbidden.forEach(fh => { | |
329 var r = new Request("http://nginx.org", | |
330 {headers: {[fh]: 'xxx', foo: 'bar'}}); | |
331 if (r.headers.get(fh) != null) { | |
332 throw new Error(`forbidden header \${fh}`); | |
333 } | |
334 | |
335 if (r.headers.get('foo') != 'bar') { | |
336 throw new Error( | |
337 `non forbidden header foo: \${r.headers.get('foo')}`); | |
338 } | |
339 }) | |
340 | |
341 return 'OK'; | |
342 | |
343 }, 'OK'], | |
344 ['method', () => { | |
345 const methods = ['get', 'hEad', 'Post', 'OPTIONS', 'PUT', | |
346 'DELETE', 'CONNECT']; | |
347 try { | |
348 methods.forEach(m => { | |
349 var r = new Request("http://nginx.org", {method: m}); | |
350 if (r.method != m.toUpperCase()) { | |
351 throw new Error(`r.method != \${m}`); | |
352 } | |
353 }) | |
354 | |
355 } catch (e) { | |
356 if (!e.message.startsWith('forbidden method: CONNECT')) { | |
357 throw e; | |
358 } | |
359 } | |
360 | |
361 return 'OK'; | |
362 | |
363 }, 'OK'], | |
364 ['mode', () => { | |
365 const props = ['same-origin', 'cors', 'no-cors', 'navigate', | |
366 'websocket', '#']; | |
367 try { | |
368 props.forEach(m => { | |
369 var r = new Request("http://nginx.org", {mode: m}); | |
370 if (r.mode != m) { | |
371 throw new Error(`r.mode != \${m}`); | |
372 } | |
373 }) | |
374 | |
375 } catch (e) { | |
376 if (!e.message.startsWith('unknown mode type: #')) { | |
377 throw e; | |
378 } | |
379 } | |
380 | |
381 return 'OK'; | |
382 | |
383 }, 'OK'], | |
384 ['inherit', () => { | |
385 var r = new Request("http://nginx.org", | |
386 {headers: {a: 'X', b: 'Y'}}); | |
387 var r2 = new Request(r); | |
388 r2.headers.append('a', 'Z') | |
389 return `\${r2.url}: \${r2.headers.get('a')}`; | |
390 }, 'http://nginx.org: X, Z'], | |
391 ['inherit2', () => { | |
392 var r = new Request("http://nginx.org", | |
393 {headers: {a: 'X', b: 'Y'}}); | |
394 var r2 = new Request(r); | |
395 r2.headers.append('a', 'Z') | |
396 return `\${r.url}: \${r.headers.get('a')}`; | |
397 }, 'http://nginx.org: X'], | |
398 ['inherit3', () => { | |
399 var h = new Headers(); | |
400 h.append('a', 'X'); | |
401 h.append('a', 'Z'); | |
402 var r = new Request("http://nginx.org", {headers: h}); | |
403 return `\${r.url}: \${r.headers.get('a')}`; | |
404 }, 'http://nginx.org: X, Z'], | |
405 ['content type', async () => { | |
406 var r = new Request("http://nginx.org", | |
407 {body: 'ABC', method: 'POST'}); | |
408 var body = await r.text(); | |
409 return `\${body}: \${r.headers.get('Content-Type')}`; | |
410 }, 'ABC: text/plain;charset=UTF-8'], | |
411 ['GET body', () => { | |
412 try { | |
413 var r = new Request("http://nginx.org", {body: 'ABC'}); | |
414 | |
415 } catch (e) { | |
416 if (!e.message.startsWith('Request body incompatible w')) { | |
417 throw e; | |
418 } | |
419 } | |
420 | |
421 return 'OK'; | |
422 | |
423 }, 'OK'], | |
424 ]; | |
425 | |
426 run(r, tests); | |
427 } | |
428 | |
429 async function response(r) { | |
430 const tests = [ | |
431 ['empty', async () => { | |
432 var r = new Response(); | |
433 var body = await r.text(); | |
434 return `\${r.url}: \${r.status} \${body} \${r.headers.get('a')}`; | |
435 }, ': 200 null'], | |
436 ['normal', async () => { | |
437 var r = new Response("ABC", {headers: {a: 'X', b: 'Y'}}); | |
438 var body = await r.text(); | |
439 return `\${r.url}: \${r.status} \${body} \${r.headers.get('a')}`; | |
440 }, ': 200 ABC X'], | |
441 ['headers', async () => { | |
442 var r = new Response(null, | |
443 {headers: new Headers({a: 'X', b: 'Y'})}); | |
444 var body = await r.text(); | |
445 return `\${r.url}: \${body} \${r.headers.get('b')}`; | |
446 }, ': Y'], | |
447 ['json', async () => { | |
448 var r = new Response('{"a": {"b": 42}}'); | |
449 var json = await r.json(); | |
450 return json.a.b; | |
451 }, 42], | |
452 ['statusText', () => { | |
453 const statuses = ['status text', 'aa\\u0000a']; | |
454 try { | |
455 statuses.forEach(s => { | |
456 var r = new Response(null, {statusText: s}); | |
457 if (r.statusText != s) { | |
458 throw new Error(`r.statusText != \${s}`); | |
459 } | |
460 }) | |
461 | |
462 } catch (e) { | |
463 if (!e.message.startsWith('invalid Response statusText')) { | |
464 throw e; | |
465 } | |
466 } | |
467 | |
468 return 'OK'; | |
469 | |
470 }, 'OK'], | |
471 ]; | |
472 | |
473 run(r, tests); | |
474 } | |
475 | |
476 async function fetch(r) { | |
477 const tests = [ | |
478 ['method', async () => { | |
479 var req = new Request("http://127.0.0.1:$p0/method", | |
480 {method: 'PUT'}); | |
481 var r = await ngx.fetch(req); | |
482 var body = await r.text(); | |
483 return `\${r.url}: \${r.status} \${body} \${r.headers.get('a')}`; | |
484 }, 'http://127.0.0.1:$p0/method: 200 PUT null'], | |
485 ['request body', async () => { | |
486 var req = new Request("http://127.0.0.1:$p0/body", | |
487 {body: 'foo'}); | |
488 var r = await ngx.fetch(req); | |
489 var body = await r.text(); | |
490 return `\${r.url}: \${r.status} \${body}`; | |
491 }, 'http://127.0.0.1:$p0/body: 201 foo'], | |
492 ['request body', async () => { | |
493 var h = new Headers({a: 'X'}); | |
494 h.append('a', 'Z'); | |
495 var req = new Request("http://127.0.0.1:$p0/header", | |
496 {headers: h}); | |
497 var r = await ngx.fetch(req); | |
498 var body = await r.text(); | |
499 return `\${r.url}: \${r.status} \${body}`; | |
500 }, 'http://127.0.0.1:$p0/header: 200 X, Z'], | |
501 ]; | |
502 | |
503 run(r, tests); | |
504 } | |
505 | |
506 export default {njs: test_njs, body, headers, request, response, fetch}; | |
507 EOF | |
508 | |
509 $t->try_run('no njs')->plan(4); | |
510 | |
511 ############################################################################### | |
512 | |
513 local $TODO = 'not yet' unless has_version('0.7.10'); | |
514 | |
515 like(http_get('/headers'), qr/200 OK/s, 'headers tests'); | |
516 like(http_get('/request'), qr/200 OK/s, 'request tests'); | |
517 like(http_get('/response'), qr/200 OK/s, 'response tests'); | |
518 like(http_get('/fetch'), qr/200 OK/s, 'fetch tests'); | |
519 | |
520 ############################################################################### | |
521 | |
522 sub has_version { | |
523 my $need = shift; | |
524 | |
525 http_get('/njs') =~ /^([.0-9]+)$/m; | |
526 | |
527 my @v = split(/\./, $1); | |
528 my ($n, $v); | |
529 | |
530 for $n (split(/\./, $need)) { | |
531 $v = shift @v || 0; | |
532 return 0 if $n > $v; | |
533 return 1 if $v > $n; | |
534 } | |
535 | |
536 return 1; | |
537 } | |
538 | |
539 ############################################################################### |