# vim:set ft= ts=4 sw=4 et fdm=marker:

use Test::Nginx::Socket::Lua;

#worker_connections(1014);
#master_on();
#workers(2);
#log_level('warn');

repeat_each(2);
#repeat_each(1);

plan tests => repeat_each() * (blocks() * 2 + 32);

#no_diff();
#no_long_string();
run_tests();

__DATA__

=== TEST 1: basic print
--- config
    location /lua {
        # NOTE: the newline escape sequence must be double-escaped, as nginx config
        # parser will unescape first!
        content_by_lua '
            local ok, err = ngx.print("Hello, Lua!\\n")
            if not ok then
                ngx.log(ngx.ERR, "print failed: ", err)
            end
        ';
    }
--- request
GET /lua
--- response_body
Hello, Lua!
--- no_error_log
[error]
--- grep_error_log eval: qr/lua caching unused lua thread|lua reusing cached lua thread/
--- grep_error_log_out eval
[
    "lua caching unused lua thread\n",
    "lua reusing cached lua thread
lua caching unused lua thread
",
]



=== TEST 2: basic say
--- config
    location /say {
        # NOTE: the newline escape sequence must be double-escaped, as nginx config
        # parser will unescape first!
        content_by_lua '
            local ok, err = ngx.say("Hello, Lua!")
            if not ok then
                ngx.log(ngx.ERR, "say failed: ", err)
                return
            end
            local ok, err = ngx.say("Yay! ", 123)
            if not ok then
                ngx.log(ngx.ERR, "say failed: ", err)
                return
            end
        ';
    }
--- request
GET /say
--- response_body
Hello, Lua!
Yay! 123
--- no_error_log
[error]



=== TEST 3: no ngx.echo
--- config
    location /lua {
        content_by_lua 'ngx.echo("Hello, Lua!\\n")';
    }
--- request
GET /lua
--- response_body_like: 500 Internal Server Error
--- error_code: 500
--- error_log eval
qr/content_by_lua\(nginx\.conf:\d+\):1: attempt to call field 'echo' \(a nil value\)/



=== TEST 4: variable
--- config
    location /lua {
        # NOTE: the newline escape sequence must be double-escaped, as nginx config
        # parser will unescape first!
        content_by_lua 'local v = ngx.var["request_uri"] ngx.print("request_uri: ", v, "\\n")';
    }
--- request
GET /lua?a=1&b=2
--- response_body
request_uri: /lua?a=1&b=2



=== TEST 5: variable (file)
--- config
    location /lua {
        content_by_lua_file html/test.lua;
    }
--- user_files
>>> test.lua
local v = ngx.var["request_uri"]
ngx.print("request_uri: ", v, "\n")
--- request
GET /lua?a=1&b=2
--- response_body
request_uri: /lua?a=1&b=2



=== TEST 6: calc expression
--- config
    location /lua {
        content_by_lua_file html/calc.lua;
    }
--- user_files
>>> calc.lua
local function uri_unescape(uri)
    local function convert(hex)
        return string.char(tonumber("0x"..hex))
    end
    local s = string.gsub(uri, "%%([0-9a-fA-F][0-9a-fA-F])", convert)
    return s
end

local function eval_exp(str)
    return loadstring("return "..str)()
end

local exp_str = ngx.var["arg_exp"]
-- print("exp: '", exp_str, "'\n")
local status, res
status, res = pcall(uri_unescape, exp_str)
if not status then
    ngx.print("error: ", res, "\n")
    return
end
status, res = pcall(eval_exp, res)
if status then
    ngx.print("result: ", res, "\n")
else
    ngx.print("error: ", res, "\n")
end
--- request
GET /lua?exp=1%2B2*math.sin(3)%2Fmath.exp(4)-math.sqrt(2)
--- response_body
result: -0.4090441561579



=== TEST 7: read $arg_xxx
--- config
    location = /lua {
        content_by_lua 'local who = ngx.var.arg_who
            ngx.print("Hello, ", who, "!")';
    }
--- request
GET /lua?who=agentzh
--- response_body chomp
Hello, agentzh!



=== TEST 8: capture location
--- config
    location /other {
        echo "hello, world";
    }

    location /lua {
        content_by_lua 'local res = ngx.location.capture("/other"); ngx.print("status=", res.status, " "); ngx.print("body=", res.body)';
    }
--- request
GET /lua
--- response_body
status=200 body=hello, world



ei= TEST 9: capture non-existed location
--- config
    location /lua {
        content_by_lua 'local res = ngx.location.capture("/other"); ngx.print("status=", res.status)';
    }
