diff options
author | Dmitry Volyntsev <xeioex@nginx.com> | 2023-05-22 17:59:47 -0700 |
---|---|---|
committer | Dmitry Volyntsev <xeioex@nginx.com> | 2023-05-22 17:59:47 -0700 |
commit | 7c39d2c23a2152f99f1602a0cb81903c6d15e980 (patch) | |
tree | 68292d76ac4b64a458ac6927909355b60b5bb4ee /nginx/t/js_fetch.t | |
parent | 3cf640f5a041bada0d39d32a27494ffe024767ef (diff) | |
download | njs-7c39d2c23a2152f99f1602a0cb81903c6d15e980.tar.gz njs-7c39d2c23a2152f99f1602a0cb81903c6d15e980.zip |
Tests: imported nginx modules tests from nginx-tests.
Diffstat (limited to 'nginx/t/js_fetch.t')
-rw-r--r-- | nginx/t/js_fetch.t | 710 |
1 files changed, 710 insertions, 0 deletions
diff --git a/nginx/t/js_fetch.t b/nginx/t/js_fetch.t new file mode 100644 index 00000000..428be90f --- /dev/null +++ b/nginx/t/js_fetch.t @@ -0,0 +1,710 @@ +#!/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 /body_special { + js_content test.body_special; + } + + location /chain { + js_content test.chain; + } + + location /chunked_ok { + js_content test.chunked_ok; + } + + location /chunked_fail { + js_content test.chunked_fail; + } + + location /header { + js_content test.header; + } + + location /host_header { + js_content test.host_header; + } + + location /header_iter { + js_content test.header_iter; + } + + location /multi { + js_content test.multi; + } + + location /property { + js_content test.property; + } + + location /loc { + js_content test.loc; + } + + location /json { } + } + + server { + listen 127.0.0.1:8081; + server_name localhost; + + location /loc { + js_content test.loc; + } + + location /host { + return 200 $http_host; + } + } +} + +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}`) + .then(reply => reply[getter]()) + .then(data => r.return(200, query(data))) + .catch(e => r.return(501, e.message)) + } + + function property(r) { + var opts = {headers:{}}; + + 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], + ]; + + 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'], + ['http://127.0.0.1:$p1/loc'], + ]; + + 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_ok(r) { + var results = []; + var tests = [ + ['http://127.0.0.1:$p2/big/ok', {max_response_body_size:128000}], + ['http://127.0.0.1:$p2/chunked/ok'], + ['http://127.0.0.1:$p2/chunked/big'], + ]; + + function collect(v) { + results.push(v); + + if (results.length == tests.length) { + r.return(200); + } + } + + tests.forEach(args => { + ngx.fetch.apply(r, args) + .then(reply => reply.text()) + .then(body => collect(body.length)) + }) + } + + function chunked_fail(r) { + var results = []; + var tests = [ + ['http://127.0.0.1:$p2/big', {max_response_body_size:128000}], + ['http://127.0.0.1:$p2/chunked'], + ['http://127.0.0.1:$p2/chunked/big', {max_response_body_size:128}], + ]; + + function collect(v) { + results.push(v); + + if (results.length == tests.length) { + r.return(200); + } + } + + tests.forEach(args => { + ngx.fetch.apply(r, args) + .then(reply => reply.text()) + .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)) + } + + async function host_header(r) { + const reply = await ngx.fetch(`http://127.0.0.1:$p1/host`, + {headers: {Host: r.args.host}}); + const body = await reply.text(); + r.return(200, body); + } + + async function body_special(r) { + let opts = {}; + + if (r.args.method) { + opts.method = r.args.method; + } + + let reply = await ngx.fetch(`http://127.0.0.1:$p2/\${r.args.loc}`, + opts); + let body = await reply.text(); + + r.return(200, body != '' ? body : '<empty>'); + } + + async function header_iter(r) { + let url = `http://127.0.0.1:$p2/\${r.args.loc}`; + + let response = await ngx.fetch(url); + + let headers = response.headers; + let out = []; + for (let key in response.headers) { + if (key != 'Connection') { + out.push(`\${key}:\${headers.get(key)}`); + } + } + + r.return(200, njs.dump(out)); + } + + function multi(r) { + var results = []; + var tests = [ + [ + 'http://127.0.0.1:$p0/loc', + { headers: {Code: 201}}, + ], + [ + 'http://127.0.0.1:$p0/loc', + { method:'POST', headers: {Code: 401}, body: 'OK'}, + ], + [ + 'http://127.0.0.1:$p1/loc', + { method:'PATCH', + headers: {bar:'xxx'}}, + ], + ]; + + 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 bar = str(r.headersIn.bar); + var c = r.headersIn.code ? Number(r.headersIn.code) : 200; + r.return(c, `\${v.request_method}:\${bar}:\${body}`); + } + + export default {njs: test_njs, body, broken, broken_response, body_special, + chain, chunked_ok, chunked_fail, header, header_iter, + host_header, multi, loc, property}; +EOF + +$t->try_run('no njs.fetch')->plan(35); + +$t->run_daemon(\&http_daemon, port(8082)); +$t->waitforsocket('127.0.0.1:' . port(8082)); + +############################################################################### + +like(http_get('/body?getter=arrayBuffer&loc=loc'), qr/200 OK.*"GET::"$/s, + 'fetch body arrayBuffer'); +like(http_get('/body?getter=text&loc=loc'), qr/200 OK.*"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('/body_special?loc=parted'), qr/200 OK.*X{32000}$/s, + 'fetch body parted'); +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":"GET::","c":201,"u":"http://127.0.0.1:'.$p0.'/loc"},' . + '{"b":"PATCH:xxx:","c":200,"u":"http://127.0.0.1:'.$p1.'/loc"},' . + '{"b":"POST::OK","c":401,"u":"http://127.0.0.1:'.$p0.'/loc"}]', + 'fetch multi'); +like(http_get('/multi?throw=1'), qr/500/s, 'fetch destructor'); +like(http_get('/broken'), qr/200/s, 'fetch broken'); +like(http_get('/broken_response'), qr/200/s, 'fetch broken response'); +like(http_get('/chunked_ok'), qr/200/s, 'fetch chunked ok'); +like(http_get('/chunked_fail'), qr/200/s, 'fetch chunked fail'); +like(http_get('/chain'), qr/200 OK.*SUCCESS$/s, 'fetch chain'); + +like(http_get('/header_iter?loc=duplicate_header_large'), + qr/\['A:a','B:a','C:a','D:a','E:a','F:a','G:a','H:a','Moo:a, ?b']$/s, + 'fetch header duplicate large'); + +TODO: { +local $TODO = 'not yet' unless has_version('0.7.7'); + +like(http_get('/body_special?loc=no_content_length'), + qr/200 OK.*CONTENT-BODY$/s, 'fetch body without content-length'); +like(http_get('/body_special?loc=no_content_length/parted'), + qr/200 OK.*X{32000}$/s, 'fetch body without content-length parted'); + +} + +TODO: { +local $TODO = 'not yet' unless has_version('0.7.8'); + +like(http_get('/body_special?loc=head&method=HEAD'), + qr/200 OK.*<empty>$/s, 'fetch head method'); +like(http_get('/body_special?loc=length&method=head'), + qr/200 OK.*<empty>$/s, 'fetch head method lower case'); + +} + +TODO: { +local $TODO = 'not yet' unless has_version('0.8.0'); + +like(http_get('/host_header?host=FOOBAR'), qr/200 OK.*FOOBAR$/s, + 'fetch host header'); +} + +############################################################################### + +sub has_version { + my $need = shift; + + http_get('/njs') =~ /^([.0-9]+)$/m; + + my @v = split(/\./, $1); + my ($n, $v); + + for $n (split(/\./, $need)) { + $v = shift @v || 0; + return 0 if $n > $v; + return 1 if $v > $n; + } + + return 1; +} + +############################################################################### + +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 '/duplicate_header_large') { + print $client + "HTTP/1.1 200 OK" . CRLF . + "A: a" . CRLF . + "B: a" . CRLF . + "C: a" . CRLF . + "D: a" . CRLF . + "E: a" . CRLF . + "F: a" . CRLF . + "G: a" . CRLF . + "H: a" . CRLF . + "Moo: a" . CRLF . + "Moo: 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 '/head') { + print $client + "HTTP/1.1 200 OK" . CRLF . + "Content-Length: 100" . CRLF . + "Connection: close" . CRLF . + CRLF; + + } elsif ($uri eq '/parted') { + print $client + "HTTP/1.1 200 OK" . CRLF . + "Content-Length: 32000" . CRLF . + "Connection: close" . CRLF . + CRLF; + + for (1 .. 4) { + select undef, undef, undef, 0.01; + print $client "X" x 8000; + } + + } elsif ($uri eq '/no_content_length') { + print $client + "HTTP/1.1 200 OK" . CRLF . + "Connection: close" . CRLF . + CRLF . + "CONTENT-BODY"; + + } elsif ($uri eq '/no_content_length/parted') { + print $client + "HTTP/1.1 200 OK" . CRLF . + "Connection: close" . CRLF . + CRLF; + + for (1 .. 4) { + select undef, undef, undef, 0.01; + print $client "X" x 8000; + } + + } 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; + } + } +} + +############################################################################### |