Mercurial > hg > nginx-tests
diff 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 |
line wrap: on
line diff
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/js_fetch.t Thu Jan 21 18:19:37 2021 +0000 @@ -0,0 +1,561 @@ +#!/usr/bin/perl + +# (C) Dmitry Volyntsev +# (C) Nginx, Inc. + +# Tests for http njs module, fetch method. + +############################################################################### + +use warnings; +use strict; + +use Test::More; + +use Socket qw/ CRLF /; + +BEGIN { use FindBin; chdir($FindBin::Bin); } + +use lib 'lib'; +use Test::Nginx; + +############################################################################### + +select STDERR; $| = 1; +select STDOUT; $| = 1; + +eval { require JSON::PP; }; +plan(skip_all => "JSON::PP not installed") if $@; + +my $t = Test::Nginx->new()->has(qw/http/) + ->write_file_expand('nginx.conf', <<'EOF'); + +%%TEST_GLOBALS%% + +daemon off; + +events { +} + +http { + %%TEST_GLOBALS_HTTP%% + + js_import test.js; + + server { + listen 127.0.0.1:8080; + server_name localhost; + + location /njs { + js_content test.njs; + } + + location /broken { + js_content test.broken; + } + + location /broken_response { + js_content test.broken_response; + } + + location /body { + js_content test.body; + } + + location /chain { + js_content test.chain; + } + + location /chunked { + js_content test.chunked; + } + + location /header { + js_content test.header; + } + + location /multi { + js_content test.multi; + } + + location /property { + js_content test.property; + } + } + + server { + listen 127.0.0.1:8080; + server_name aaa; + + location /loc { + js_content test.loc; + } + + location /json { } + } + + server { + listen 127.0.0.1:8080; + server_name bbb; + + location /loc { + js_content test.loc; + } + } + + server { + listen 127.0.0.1:8081; + server_name ccc; + + location /loc { + js_content test.loc; + } + } +} + +EOF + +my $p0 = port(8080); +my $p1 = port(8081); +my $p2 = port(8082); + +$t->write_file('json', '{"a":[1,2], "b":{"c":"FIELD"}}'); + +$t->write_file('test.js', <<EOF); + function test_njs(r) { + r.return(200, njs.version); + } + + function body(r) { + var loc = r.args.loc; + var getter = r.args.getter; + + function query(obj) { + var path = r.args.path; + var retval = (getter == 'arrayBuffer') ? Buffer.from(obj).toString() + : obj; + + if (path) { + retval = path.split('.').reduce((a, v) => a[v], obj); + } + + return JSON.stringify(retval); + } + + ngx.fetch(`http://127.0.0.1:$p0/\${loc}`, {headers: {Host: 'aaa'}}) + .then(reply => reply[getter]()) + .then(data => r.return(200, query(data))) + .catch(e => r.return(501, e.message)) + } + + function property(r) { + var opts = {headers:{Host: 'aaa'}}; + + if (r.args.code) { + opts.headers.code = r.args.code; + } + + var p = ngx.fetch('http://127.0.0.1:$p0/loc', opts) + + if (r.args.readBody) { + p = p.then(rep => + rep.text().then(body => {rep.text = body; return rep;})) + } + + p.then(reply => r.return(200, reply[r.args.pr])) + .catch(e => r.return(501, e.message)) + } + + function process_errors(r, tests) { + var results = []; + + tests.forEach(args => { + ngx.fetch.apply(r, args) + .then(reply => { + r.return(400, '["unexpected then"]'); + }) + .catch(e => { + results.push(e.message); + + if (results.length == tests.length) { + results.sort(); + r.return(200, JSON.stringify(results)); + } + }) + }) + } + + function broken(r) { + var tests = [ + ['http://127.0.0.1:1/loc'], + ['http://127.0.0.1:80800/loc'], + [Symbol.toStringTag], + ['https://127.0.0.1:$p0/loc'], + ]; + + return process_errors(r, tests); + } + + function broken_response(r) { + var tests = [ + ['http://127.0.0.1:$p2/status_line'], + ['http://127.0.0.1:$p2/length'], + ['http://127.0.0.1:$p2/header'], + ['http://127.0.0.1:$p2/headers'], + ['http://127.0.0.1:$p2/content_length'], + ]; + + return process_errors(r, tests); + } + + function chain(r) { + var results = []; + var reqs = [ + ['http://127.0.0.1:$p0/loc', {headers: {Host:'aaa'}}], + ['http://127.0.0.1:$p0/loc', {headers: {Host:'bbb'}}], + ]; + + function next(reply) { + if (reqs.length == 0) { + r.return(200, "SUCCESS"); + return; + } + + ngx.fetch.apply(r, reqs.pop()) + .then(next) + .catch(e => r.return(400, e.message)) + } + + next(); + } + + function chunked(r) { + var results = []; + var tests = [ + ['http://127.0.0.1:$p2/big', {max_response_body_size:128000}], + ['http://127.0.0.1:$p2/big/ok', {max_response_body_size:128000}], + ['http://127.0.0.1:$p2/chunked'], + ['http://127.0.0.1:$p2/chunked/ok'], + ['http://127.0.0.1:$p2/chunked/big', {max_response_body_size:128}], + ['http://127.0.0.1:$p2/chunked/big'], + ]; + + function collect(v) { + results.push(v); + + if (results.length == tests.length) { + results.sort(); + r.return(200, JSON.stringify(results)); + } + } + + tests.forEach(args => { + ngx.fetch.apply(r, args) + .then(reply => reply.text()) + .then(body => collect(body.length)) + .catch(e => collect(e.message)) + }) + } + + function header(r) { + var url = `http://127.0.0.1:$p2/\${r.args.loc}`; + var method = r.args.method ? r.args.method : 'get'; + + var p = ngx.fetch(url) + + if (r.args.readBody) { + p = p.then(rep => + rep.text().then(body => {rep.text = body; return rep;})) + } + + p.then(reply => { + var h = reply.headers[method](r.args.h); + r.return(200, njs.dump(h)); + }) + .catch(e => r.return(501, e.message)) + } + + function multi(r) { + var results = []; + var tests = [ + [ + 'http://127.0.0.1:$p0/loc', + { headers: {Code: 201, Host: 'aaa'}}, + ], + [ + 'http://127.0.0.1:$p0/loc', + { method:'POST', headers: {Code: 401, Host: 'bbb'}, body: 'OK'}, + ], + [ + 'http://127.0.0.1:$p1/loc', + { method:'PATCH', + headers: {foo:undefined, bar:'xxx', Host: 'ccc'}}, + ], + ]; + + function cmp(a,b) { + if (a.b > b.b) {return 1;} + if (a.b < b.b) {return -1;} + return 0 + } + + tests.forEach(args => { + ngx.fetch.apply(r, args) + .then(rep => + rep.text().then(body => {rep.text = body; return rep;})) + .then(rep => { + results.push({b:rep.text, + c:rep.status, + u:rep.url}); + + if (results.length == tests.length) { + results.sort(cmp); + r.return(200, JSON.stringify(results)); + } + }) + .catch(e => { + r.return(400, `["\${e.message}"]`); + throw e; + }) + }) + + if (r.args.throw) { + throw 'Oops'; + } + } + + function str(v) { return v ? v : ''}; + + function loc(r) { + var v = r.variables; + var body = str(r.requestText); + var foo = str(r.headersIn.foo); + var bar = str(r.headersIn.bar); + var c = r.headersIn.code ? Number(r.headersIn.code) : 200; + r.return(c, `\${v.host}:\${v.request_method}:\${foo}:\${bar}:\${body}`); + } + + export default {njs: test_njs, body, broken, broken_response, + chain, chunked, header, multi, loc, property}; +EOF + +$t->try_run('no njs.fetch')->plan(27); + +$t->run_daemon(\&http_daemon, port(8082)); +$t->waitforsocket('127.0.0.1:' . port(8082)); + +############################################################################### + +local $TODO = 'not yet' + unless http_get('/njs') =~ /^([.0-9]+)$/m && $1 ge '0.5.1'; + +like(http_get('/body?getter=arrayBuffer&loc=loc'), qr/200 OK.*"aaa:GET:::"$/s, + 'fetch body arrayBuffer'); +like(http_get('/body?getter=text&loc=loc'), qr/200 OK.*"aaa:GET:::"$/s, + 'fetch body text'); +like(http_get('/body?getter=json&loc=json&path=b.c'), + qr/200 OK.*"FIELD"$/s, 'fetch body json'); +like(http_get('/body?getter=json&loc=loc'), qr/501/s, + 'fetch body json invalid'); +like(http_get('/property?pr=bodyUsed'), qr/false$/s, + 'fetch bodyUsed false'); +like(http_get('/property?pr=bodyUsed&readBody=1'), qr/true$/s, + 'fetch bodyUsed true'); +like(http_get('/property?pr=ok'), qr/200 OK.*true$/s, + 'fetch ok true'); +like(http_get('/property?pr=ok&code=401'), qr/200 OK.*false$/s, + 'fetch ok false'); +like(http_get('/property?pr=redirected'), qr/200 OK.*false$/s, + 'fetch redirected false'); +like(http_get('/property?pr=statusText'), qr/200 OK.*OK$/s, + 'fetch statusText OK'); +like(http_get('/property?pr=statusText&code=403'), qr/200 OK.*Forbidden$/s, + 'fetch statusText Forbidden'); +like(http_get('/property?pr=type'), qr/200 OK.*basic$/s, + 'fetch type'); +like(http_get('/header?loc=duplicate_header&h=BAR'), qr/200 OK.*c$/s, + 'fetch header'); +like(http_get('/header?loc=duplicate_header&h=BARR'), qr/200 OK.*null$/s, + 'fetch no header'); +like(http_get('/header?loc=duplicate_header&h=foo'), qr/200 OK.*a,b$/s, + 'fetch header duplicate'); +like(http_get('/header?loc=duplicate_header&h=BAR&method=getAll'), + qr/200 OK.*\['c']$/s, 'fetch getAll header'); +like(http_get('/header?loc=duplicate_header&h=BARR&method=getAll'), + qr/200 OK.*\[]$/s, 'fetch getAll no header'); +like(http_get('/header?loc=duplicate_header&h=FOO&method=getAll'), + qr/200 OK.*\['a','b']$/s, 'fetch getAll duplicate'); +like(http_get('/header?loc=duplicate_header&h=bar&method=has'), + qr/200 OK.*true$/s, 'fetch header has'); +like(http_get('/header?loc=duplicate_header&h=buz&method=has'), + qr/200 OK.*false$/s, 'fetch header does not have'); +like(http_get('/header?loc=chunked/big&h=BAR&readBody=1'), qr/200 OK.*xxx$/s, + 'fetch chunked header'); +is(get_json('/multi'), + '[{"b":"aaa:GET:::","c":201,"u":"http://127.0.0.1:'.$p0.'/loc"},' . + '{"b":"bbb:POST:::OK","c":401,"u":"http://127.0.0.1:'.$p0.'/loc"},' . + '{"b":"ccc:PATCH::xxx:","c":200,"u":"http://127.0.0.1:'.$p1.'/loc"}]', + 'fetch multi'); +like(http_get('/multi?throw=1'), qr/500/s, 'fetch destructor'); +is(get_json('/broken'), + '[' . + '"connect failed",' . + '"failed to convert url arg",' . + '"invalid url",' . + '"unsupported URL prefix"]', 'fetch broken'); +is(get_json('/broken_response'), + '["invalid fetch content length",' . + '"invalid fetch header",' . + '"invalid fetch status line",' . + '"prematurely closed connection",' . + '"prematurely closed connection"]', 'fetch broken response'); +is(get_json('/chunked'), + '[10,100010,25500,' . + '"invalid fetch chunked response",' . + '"prematurely closed connection",' . + '"very large fetch chunked response"]', 'fetch chunked'); +like(http_get('/chain'), qr/200 OK.*SUCCESS$/s, 'fetch chain'); + +############################################################################### + +sub recode { + my $json; + eval { $json = JSON::PP::decode_json(shift) }; + + if ($@) { + return "<failed to parse JSON>"; + } + + JSON::PP->new()->canonical()->encode($json); +} + +sub get_json { + http_get(shift) =~ /\x0d\x0a?\x0d\x0a?(.*)/ms; + recode($1); +} + +############################################################################### + +sub http_daemon { + my $port = shift; + + my $server = IO::Socket::INET->new( + Proto => 'tcp', + LocalAddr => '127.0.0.1:' . $port, + Listen => 5, + Reuse => 1 + ) or die "Can't create listening socket: $!\n"; + + local $SIG{PIPE} = 'IGNORE'; + + while (my $client = $server->accept()) { + $client->autoflush(1); + + my $headers = ''; + my $uri = ''; + + while (<$client>) { + $headers .= $_; + last if (/^\x0d?\x0a?$/); + } + + $uri = $1 if $headers =~ /^\S+\s+([^ ]+)\s+HTTP/i; + + if ($uri eq '/status_line') { + print $client + "HTTP/1.1 2A"; + + } elsif ($uri eq '/content_length') { + print $client + "HTTP/1.1 200 OK" . CRLF . + "Content-Length: " . CRLF . + "Connection: close" . CRLF . + CRLF; + + } elsif ($uri eq '/header') { + print $client + "HTTP/1.1 200 OK" . CRLF . + "@#" . CRLF . + "Connection: close" . CRLF . + CRLF; + + } elsif ($uri eq '/duplicate_header') { + print $client + "HTTP/1.1 200 OK" . CRLF . + "Foo: a" . CRLF . + "bar: c" . CRLF . + "Foo: b" . CRLF . + "Connection: close" . CRLF . + CRLF; + + } elsif ($uri eq '/headers') { + print $client + "HTTP/1.1 200 OK" . CRLF . + "Connection: close" . CRLF; + + } elsif ($uri eq '/length') { + print $client + "HTTP/1.1 200 OK" . CRLF . + "Content-Length: 100" . CRLF . + "Connection: close" . CRLF . + CRLF . + "unfinished" . CRLF; + + } elsif ($uri eq '/big') { + print $client + "HTTP/1.1 200 OK" . CRLF . + "Content-Length: 100100" . CRLF . + "Connection: close" . CRLF . + CRLF; + for (1 .. 1000) { + print $client ("X" x 98) . CRLF; + } + print $client "unfinished" . CRLF; + + } elsif ($uri eq '/big/ok') { + print $client + "HTTP/1.1 200 OK" . CRLF . + "Content-Length: 100010" . CRLF . + "Connection: close" . CRLF . + CRLF; + for (1 .. 1000) { + print $client ("X" x 98) . CRLF; + } + print $client "finished" . CRLF; + + } elsif ($uri eq '/chunked') { + print $client + "HTTP/1.1 200 OK" . CRLF . + "Transfer-Encoding: chunked" . CRLF . + "Connection: close" . CRLF . + CRLF . + "ff" . CRLF . + "unfinished" . CRLF; + + } elsif ($uri eq '/chunked/ok') { + print $client + "HTTP/1.1 200 OK" . CRLF . + "Transfer-Encoding: chunked" . CRLF . + "Connection: close" . CRLF . + CRLF . + "a" . CRLF . + "finished" . CRLF . + CRLF . "0" . CRLF . CRLF; + } elsif ($uri eq '/chunked/big') { + print $client + "HTTP/1.1 200 OK" . CRLF . + "Transfer-Encoding: chunked" . CRLF . + "Bar: xxx" . CRLF . + "Connection: close" . CRLF . + CRLF; + + for (1 .. 100) { + print $client "ff" . CRLF . ("X" x 255) . CRLF; + } + + print $client "0" . CRLF . CRLF; + } + } +} + +###############################################################################