Mercurial > hg > nginx-tests
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 ############################################################################### |