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 ###############################################################################