--- request
GET /lua
--- response_body: status=404



=== TEST 9: invalid capture location (not as expected...)
--- config
    location /lua {
        content_by_lua 'local res = ngx.location.capture("*(#*"); ngx.say("res=", res.status)';
    }
--- request
GET /lua
--- response_body
res=404



=== TEST 10: nil is "nil"
--- config
    location /lua {
        content_by_lua 'ngx.say(nil)';
    }
--- request
GET /lua
--- response_body
nil



=== TEST 11: write boolean
--- config
    location /lua {
        content_by_lua 'ngx.say(true, " ", false)';
    }
--- request
GET /lua
--- response_body
true false



=== TEST 12: bad argument type to ngx.location.capture
--- config
    location /lua {
        content_by_lua 'ngx.location.capture(nil)';
    }
--- request
GET /lua
--- response_body_like: 500 Internal Server Error
--- error_code: 500



=== TEST 13: capture location (default 0);
--- config
 location /recur {
       content_by_lua '
           local num = tonumber(ngx.var.arg_num) or 0;
           ngx.print("num is: ", num, "\\n");

           if (num > 0) then
               local res = ngx.location.capture("/recur?num="..tostring(num - 1));
               ngx.print("status=", res.status, " ");
               ngx.print("body=", res.body, "\\n");
           else
               ngx.print("end\\n");
           end
           ';
   }
--- request
GET /recur
--- response_body
num is: 0
end



=== TEST 14: capture location
--- config
 location /recur {
       content_by_lua '
           local num = tonumber(ngx.var.arg_num) or 0;
           ngx.print("num is: ", num, "\\n");

           if (num > 0) then
               local res = ngx.location.capture("/recur?num="..tostring(num - 1));
               ngx.print("status=", res.status, " ");
               ngx.print("body=", res.body);
           else
               ngx.print("end\\n");
           end
           ';
   }
--- request
GET /recur?num=3
--- response_body
num is: 3
status=200 body=num is: 2
status=200 body=num is: 1
status=200 body=num is: 0
end



=== TEST 15: setting nginx variables from within Lua
--- config
 location /set {
       set $a "";
       content_by_lua 'ngx.var.a = 32; ngx.say(ngx.var.a)';
       add_header Foo $a;
   }
--- request
GET /set
--- response_headers
Foo: 32
--- response_body
32



=== TEST 16: nginx quote sql string 1
--- config
 location /set {
       set $a 'hello\n\r\'"\\';
       content_by_lua 'ngx.say(ngx.quote_sql_str(ngx.var.a))';
   }
--- request
GET /set
--- response_body
'hello\n\r\'\"\\'



=== TEST 17: nginx quote sql string 2
--- config
location /set {
    set $a "hello\n\r'\"\\";
    content_by_lua 'ngx.say(ngx.quote_sql_str(ngx.var.a))';
}
--- request
GET /set
--- response_body
'hello\n\r\'\"\\'



=== TEST 18: use dollar
--- config
location /set {
    content_by_lua '
        local s = "hello 112";
        ngx.say(string.find(s, "%d+$"))';
}
--- request
GET /set
--- response_body
79



=== TEST 19: subrequests do not share variables of main requests by default
--- config
location /sub {
    echo $a;
}
location /parent {
    set $a 12;
    content_by_lua 'local res = ngx.location.capture("/sub"); ngx.print(res.body)';
}
--- request
GET /parent
--- response_body eval: "\n"



=== TEST 20: subrequests can share variables of main requests
--- config
location /sub {
    echo $a;
}
location /parent {
    set $a 12;
    content_by_lua '
        local res = ngx.location.capture(
            "/sub",
            { share_all_vars = true }
        );
        ngx.print(res.body)
    ';
}
--- request
GET /parent
--- response_body
12



=== TEST 21: main requests use subrequests' variables
--- config
location /sub {
    set $a 12;
}
location /parent {
    content_by_lua '
        local res = ngx.location.capture("/sub", { share_all_vars = true });
        ngx.say(ngx.var.a)
    ';
}
--- request
GET /parent
--- response_body
12



=== TEST 22: main requests do NOT use subrequests' variables
--- config
location /sub {
    set $a 12;
}
location /parent {
    content_by_lua '
        local res = ngx.location.capture("/sub", { share_all_vars = false });
        ngx.say(ngx.var.a)
    ';
}
--- request
GET /parent
--- response_body_like eval: "\n"



=== TEST 23: capture location headers
--- config
    location /other {
        default_type 'foo/bar';
        echo "hello, world";
    }

    location /lua {
        content_by_lua '
            local res = ngx.location.capture("/other");
            ngx.say("type: ", res.header["Content-Type"]);
        ';
    }
