comparison js_fetch.t @ 1640:67adc5fd0548

Tests: added js tests for ngx.fetch() method.
author Dmitry Volyntsev <xeioex@nginx.com>
date Thu, 21 Jan 2021 18:19:37 +0000
parents
children bdefe70ae1a7
comparison
equal deleted inserted replaced
1639:6c323c672a86 1640:67adc5fd0548
1 #!/usr/bin/perl
2
3 # (C) Dmitry Volyntsev
4 # (C) Nginx, Inc.
5
6 # Tests for http njs module, fetch method.
7
8 ###############################################################################
9
10 use warnings;
11 use strict;
12
13 use Test::More;
14
15 use Socket qw/ CRLF /;
16
17 BEGIN { use FindBin; chdir($FindBin::Bin); }
18
19 use lib 'lib';
20 use Test::Nginx;
21
22 ###############################################################################
23
24 select STDERR; $| = 1;
25 select STDOUT; $| = 1;
26
27 eval { require JSON::PP; };
28 plan(skip_all => "JSON::PP not installed") if $@;
29
30 my $t = Test::Nginx->new()->has(qw/http/)
31 ->write_file_expand('nginx.conf', <<'EOF');
32
33 %%TEST_GLOBALS%%
34
35 daemon off;
36
37 events {
38 }
39
40 http {
41 %%TEST_GLOBALS_HTTP%%
42
43 js_import test.js;
44
45 server {
46 listen 127.0.0.1:8080;
47 server_name localhost;
48
49 location /njs {
50 js_content test.njs;
51 }
52
53 location /broken {
54 js_content test.broken;
55 }
56
57 location /broken_response {
58 js_content test.broken_response;
59 }
60
61 location /body {
62 js_content test.body;
63 }
64
65 location /chain {
66 js_content test.chain;
67 }
68
69 location /chunked {
70 js_content test.chunked;
71 }
72
73 location /header {
74 js_content test.header;
75 }
76
77 location /multi {
78 js_content test.multi;
79 }
80
81 location /property {
82 js_content test.property;
83 }
84 }
85
86 server {
87 listen 127.0.0.1:8080;
88 server_name aaa;
89
90 location /loc {
91 js_content test.loc;
92 }
93
94 location /json { }
95 }
96
97 server {
98 listen 127.0.0.1:8080;
99 server_name bbb;
100
101 location /loc {
102 js_content test.loc;
103 }
104 }
105
106 server {
107 listen 127.0.0.1:8081;
108 server_name ccc;
109
110 location /loc {
111 js_content test.loc;
112 }
113 }
114 }
115
116 EOF
117
118 my $p0 = port(8080);
119 my $p1 = port(8081);
120 my $p2 = port(8082);
121
122 $t->write_file('json', '{"a":[1,2], "b":{"c":"FIELD"}}');
123
124 $t->write_file('test.js', <<EOF);
125 function test_njs(r) {
126 r.return(200, njs.version);
127 }
128
129 function body(r) {
130 var loc = r.args.loc;
131 var getter = r.args.getter;
132
133 function query(obj) {
134 var path = r.args.path;
135 var retval = (getter == 'arrayBuffer') ? Buffer.from(obj).toString()
136 : obj;
137
138 if (path) {
139 retval = path.split('.').reduce((a, v) => a[v], obj);
140 }
141
142 return JSON.stringify(retval);
143 }
144
145 ngx.fetch(`http://127.0.0.1:$p0/\${loc}`, {headers: {Host: 'aaa'}})
146 .then(reply => reply[getter]())
147 .then(data => r.return(200, query(data)))
148 .catch(e => r.return(501, e.message))
149 }
150
151 function property(r) {
152 var opts = {headers:{Host: 'aaa'}};
153
154 if (r.args.code) {
155 opts.headers.code = r.args.code;
156 }
157
158 var p = ngx.fetch('http://127.0.0.1:$p0/loc', opts)
159
160 if (r.args.readBody) {
161 p = p.then(rep =>
162 rep.text().then(body => {rep.text = body; return rep;}))
163 }
164
165 p.then(reply => r.return(200, reply[r.args.pr]))
166 .catch(e => r.return(501, e.message))
167 }
168
169 function process_errors(r, tests) {
170 var results = [];
171
172 tests.forEach(args => {
173 ngx.fetch.apply(r, args)
174 .then(reply => {
175 r.return(400, '["unexpected then"]');
176 })
177 .catch(e => {
178 results.push(e.message);
179
180 if (results.length == tests.length) {
181 results.sort();
182 r.return(200, JSON.stringify(results));
183 }
184 })
185 })
186 }
187
188 function broken(r) {
189 var tests = [
190 ['http://127.0.0.1:1/loc'],
191 ['http://127.0.0.1:80800/loc'],
192 [Symbol.toStringTag],
193 ['https://127.0.0.1:$p0/loc'],
194 ];
195
196 return process_errors(r, tests);
197 }
198
199 function broken_response(r) {
200 var tests = [
201 ['http://127.0.0.1:$p2/status_line'],
202 ['http://127.0.0.1:$p2/length'],
203 ['http://127.0.0.1:$p2/header'],
204 ['http://127.0.0.1:$p2/headers'],
205 ['http://127.0.0.1:$p2/content_length'],
206 ];
207
208 return process_errors(r, tests);
209 }
210
211 function chain(r) {
212 var results = [];
213 var reqs = [
214 ['http://127.0.0.1:$p0/loc', {headers: {Host:'aaa'}}],
215 ['http://127.0.0.1:$p0/loc', {headers: {Host:'bbb'}}],
216 ];
217
218 function next(reply) {
219 if (reqs.length == 0) {
220 r.return(200, "SUCCESS");
221 return;
222 }
223
224 ngx.fetch.apply(r, reqs.pop())
225 .then(next)
226 .catch(e => r.return(400, e.message))
227 }
228
229 next();
230 }
231
232 function chunked(r) {
233 var results = [];
234 var tests = [
235 ['http://127.0.0.1:$p2/big', {max_response_body_size:128000}],
236 ['http://127.0.0.1:$p2/big/ok', {max_response_body_size:128000}],
237 ['http://127.0.0.1:$p2/chunked'],
238 ['http://127.0.0.1:$p2/chunked/ok'],
239 ['http://127.0.0.1:$p2/chunked/big', {max_response_body_size:128}],
240 ['http://127.0.0.1:$p2/chunked/big'],
241 ];
242
243 function collect(v) {
244 results.push(v);
245
246 if (results.length == tests.length) {
247 results.sort();
248 r.return(200, JSON.stringify(results));
249 }
250 }
251
252 tests.forEach(args => {
253 ngx.fetch.apply(r, args)
254 .then(reply => reply.text())
255 .then(body => collect(body.length))
256 .catch(e => collect(e.message))
257 })
258 }
259
260 function header(r) {
261 var url = `http://127.0.0.1:$p2/\${r.args.loc}`;
262 var method = r.args.method ? r.args.method : 'get';
263
264 var p = ngx.fetch(url)
265
266 if (r.args.readBody) {
267 p = p.then(rep =>
268 rep.text().then(body => {rep.text = body; return rep;}))
269 }
270
271 p.then(reply => {
272 var h = reply.headers[method](r.args.h);
273 r.return(200, njs.dump(h));
274 })
275 .catch(e => r.return(501, e.message))
276 }
277
278 function multi(r) {
279 var results = [];
280 var tests = [
281 [
282 'http://127.0.0.1:$p0/loc',
283 { headers: {Code: 201, Host: 'aaa'}},
284 ],
285 [
286 'http://127.0.0.1:$p0/loc',
287 { method:'POST', headers: {Code: 401, Host: 'bbb'}, body: 'OK'},
288 ],
289 [
290 'http://127.0.0.1:$p1/loc',
291 { method:'PATCH',
292 headers: {foo:undefined, bar:'xxx', Host: 'ccc'}},
293 ],
294 ];
295
296 function cmp(a,b) {
297 if (a.b > b.b) {return 1;}
298 if (a.b < b.b) {return -1;}
299 return 0
300 }
301
302 tests.forEach(args => {
303 ngx.fetch.apply(r, args)
304 .then(rep =>
305 rep.text().then(body => {rep.text = body; return rep;}))
306 .then(rep => {
307 results.push({b:rep.text,
308 c:rep.status,
309 u:rep.url});
310
311 if (results.length == tests.length) {
312 results.sort(cmp);
313 r.return(200, JSON.stringify(results));
314 }
315 })
316 .catch(e => {
317 r.return(400, `["\${e.message}"]`);
318 throw e;
319 })
320 })
321
322 if (r.args.throw) {
323 throw 'Oops';
324 }
325 }
326
327 function str(v) { return v ? v : ''};
328
329 function loc(r) {
330 var v = r.variables;
331 var body = str(r.requestText);
332 var foo = str(r.headersIn.foo);
333 var bar = str(r.headersIn.bar);
334 var c = r.headersIn.code ? Number(r.headersIn.code) : 200;
335 r.return(c, `\${v.host}:\${v.request_method}:\${foo}:\${bar}:\${body}`);
336 }
337
338 export default {njs: test_njs, body, broken, broken_response,
339 chain, chunked, header, multi, loc, property};
340 EOF
341
342 $t->try_run('no njs.fetch')->plan(27);
343
344 $t->run_daemon(\&http_daemon, port(8082));
345 $t->waitforsocket('127.0.0.1:' . port(8082));
346
347 ###############################################################################
348
349 local $TODO = 'not yet'
350 unless http_get('/njs') =~ /^([.0-9]+)$/m && $1 ge '0.5.1';
351
352 like(http_get('/body?getter=arrayBuffer&loc=loc'), qr/200 OK.*"aaa:GET:::"$/s,
353 'fetch body arrayBuffer');
354 like(http_get('/body?getter=text&loc=loc'), qr/200 OK.*"aaa:GET:::"$/s,
355 'fetch body text');
356 like(http_get('/body?getter=json&loc=json&path=b.c'),
357 qr/200 OK.*"FIELD"$/s, 'fetch body json');
358 like(http_get('/body?getter=json&loc=loc'), qr/501/s,
359 'fetch body json invalid');
360 like(http_get('/property?pr=bodyUsed'), qr/false$/s,
361 'fetch bodyUsed false');
362 like(http_get('/property?pr=bodyUsed&readBody=1'), qr/true$/s,
363 'fetch bodyUsed true');
364 like(http_get('/property?pr=ok'), qr/200 OK.*true$/s,
365 'fetch ok true');
366 like(http_get('/property?pr=ok&code=401'), qr/200 OK.*false$/s,
367 'fetch ok false');
368 like(http_get('/property?pr=redirected'), qr/200 OK.*false$/s,
369 'fetch redirected false');
370 like(http_get('/property?pr=statusText'), qr/200 OK.*OK$/s,
371 'fetch statusText OK');
372 like(http_get('/property?pr=statusText&code=403'), qr/200 OK.*Forbidden$/s,
373 'fetch statusText Forbidden');
374 like(http_get('/property?pr=type'), qr/200 OK.*basic$/s,
375 'fetch type');
376 like(http_get('/header?loc=duplicate_header&h=BAR'), qr/200 OK.*c$/s,
377 'fetch header');
378 like(http_get('/header?loc=duplicate_header&h=BARR'), qr/200 OK.*null$/s,
379 'fetch no header');
380 like(http_get('/header?loc=duplicate_header&h=foo'), qr/200 OK.*a,b$/s,
381 'fetch header duplicate');
382 like(http_get('/header?loc=duplicate_header&h=BAR&method=getAll'),
383 qr/200 OK.*\['c']$/s, 'fetch getAll header');
384 like(http_get('/header?loc=duplicate_header&h=BARR&method=getAll'),
385 qr/200 OK.*\[]$/s, 'fetch getAll no header');
386 like(http_get('/header?loc=duplicate_header&h=FOO&method=getAll'),
387 qr/200 OK.*\['a','b']$/s, 'fetch getAll duplicate');
388 like(http_get('/header?loc=duplicate_header&h=bar&method=has'),
389 qr/200 OK.*true$/s, 'fetch header has');
390 like(http_get('/header?loc=duplicate_header&h=buz&method=has'),
391 qr/200 OK.*false$/s, 'fetch header does not have');
392 like(http_get('/header?loc=chunked/big&h=BAR&readBody=1'), qr/200 OK.*xxx$/s,
393 'fetch chunked header');
394 is(get_json('/multi'),
395 '[{"b":"aaa:GET:::","c":201,"u":"http://127.0.0.1:'.$p0.'/loc"},' .
396 '{"b":"bbb:POST:::OK","c":401,"u":"http://127.0.0.1:'.$p0.'/loc"},' .
397 '{"b":"ccc:PATCH::xxx:","c":200,"u":"http://127.0.0.1:'.$p1.'/loc"}]',
398 'fetch multi');
399 like(http_get('/multi?throw=1'), qr/500/s, 'fetch destructor');
400 is(get_json('/broken'),
401 '[' .
402 '"connect failed",' .
403 '"failed to convert url arg",' .
404 '"invalid url",' .
405 '"unsupported URL prefix"]', 'fetch broken');
406 is(get_json('/broken_response'),
407 '["invalid fetch content length",' .
408 '"invalid fetch header",' .
409 '"invalid fetch status line",' .
410 '"prematurely closed connection",' .
411 '"prematurely closed connection"]', 'fetch broken response');
412 is(get_json('/chunked'),
413 '[10,100010,25500,' .
414 '"invalid fetch chunked response",' .
415 '"prematurely closed connection",' .
416 '"very large fetch chunked response"]', 'fetch chunked');
417 like(http_get('/chain'), qr/200 OK.*SUCCESS$/s, 'fetch chain');
418
419 ###############################################################################
420
421 sub recode {
422 my $json;
423 eval { $json = JSON::PP::decode_json(shift) };
424
425 if ($@) {
426 return "<failed to parse JSON>";
427 }
428
429 JSON::PP->new()->canonical()->encode($json);
430 }
431
432 sub get_json {
433 http_get(shift) =~ /\x0d\x0a?\x0d\x0a?(.*)/ms;
434 recode($1);
435 }
436
437 ###############################################################################
438
439 sub http_daemon {
440 my $port = shift;
441
442 my $server = IO::Socket::INET->new(
443 Proto => 'tcp',
444 LocalAddr => '127.0.0.1:' . $port,
445 Listen => 5,
446 Reuse => 1
447 ) or die "Can't create listening socket: $!\n";
448
449 local $SIG{PIPE} = 'IGNORE';
450
451 while (my $client = $server->accept()) {
452 $client->autoflush(1);
453
454 my $headers = '';
455 my $uri = '';
456
457 while (<$client>) {
458 $headers .= $_;
459 last if (/^\x0d?\x0a?$/);
460 }
461
462 $uri = $1 if $headers =~ /^\S+\s+([^ ]+)\s+HTTP/i;
463
464 if ($uri eq '/status_line') {
465 print $client
466 "HTTP/1.1 2A";
467
468 } elsif ($uri eq '/content_length') {
469 print $client
470 "HTTP/1.1 200 OK" . CRLF .
471 "Content-Length: " . CRLF .
472 "Connection: close" . CRLF .
473 CRLF;
474
475 } elsif ($uri eq '/header') {
476 print $client
477 "HTTP/1.1 200 OK" . CRLF .
478 "@#" . CRLF .
479 "Connection: close" . CRLF .
480 CRLF;
481
482 } elsif ($uri eq '/duplicate_header') {
483 print $client
484 "HTTP/1.1 200 OK" . CRLF .
485 "Foo: a" . CRLF .
486 "bar: c" . CRLF .
487 "Foo: b" . CRLF .
488 "Connection: close" . CRLF .
489 CRLF;
490
491 } elsif ($uri eq '/headers') {
492 print $client
493 "HTTP/1.1 200 OK" . CRLF .
494 "Connection: close" . CRLF;
495
496 } elsif ($uri eq '/length') {
497 print $client
498 "HTTP/1.1 200 OK" . CRLF .
499 "Content-Length: 100" . CRLF .
500 "Connection: close" . CRLF .
501 CRLF .
502 "unfinished" . CRLF;
503
504 } elsif ($uri eq '/big') {
505 print $client
506 "HTTP/1.1 200 OK" . CRLF .
507 "Content-Length: 100100" . CRLF .
508 "Connection: close" . CRLF .
509 CRLF;
510 for (1 .. 1000) {
511 print $client ("X" x 98) . CRLF;
512 }
513 print $client "unfinished" . CRLF;
514
515 } elsif ($uri eq '/big/ok') {
516 print $client
517 "HTTP/1.1 200 OK" . CRLF .
518 "Content-Length: 100010" . CRLF .
519 "Connection: close" . CRLF .
520 CRLF;
521 for (1 .. 1000) {
522 print $client ("X" x 98) . CRLF;
523 }
524 print $client "finished" . CRLF;
525
526 } elsif ($uri eq '/chunked') {
527 print $client
528 "HTTP/1.1 200 OK" . CRLF .
529 "Transfer-Encoding: chunked" . CRLF .
530 "Connection: close" . CRLF .
531 CRLF .
532 "ff" . CRLF .
533 "unfinished" . CRLF;
534
535 } elsif ($uri eq '/chunked/ok') {
536 print $client
537 "HTTP/1.1 200 OK" . CRLF .
538 "Transfer-Encoding: chunked" . CRLF .
539 "Connection: close" . CRLF .
540 CRLF .
541 "a" . CRLF .
542 "finished" . CRLF .
543 CRLF . "0" . CRLF . CRLF;
544 } elsif ($uri eq '/chunked/big') {
545 print $client
546 "HTTP/1.1 200 OK" . CRLF .
547 "Transfer-Encoding: chunked" . CRLF .
548 "Bar: xxx" . CRLF .
549 "Connection: close" . CRLF .
550 CRLF;
551
552 for (1 .. 100) {
553 print $client "ff" . CRLF . ("X" x 255) . CRLF;
554 }
555
556 print $client "0" . CRLF . CRLF;
557 }
558 }
559 }
560
561 ###############################################################################