--- request
GET /lua
--- response_body
type: foo/bar



=== TEST 24: capture location multi-value headers
--- config
    location /other {
        #echo "hello, world";
        content_by_lua '
            ngx.header["Set-Cookie"] = {"a", "hello, world", "foo"}
            local ok, err = ngx.eof()
            if not ok then
                ngx.log(ngx.ERR, "eof failed: ", err)
                return
            end
        ';
    }

    location /lua {
        content_by_lua '
            local res = ngx.location.capture("/other");
            ngx.say("type: ", type(res.header["Set-Cookie"]));
            ngx.say("len: ", #res.header["Set-Cookie"]);
            ngx.say("value: ", table.concat(res.header["Set-Cookie"], "|"))
        ';
    }
--- request
GET /lua
--- response_body
type: table
len: 3
value: a|hello, world|foo
--- no_error_log
[error]



=== TEST 25: capture location headers
--- config
    location /other {
        default_type 'foo/bar';
        content_by_lua '
            ngx.header.Bar = "Bah";
        ';
    }

    location /lua {
        content_by_lua '
            local res = ngx.location.capture("/other");
            ngx.say("type: ", res.header["Content-Type"]);
            ngx.say("Bar: ", res.header["Bar"]);
        ';
    }
--- request
GET /lua
--- response_body
type: foo/bar
Bar: Bah



=== TEST 26: capture location headers
--- config
    location /other {
        default_type 'foo/bar';
        content_by_lua '
            ngx.header.Bar = "Bah";
            ngx.header.Bar = nil;
        ';
    }

    location /lua {
        content_by_lua '
            local res = ngx.location.capture("/other");
            ngx.say("type: ", res.header["Content-Type"]);
            ngx.say("Bar: ", res.header["Bar"] or "nil");
        ';
    }
--- request
GET /lua
--- response_body
type: foo/bar
Bar: nil



=== TEST 27: HTTP 1.0 response
--- config
    location /lua {
        content_by_lua '
            local data = "hello, world"
            ngx.print(data)
        ';
    }
    location /main {
        proxy_pass http://127.0.0.1:$server_port/lua;
    }
--- request
GET /main
--- response_headers
Content-Type: text/plain
--- response_body chop
hello, world
--- no_error_log
[error]
[alert]



=== TEST 28: multiple eof
--- config
    location /lua {
        content_by_lua '
            ngx.say("Hi")

            local ok, err = ngx.eof()
            if not ok then
                ngx.log(ngx.WARN, "eof failed: ", err)
                return
            end

            ok, err = ngx.eof()
            if not ok then
                ngx.log(ngx.WARN, "eof failed: ", err)
                return
            end

        ';
    }
--- request
GET /lua
--- response_body
Hi
--- no_error_log
[error]
--- error_log
eof failed: seen eof



=== TEST 29: nginx vars in script path
--- config
    location ~ ^/lua/(.+)$ {
        content_by_lua_file html/$1.lua;
    }
--- user_files
>>> calc.lua
local a,b = ngx.var.arg_a, ngx.var.arg_b
ngx.say(a+b)
--- request
GET /lua/calc?a=19&b=81
--- response_body
100



=== TEST 30: nginx vars in script path
--- config
    location ~ ^/lua/(.+)$ {
        content_by_lua_file html/$1.lua;
    }
    location /main {
        echo_location /lua/sum a=3&b=2;
        echo_location /lua/diff a=3&b=2;
    }
--- user_files
>>> sum.lua
local a,b = ngx.var.arg_a, ngx.var.arg_b
ngx.say(a+b)
>>> diff.lua
local a,b = ngx.var.arg_a, ngx.var.arg_b
ngx.say(a-b)
--- request
GET /main
--- response_body
5
1



=== TEST 31: basic print (HEAD + HTTP 1.1)
--- config
    location /lua {
        # NOTE: the newline escape sequence must be double-escaped, as nginx config
        # parser will unescape first!
        content_by_lua 'ngx.print("Hello, Lua!\\n")';
    }
--- request
HEAD /lua
--- response_body



=== TEST 32: basic print (HEAD + HTTP 1.0)
--- config
    location /lua {
        # NOTE: the newline escape sequence must be double-escaped, as nginx config
        # parser will unescape first!
        content_by_lua '
            ngx.print("Hello, Lua!\\n")
        ';
    }
--- request
HEAD /lua HTTP/1.0
--- response_headers
!Content-Length
--- response_body



=== TEST 33: headers_sent & HEAD
--- config
    location /lua {
        content_by_lua '
            ngx.say(ngx.headers_sent)
            local ok, err = ngx.flush()
            if not ok then
                ngx.log(ngx.WARN, "failed to flush: ", err)
                return
            end
            ngx.say(ngx.headers_sent)
        ';
    }
--- request
HEAD /lua
--- response_body
--- no_error_log
[error]
--- error_log
failed to flush: header only



=== TEST 34: HEAD & ngx.say
--- config
    location /lua {
        content_by_lua '
            ngx.send_headers()
            local ok, err = ngx.say(ngx.headers_sent)
            if not ok then
                ngx.log(ngx.WARN, "failed to say: ", err)
                return
            end
        ';
    }
--- request
HEAD /lua
--- response_body
--- no_error_log
[error]
--- error_log
failed to say: header only



=== TEST 35: ngx.eof before ngx.say
--- config
    location /lua {
        content_by_lua '
            local ok, err = ngx.eof()
            if not ok then
                ngx.log(ngx.ERR, "eof failed: ", err)
                return
            end

            ok, err = ngx.say(ngx.headers_sent)
            if not ok then
                ngx.log(ngx.WARN, "failed to say: ", err)
                return
            end
        ';
    }
--- request
GET /lua
--- response_body
--- no_error_log
[error]
--- error_log
failed to say: seen eof



=== TEST 36: headers_sent + GET
--- config
    location /lua {
        content_by_lua '
            -- print("headers sent: ", ngx.headers_sent)
            ngx.say(ngx.headers_sent)
            ngx.say(ngx.headers_sent)
            -- ngx.flush()
            ngx.say(ngx.headers_sent)
        ';
    }
--- request
GET /lua
--- response_body
false
true
true



=== TEST 37: HTTP 1.0 response with Content-Length
--- config
    location /lua {
        content_by_lua '
            local data = "hello,\\nworld\\n"
            ngx.header["Content-Length"] = #data
            ngx.say("hello,")
            ngx.flush()
            -- ngx.location.capture("/sleep")
            ngx.say("world")
        ';
    }
    location /sleep {
        echo_sleep 2;
    }
    location /main {
        proxy_pass http://127.0.0.1:$server_port/lua;
    }
--- request
GET /main
--- response_headers
Content-Length: 13
--- response_body
hello,
world
--- timeout: 5
--- no_error_log
[error]
[alert]



=== TEST 38: ngx.print table arguments (github issue #54)
--- config
    location /t {
        content_by_lua 'ngx.print({10, {0, 5}, 15}, 32)';
    }
--- request
    GET /t
--- response_body chop
10051532



=== TEST 39: ngx.say table arguments (github issue #54)
--- config
    location /t {
        content_by_lua 'ngx.say({10, {0, "5"}, 15}, 32)';
    }
--- request
    GET /t
--- response_body
10051532



=== TEST 40: Lua file does not exist
--- config
    location /lua {
        content_by_lua_file html/test2.lua;
    }
--- user_files
>>> test.lua
local v = ngx.var["request_uri"]
ngx.print("request_uri: ", v, "\n")
--- request
GET /lua?a=1&b=2
--- response_body_like: 404 Not Found
--- error_code: 404
--- error_log eval
qr/failed to load external Lua file ".*?test2\.lua": cannot open .*? No such file or directory/



=== TEST 41: .lua file with shebang
--- config
    location /lua {
        content_by_lua_file html/test.lua;
    }
--- user_files
>>> test.lua
#!/bin/lua

ngx.say("line ", debug.getinfo(1).currentline)
--- request
GET /lua?a=1&b=2
--- response_body
line 3
--- no_error_log
[error]



=== TEST 42: syntax error in inlined Lua code
--- config
    location /lua {
        content_by_lua 'for end';
    }
--- request
GET /lua
--- response_body_like: 500 Internal Server Error
--- error_code: 500
--- error_log eval
qr/failed to load inlined Lua code: content_by_lua\(nginx.conf:40\)/



=== TEST 43: syntax error in content_by_lua_block
--- config
    location /lua {

        content_by_lua_block {
            'for end';
        }
    }
--- request
GET /lua
--- response_body_like: 500 Internal Server Error
--- error_code: 500
--- error_log eval
qr/failed to load inlined Lua code: content_by_lua\(nginx.conf:41\)/



=== TEST 44: syntax error in second content_by_lua_block
--- config
    location /foo {
        content_by_lua_block {
            'for end';
        }
    }

    location /lua {
        content_by_lua_block {
            'for end';
        }
    }
--- request
GET /lua
--- response_body_like: 500 Internal Server Error
--- error_code: 500
--- error_log eval
qr/failed to load inlined Lua code: content_by_lua\(nginx.conf:46\)/



=== TEST 45: syntax error in thrid content_by_lua_block
--- config
    location /foo {
        content_by_lua_block {
            'for end';
        }
    }

    location /bar {
        content_by_lua_block {
            'for end';
        }
    }

    location /lua {
        content_by_lua_block {
            'for end';
        }
    }
--- request
GET /lua
--- response_body_like: 500 Internal Server Error
--- error_code: 500
--- error_log eval
qr/failed to load inlined Lua code: content_by_lua\(nginx.conf:52\)/



=== TEST 46: syntax error in included file
--- config
    location /foo {
        content_by_lua_block {
            'for end';
        }
    }

    location /bar {
        content_by_lua_block {
            'for end';
        }
    }

    include ../html/lua.conf;
--- user_files
>>> lua.conf
    location /lua {
        content_by_lua_block {
            'for end';
        }
    }
--- request
GET /lua
--- response_body_like: 500 Internal Server Error
--- error_code: 500
--- error_log
failed to load inlined Lua code: content_by_lua(../html/lua.conf:2):2: unexpected symbol near ''for end''



=== TEST 47: syntax error with very long filename
--- config
    location /foo {
        content_by_lua_block {
            'for end';
        }
    }

    location /bar {
        content_by_lua_block {
            'for end';
        }
    }

    include ../html/1234567890123456789012345678901234.conf;
--- user_files
>>> 1234567890123456789012345678901234.conf
    location /lua {
        content_by_lua_block {
            'for end';
        }
    }
--- request
GET /lua
--- response_body_like: 500 Internal Server Error
--- error_code: 500
--- error_log
failed to load inlined Lua code: content_by_lua(...234567890123456789012345678901234.conf:2)



=== TEST 48: syntax error in /tmp/lua.conf
--- config
    location /foo {
        content_by_lua_block {
            'for end';
        }
    }

    location /bar {
        content_by_lua_block {
            'for end';
        }
    }

    include /tmp/lua.conf;
--- user_files
>>> /tmp/lua.conf
    location /lua {
        content_by_lua_block {
            'for end';
        }
    }
--- request
GET /lua
--- response_body_like: 500 Internal Server Error
--- error_code: 500
--- error_log
failed to load inlined Lua code: content_by_lua(/tmp/lua.conf:2)



=== TEST 49: syntax error in /tmp/12345678901234567890123456789012345.conf
--- config
    location /foo {
        content_by_lua_block {
            'for end';
        }
    }

    location /bar {
        content_by_lua_block {
            'for end';
        }
    }

    include /tmp/12345678901234567890123456789012345.conf;

--- user_files
>>> /tmp/12345678901234567890123456789012345.conf
    location /lua {
        content_by_lua_block {
            'for end';
        }
    }
--- request
GET /lua
--- response_body_like: 500 Internal Server Error
--- error_code: 500
--- error_log
failed to load inlined Lua code: content_by_lua(...345678901234567890123456789012345.conf:2)



=== TEST 50: the error line number greater than 9
--- config
    location /foo {
        content_by_lua_block {
            'for end';
        }
    }

    location /bar {
        content_by_lua_block {
            'for end';
        }
    }

    include /tmp/12345678901234567890123456789012345.conf;

--- user_files
>>> /tmp/12345678901234567890123456789012345.conf
    location /lua {












        content_by_lua_block {
            'for end';
        }
    }
--- request
GET /lua
--- response_body_like: 500 Internal Server Error
--- error_code: 500
--- error_log
failed to load inlined Lua code: content_by_lua(...45678901234567890123456789012345.conf:14)



=== TEST 51: Lua file permission denied
--- config
    location /lua {
        content_by_lua_file /etc/shadow;
    }
--- request
GET /lua
--- response_body_like: 503 Service Temporarily Unavailable
--- error_code: 503



=== TEST 52: send_header trigger filter finalize does not clear the ctx
--- config
    location /lua {
        content_by_lua_block {
            ngx.header["Last-Modified"] = ngx.http_time(ngx.time())
            ngx.send_headers()
            local phase = ngx.get_phase()
        }
        header_filter_by_lua_block {
            ngx.header["X-Hello-World"] = "Hello World"
        }
    }
--- request
GET /lua
--- more_headers
If-Unmodified-Since: Wed, 01 Jan 2020 07:28:00 GMT
--- error_code: 412
--- no_error_log
unknown phase: 0
--- skip_eval: 2:defined($ENV{MOCKEAGAIN}) && ($ENV{MOCKEAGAIN} =~ /w/)
