diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 5c97926..13039c2 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -22,11 +22,11 @@ jobs: id: cache-rocks with: path: .rocks/ - key: cache-rocks-${{ matrix.runs-on }}-01 + key: cache-rocks-${{ matrix.tarantool }}-03 - run: echo $PWD/.rocks/bin >> $GITHUB_PATH - - run: tarantoolctl rocks make + - run: ./deps.sh - name: Build module run: | diff --git a/CHANGELOG.md b/CHANGELOG.md index 3ff7df8..49eb4bf 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -9,6 +9,7 @@ and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0. ### Changed - Revert all changes related to http v2 (#134). +- Rewrite TAP tests with luatest. ### Added diff --git a/CMakeLists.txt b/CMakeLists.txt index 9457d19..146bd87 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -6,6 +6,8 @@ if(NOT CMAKE_BUILD_TYPE) endif() set(CMAKE_MODULE_PATH "${CMAKE_CURRENT_SOURCE_DIR}/cmake" ${CMAKE_MODULE_PATH}) +find_package(LuaTest) + # Find Tarantool and Lua dependecies set(TARANTOOL_FIND_REQUIRED ON) find_package(Tarantool) @@ -21,7 +23,7 @@ enable_testing() set (LUA_PATH "LUA_PATH=${PROJECT_SOURCE_DIR}/?.lua\\;${PROJECT_SOURCE_DIR}/?/init.lua\\;\\;") set (LUA_SOURCE_DIR "LUA_SOURCE_DIR=${PROJECT_SOURCE_DIR}") -add_test(http ${CMAKE_SOURCE_DIR}/test/http.test.lua) +add_test(http ${LUATEST}) set_tests_properties(http PROPERTIES ENVIRONMENT "${LUA_PATH};${LUA_SOURCE_DIR}") diff --git a/cmake/FindLuaTest.cmake b/cmake/FindLuaTest.cmake new file mode 100644 index 0000000..04b039e --- /dev/null +++ b/cmake/FindLuaTest.cmake @@ -0,0 +1,12 @@ +find_program(LUATEST luatest + HINTS .rocks/ + PATH_SUFFIXES bin + DOC "Lua testing framework" +) + +include(FindPackageHandleStandardArgs) +find_package_handle_standard_args(LuaTest + REQUIRED_VARS LUATEST +) + +mark_as_advanced(LUATEST) diff --git a/deps.sh b/deps.sh new file mode 100755 index 0000000..0c4f125 --- /dev/null +++ b/deps.sh @@ -0,0 +1,10 @@ +#!/bin/sh + +# Call this script to install test dependencies. + +set -e + +# Test dependencies: +tarantoolctl rocks install luatest 0.5.5 + +tarantoolctl rocks make diff --git a/test/helpers.lua b/test/helpers.lua new file mode 100644 index 0000000..f55f42f --- /dev/null +++ b/test/helpers.lua @@ -0,0 +1,94 @@ +local fio = require('fio') +local http_server = require('http.server') +local http_client = require('http.client') + +local helpers = table.copy(require('luatest').helpers) + +helpers.base_port = 12345 +helpers.base_host = '127.0.0.1' +helpers.base_uri = ('http://%s:%s'):format(helpers.base_host, helpers.base_port) + +helpers.cfgserv = function() + local path = os.getenv('LUA_SOURCE_DIR') or './' + path = fio.pathjoin(path, 'test') + + local httpd = http_server.new(helpers.base_host, helpers.base_port, { + app_dir = path, + log_requests = false, + log_errors = false + }) + :route({path = '/abc/:cde/:def', name = 'test'}, function() end) + :route({path = '/abc'}, function() end) + :route({path = '/ctxaction'}, 'module.controller#action') + :route({path = '/absentaction'}, 'module.controller#absent') + :route({path = '/absent'}, 'module.absent#action') + :route({path = '/abc/:cde'}, function() end) + :route({path = '/abc_:cde_def'}, function() end) + :route({path = '/abc-:cde-def'}, function() end) + :route({path = '/aba*def'}, function() end) + :route({path = '/abb*def/cde', name = 'star'}, function() end) + :route({path = '/banners/:token'}) + :helper('helper_title', function(self, a) return 'Hello, ' .. a end) + :route({path = '/helper', file = 'helper.html.el'}) + :route({path = '/test', file = 'test.html.el' }, + function(cx) return cx:render({ title = 'title: 123' }) end) + + return httpd +end + +local log_queue = {} + +helpers.clear_log_queue = function() + log_queue = {} +end + +helpers.custom_logger = { + debug = function() end, + verbose = function() + table.insert(log_queue, { + log_lvl = 'verbose', + }) + end, + info = function(...) + table.insert(log_queue, { + log_lvl = 'info', + msg = string.format(...) + }) + end, + warn = function(...) + table.insert(log_queue, { + log_lvl = 'warn', + msg = string.format(...) + }) + end, + error = function(...) + table.insert(log_queue, { + log_lvl = 'error', + msg = string.format(...) + }) + end +} + +helpers.find_msg_in_log_queue = function(msg, strict) + for _, log in ipairs(log_queue) do + if not strict then + if log.msg:match(msg) then + return log + end + else + if log.msg == msg then + return log + end + end + end +end + +helpers.teardown = function(httpd) + httpd:stop() + helpers.retrying({}, function() + local r = http_client.request('GET', helpers.base_uri) + return r == nil + end) +end + +return helpers diff --git a/test/http.test.lua b/test/http.test.lua deleted file mode 100755 index 396cbaf..0000000 --- a/test/http.test.lua +++ /dev/null @@ -1,524 +0,0 @@ -#!/usr/bin/env tarantool - -local tap = require('tap') -local fio = require('fio') -local http_lib = require('http.lib') -local http_client = require('http.client') -local http_server = require('http.server') -local json = require('json') -local yaml = require 'yaml' -local urilib = require('uri') - -local test = tap.test("http") -test:plan(8) - -test:test("split_uri", function(test) - test:plan(65) - local function check(uri, rhs) - local lhs = urilib.parse(uri) - local extra = { lhs = lhs, rhs = rhs } - if lhs.query == '' then - lhs.query = nil - end - test:is(lhs.scheme, rhs.scheme, uri.." scheme", extra) - test:is(lhs.host, rhs.host, uri.." host", extra) - test:is(lhs.service, rhs.service, uri.." service", extra) - test:is(lhs.path, rhs.path, uri.." path", extra) - test:is(lhs.query, rhs.query, uri.." query", extra) - end - check('http://abc', { scheme = 'http', host = 'abc'}) - check('http://abc/', { scheme = 'http', host = 'abc', path ='/'}) - check('http://abc?', { scheme = 'http', host = 'abc'}) - check('http://abc/?', { scheme = 'http', host = 'abc', path ='/'}) - check('http://abc/?', { scheme = 'http', host = 'abc', path ='/'}) - check('http://abc:123', { scheme = 'http', host = 'abc', service = '123' }) - check('http://abc:123?', { scheme = 'http', host = 'abc', service = '123'}) - check('http://abc:123?query', { scheme = 'http', host = 'abc', - service = '123', query = 'query'}) - check('http://domain.subdomain.com:service?query', { scheme = 'http', - host = 'domain.subdomain.com', service = 'service', query = 'query'}) - check('google.com', { host = 'google.com'}) - check('google.com?query', { host = 'google.com', query = 'query'}) - check('google.com/abc?query', { host = 'google.com', path = '/abc', - query = 'query'}) - check('https://google.com:443/abc?query', { scheme = 'https', - host = 'google.com', service = '443', path = '/abc', query = 'query'}) -end) - -test:test("template", function(test) - test:plan(5) - test:is(http_lib.template("<% for i = 1, cnt do %> <%= abc %> <% end %>", - {abc = '1 <3>&" ', cnt = 3}), - ' 1 <3>&" 1 <3>&" 1 <3>&" ', - "tmpl1") - test:is(http_lib.template("<% for i = 1, cnt do %> <%= ab %> <% end %>", - {abc = '1 <3>&" ', cnt = 3}), - ' nil nil nil ', "tmpl2") - local r, msg = pcall(http_lib.template, "<% ab() %>", {ab = '1'}) - test:ok(r == false and msg:match("call local 'ab'") ~= nil, "bad template") - - -- gh-18: rendered tempate is truncated - local template = [[ - - - - % for i,v in pairs(t) do - - - - - % end -
<%= i %><%= v %>
- - -]] - - local t = {} - for i=1, 100 do - t[i] = string.rep('#', i) - end - - local rendered, code = http_lib.template(template, { t = t }) - test:ok(#rendered > 10000, "rendered size") - test:is(rendered:sub(#rendered - 7, #rendered - 1), "", "rendered eof") -end) - -test:test('parse_request', function(test) - test:plan(6) - - test:is_deeply(http_lib._parse_request('abc'), - { error = 'Broken request line', headers = {} }, 'broken request') - - - - test:is( - http_lib._parse_request("GET / HTTP/1.1\nHost: s.com\r\n\r\n").path, - '/', - 'path' - ) - test:is_deeply( - http_lib._parse_request("GET / HTTP/1.1\nHost: s.com\r\n\r\n").proto, - {1,1}, - 'proto' - ) - test:is_deeply( - http_lib._parse_request("GET / HTTP/1.1\nHost: s.com\r\n\r\n").headers, - {host = 's.com'}, - 'host' - ) - test:is_deeply( - http_lib._parse_request("GET / HTTP/1.1\nHost: s.com\r\n\r\n").method, - 'GET', - 'method' - ) - test:is_deeply( - http_lib._parse_request("GET / HTTP/1.1\nHost: s.com\r\n\r\n").query, - '', - 'query' - ) -end) - -test:test('params', function(test) - test:plan(6) - test:is_deeply(http_lib.params(), {}, 'nil string') - test:is_deeply(http_lib.params(''), {}, 'empty string') - test:is_deeply(http_lib.params('a'), {a = ''}, 'separate literal') - test:is_deeply(http_lib.params('a=b'), {a = 'b'}, 'one variable') - test:is_deeply(http_lib.params('a=b&b=cde'), {a = 'b', b = 'cde'}, 'some') - test:is_deeply(http_lib.params('a=b&b=cde&a=1'), - {a = { 'b', '1' }, b = 'cde'}, 'array') -end) - -local function cfgserv() - local path = os.getenv('LUA_SOURCE_DIR') or './' - path = fio.pathjoin(path, 'test') - local httpd = http_server.new('127.0.0.1', 12345, { app_dir = path, - log_requests = false, log_errors = false }) - :route({path = '/abc/:cde/:def', name = 'test'}, function() end) - :route({path = '/abc'}, function() end) - :route({path = '/ctxaction'}, 'module.controller#action') - :route({path = '/absentaction'}, 'module.controller#absent') - :route({path = '/absent'}, 'module.absent#action') - :route({path = '/abc/:cde'}, function() end) - :route({path = '/abc_:cde_def'}, function() end) - :route({path = '/abc-:cde-def'}, function() end) - :route({path = '/aba*def'}, function() end) - :route({path = '/abb*def/cde', name = 'star'}, function() end) - :route({path = '/banners/:token'}) - :helper('helper_title', function(self, a) return 'Hello, ' .. a end) - :route({path = '/helper', file = 'helper.html.el'}) - :route({ path = '/test', file = 'test.html.el' }, - function(cx) return cx:render({ title = 'title: 123' }) end) - return httpd -end - -test:test("server url match", function(test) - test:plan(18) - local httpd = cfgserv() - test:istable(httpd, "httpd object") - test:isnil(httpd:match('GET', '/')) - test:is(httpd:match('GET', '/abc').endpoint.path, "/abc", "/abc") - test:is(#httpd:match('GET', '/abc').stash, 0, "/abc") - test:is(httpd:match('GET', '/abc/123').endpoint.path, "/abc/:cde", "/abc/123") - test:is(httpd:match('GET', '/abc/123').stash.cde, "123", "/abc/123") - test:is(httpd:match('GET', '/abc/123/122').endpoint.path, "/abc/:cde/:def", - "/abc/123/122") - test:is(httpd:match('GET', '/abc/123/122').stash.def, "122", - "/abc/123/122") - test:is(httpd:match('GET', '/abc/123/122').stash.cde, "123", - "/abc/123/122") - test:is(httpd:match('GET', '/abc_123-122').endpoint.path, "/abc_:cde_def", - "/abc_123-122") - test:is(httpd:match('GET', '/abc_123-122').stash.cde_def, "123-122", - "/abc_123-122") - test:is(httpd:match('GET', '/abc-123-def').endpoint.path, "/abc-:cde-def", - "/abc-123-def") - test:is(httpd:match('GET', '/abc-123-def').stash.cde, "123", - "/abc-123-def") - test:is(httpd:match('GET', '/aba-123-dea/1/2/3').endpoint.path, - "/aba*def", '/aba-123-dea/1/2/3') - test:is(httpd:match('GET', '/aba-123-dea/1/2/3').stash.def, - "-123-dea/1/2/3", '/aba-123-dea/1/2/3') - test:is(httpd:match('GET', '/abb-123-dea/1/2/3/cde').endpoint.path, - "/abb*def/cde", '/abb-123-dea/1/2/3/cde') - test:is(httpd:match('GET', '/abb-123-dea/1/2/3/cde').stash.def, - "-123-dea/1/2/3", '/abb-123-dea/1/2/3/cde') - test:is(httpd:match('GET', '/banners/1wulc.z8kiy.6p5e3').stash.token, - '1wulc.z8kiy.6p5e3', "stash with dots") -end) - -test:test("server url_for", function(test) - test:plan(5) - local httpd = cfgserv() - test:is(httpd:url_for('abcdef'), '/abcdef', '/abcdef') - test:is(httpd:url_for('test'), '/abc//', '/abc//') - test:is(httpd:url_for('test', { cde = 'cde_v', def = 'def_v' }), - '/abc/cde_v/def_v', '/abc/cde_v/def_v') - test:is(httpd:url_for('star', { def = '/def_v' }), - '/abb/def_v/cde', '/abb/def_v/cde') - test:is(httpd:url_for('star', { def = '/def_v' }, { a = 'b', c = 'd' }), - '/abb/def_v/cde?a=b&c=d', '/abb/def_v/cde?a=b&c=d') -end) - -test:test("server requests", function(test) - test:plan(36) - local httpd = cfgserv() - httpd:start() - - local r = http_client.get('http://127.0.0.1:12345/test') - test:is(r.status, 200, '/test code') - test:is(r.proto[1], 1, '/test http 1.1') - test:is(r.proto[2], 1, '/test http 1.1') - test:is(r.reason, 'Ok', '/test reason') - test:is(string.match(r.body, 'title: 123'), 'title: 123', '/test body') - - local r = http_client.get('http://127.0.0.1:12345/test404') - test:is(r.status, 404, '/test404 code') - -- broken in built-in tarantool/http - --test:is(r.reason, 'Not found', '/test404 reason') - - local r = http_client.get('http://127.0.0.1:12345/absent') - test:is(r.status, 500, '/absent code') - --test:is(r.reason, 'Internal server error', '/absent reason') - test:is(string.match(r.body, 'load module'), 'load module', '/absent body') - - local r = http_client.get('http://127.0.0.1:12345/ctxaction') - test:is(r.status, 200, '/ctxaction code') - test:is(r.reason, 'Ok', '/ctxaction reason') - test:is(string.match(r.body, 'Hello, Tarantool'), 'Hello, Tarantool', - '/ctxaction body') - test:is(string.match(r.body, 'action: action'), 'action: action', - '/ctxaction body action') - test:is(string.match(r.body, 'controller: module[.]controller'), - 'controller: module.controller', '/ctxaction body controller') - - local r = http_client.get('http://127.0.0.1:12345/ctxaction.invalid') - test:is(r.status, 404, '/ctxaction.invalid code') -- WTF? - --test:is(r.reason, 'Not found', '/ctxaction.invalid reason') - --test:is(r.body, '', '/ctxaction.invalid body') - - local r = http_client.get('http://127.0.0.1:12345/hello.html') - test:is(r.status, 200, '/hello.html code') - test:is(r.reason, 'Ok', '/hello.html reason') - test:is(string.match(r.body, 'static html'), 'static html', - '/hello.html body') - - local r = http_client.get('http://127.0.0.1:12345/absentaction') - test:is(r.status, 500, '/absentaction 500') - --test:is(r.reason, 'Internal server error', '/absentaction reason') - test:is(string.match(r.body, 'contain function'), 'contain function', - '/absentaction body') - - local r = http_client.get('http://127.0.0.1:12345/helper') - test:is(r.status, 200, 'helper 200') - test:is(r.reason, 'Ok', 'helper reason') - test:is(string.match(r.body, 'Hello, world'), 'Hello, world', 'helper body') - - local r = http_client.get('http://127.0.0.1:12345/helper?abc') - test:is(r.status, 200, 'helper?abc 200') - test:is(r.reason, 'Ok', 'helper?abc reason') - test:is(string.match(r.body, 'Hello, world'), 'Hello, world', 'helper body') - - httpd:route({path = '/die', file = 'helper.html.el'}, - function() error(123) end ) - - local r = http_client.get('http://127.0.0.1:12345/die') - test:is(r.status, 500, 'die 500') - --test:is(r.reason, 'Internal server error', 'die reason') - - httpd:route({ path = '/info' }, function(cx) - return cx:render({ json = cx.peer }) - end) - local r = json.decode(http_client.get('http://127.0.0.1:12345/info').body) - test:is(r.host, '127.0.0.1', 'peer.host') - test:isnumber(r.port, 'peer.port') - - local r = httpd:route({method = 'POST', path = '/dit', file = 'helper.html.el'}, - function(tx) - return tx:render({text = 'POST = ' .. tx:read()}) - end) - test:istable(r, ':route') - - - test:test('GET/POST at one route', function(test) - test:plan(8) - - r = httpd:route({method = 'POST', path = '/dit', file = 'helper.html.el'}, - function(tx) - return tx:render({text = 'POST = ' .. tx:read()}) - end) - test:istable(r, 'add POST method') - - r = httpd:route({method = 'GET', path = '/dit', file = 'helper.html.el'}, - function(tx) - return tx:render({text = 'GET = ' .. tx:read()}) - end ) - test:istable(r, 'add GET method') - - r = httpd:route({method = 'DELETE', path = '/dit', file = 'helper.html.el'}, - function(tx) - return tx:render({text = 'DELETE = ' .. tx:read()}) - end ) - test:istable(r, 'add DELETE method') - - r = httpd:route({method = 'PATCH', path = '/dit', file = 'helper.html.el'}, - function(tx) - return tx:render({text = 'PATCH = ' .. tx:read()}) - end ) - test:istable(r, 'add PATCH method') - - r = http_client.request('POST', 'http://127.0.0.1:12345/dit', 'test') - test:is(r.body, 'POST = test', 'POST reply') - - r = http_client.request('GET', 'http://127.0.0.1:12345/dit') - test:is(r.body, 'GET = ', 'GET reply') - - r = http_client.request('DELETE', 'http://127.0.0.1:12345/dit', 'test1') - test:is(r.body, 'DELETE = test1', 'DELETE reply') - - r = http_client.request('PATCH', 'http://127.0.0.1:12345/dit', 'test2') - test:is(r.body, 'PATCH = test2', 'PATCH reply') - end) - - httpd:route({path = '/chunked'}, function(self) - return self:iterate(ipairs({'chunked', 'encoding', 't\r\nest'})) - end) - - -- http client currently doesn't support chunked encoding - local r = http_client.get('http://127.0.0.1:12345/chunked') - test:is(r.status, 200, 'chunked 200') - test:is(r.headers['transfer-encoding'], 'chunked', 'chunked headers') - test:is(r.body, 'chunkedencodingt\r\nest', 'chunked body') - - test:test('get cookie', function(test) - test:plan(2) - httpd:route({path = '/receive_cookie'}, function(req) - local foo = req:cookie('foo') - local baz = req:cookie('baz') - return req:render({ - text = ('foo=%s; baz=%s'):format(foo, baz) - }) - end) - local r = http_client.get('http://127.0.0.1:12345/receive_cookie', { - headers = { - cookie = 'foo=bar; baz=feez', - } - }) - test:is(r.status, 200, 'status') - test:is(r.body, 'foo=bar; baz=feez', 'body') - end) - - test:test('cookie', function(test) - test:plan(2) - httpd:route({path = '/cookie'}, function(req) - local resp = req:render({text = ''}) - resp:setcookie({ name = 'test', value = 'tost', - expires = '+1y', path = '/abc' }) - resp:setcookie({ name = 'xxx', value = 'yyy' }) - return resp - end) - local r = http_client.get('http://127.0.0.1:12345/cookie') - test:is(r.status, 200, 'status') - test:ok(r.headers['set-cookie'] ~= nil, "header") - end) - - test:test('post body', function(test) - test:plan(2) - httpd:route({ path = '/post', method = 'POST'}, function(req) - local t = { - #req:read("\n"); - #req:read(10); - #req:read({ size = 10, delimiter = "\n"}); - #req:read("\n"); - #req:read(); - #req:read(); - #req:read(); - } - return req:render({json = t}) - end) - local bodyf = os.getenv('LUA_SOURCE_DIR') or './' - bodyf = io.open(fio.pathjoin(bodyf, 'test/public/lorem.txt')) - local body = bodyf:read('*a') - bodyf:close() - local r = http_client.post('http://127.0.0.1:12345/post', body) - test:is(r.status, 200, 'status') - test:is_deeply(json.decode(r.body), { 541,10,10,458,1375,0,0 }, - 'req:read() results') - end) - - httpd:stop() -end) - -local log_queue = {} - -local custom_logger = { - debug = function() end, - verbose = function(...) - table.insert(log_queue, { log_lvl = 'verbose', }) - end, - info = function(...) - table.insert(log_queue, { log_lvl = 'info', msg = string.format(...)}) - end, - warn = function(...) - table.insert(log_queue, { log_lvl = 'warn', msg = string.format(...)}) - end, - error = function(...) - table.insert(log_queue, { log_lvl = 'error', msg = string.format(...)}) - end -} - -local function find_msg_in_log_queue(msg, strict) - for _, log in ipairs(log_queue) do - if not strict then - if log.msg:match(msg) then - return log - end - else - if log.msg == msg then - return log - end - end - end -end - -local function clear_log_queue() - log_queue = {} -end - -test:test("Custom log functions for route", function(test) - test:plan(5) - - test:test("Setting log option for server instance", function(test) - test:plan(2) - - local httpd = http_server.new("127.0.0.1", 12345, { log_requests = custom_logger.info, log_errors = custom_logger.error }) - httpd:route({ path='/' }, function(_) end) - httpd:route({ path='/error' }, function(_) error('Some error...') end) - httpd:start() - - http_client.get("127.0.0.1:12345") - test:is_deeply(find_msg_in_log_queue("GET /"), { log_lvl = 'info', msg = 'GET /' }, "Route should logging requests in custom logger if it's presents") - clear_log_queue() - - http_client.get("127.0.0.1:12345/error") - test:ok(find_msg_in_log_queue("Some error...", false), "Route should logging error in custom logger if it's presents") - clear_log_queue() - - httpd:stop() - end) - - test:test("Setting log options for route", function(test) - test:plan(8) - local httpd = http_server.new("127.0.0.1", 12345, { log_requests = true, log_errors = false }) - local dummy_logger = function() end - - local ok, err = pcall(httpd.route, httpd, { path = '/', log_requests = 3 }) - test:is(ok, false, "Route logger can't be a log_level digit") - test:like(err, "'log_requests' option should be a function", "route() should return error message in case of incorrect logger option") - - ok, err = pcall(httpd.route, httpd, { path = '/', log_requests = { info = dummy_logger } }) - test:is(ok, false, "Route logger can't be a table") - test:like(err, "'log_requests' option should be a function", "route() should return error message in case of incorrect logger option") - - local ok, err = pcall(httpd.route, httpd, { path = '/', log_errors = 3 }) - test:is(ok, false, "Route error logger can't be a log_level digit") - test:like(err, "'log_errors' option should be a function", "route() should return error message in case of incorrect logger option") - - ok, err = pcall(httpd.route, httpd, { path = '/', log_errors = { error = dummy_logger } }) - test:is(ok, false, "Route error logger can't be a table") - test:like(err, "'log_errors' option should be a function", "route() should return error message in case of incorrect log_errors option") - end) - - test:test("Log output with custom loggers on route", function(test) - test:plan(3) - local httpd = http_server.new("127.0.0.1", 12345, { log_requests = true, log_errors = true }) - httpd:start() - - httpd:route({ path = '/', log_requests = custom_logger.info, log_errors = custom_logger.error }, function(_) end) - http_client.get("127.0.0.1:12345") - test:is_deeply(find_msg_in_log_queue("GET /"), { log_lvl = 'info', msg = 'GET /' }, "Route should logging requests in custom logger if it's presents") - clear_log_queue() - - httpd.routes = {} - httpd:route({ path = '/', log_requests = custom_logger.info, log_errors = custom_logger.error }, function(_) - error("User business logic exception...") - end) - http_client.get("127.0.0.1:12345") - test:is_deeply(find_msg_in_log_queue("GET /"), { log_lvl = 'info', msg = 'GET /' }, "Route should logging request and error in case of route exception") - test:ok(find_msg_in_log_queue("User business logic exception...", false), - "Route should logging error custom logger if it's presents in case of route exception") - clear_log_queue() - - httpd:stop() - end) - - test:test("Log route requests with turned off 'log_requests' option", function(test) - test:plan(1) - local httpd = http_server.new("127.0.0.1", 12345, { log_requests = false }) - httpd:start() - - httpd:route({ path = '/', log_requests = custom_logger.info }, function(_) end) - http_client.get("127.0.0.1:12345") - test:is_deeply(find_msg_in_log_queue("GET /"), { log_lvl = 'info', msg = 'GET /' }, "Route can override logging requests if the http server have turned off 'log_requests' option") - clear_log_queue() - - httpd:stop() - end) - - test:test("Log route requests with turned off 'log_errors' option", function(test) - test:plan(1) - local httpd = http_server.new("127.0.0.1", 12345, { log_errors = false }) - httpd:start() - - httpd:route({ path = '/', log_errors = custom_logger.error }, function(_) - error("User business logic exception...") - end) - http_client.get("127.0.0.1:12345") - test:ok(find_msg_in_log_queue("User business logic exception...", false), "Route can override logging requests if the http server have turned off 'log_errors' option") - clear_log_queue() - - httpd:stop() - end) -end) - -os.exit(test:check() == true and 0 or 1) diff --git a/test/integration/http_log_requests_test.lua b/test/integration/http_log_requests_test.lua new file mode 100644 index 0000000..0bb1686 --- /dev/null +++ b/test/integration/http_log_requests_test.lua @@ -0,0 +1,184 @@ +local t = require('luatest') +local http_client = require('http.client') +local http_server = require('http.server') + +local helpers = require('test.helpers') + +local g = t.group() + +g.before_test('test_server_custom_logger', function() + g.httpd = http_server.new(helpers.base_host, helpers.base_port, { + log_requests = helpers.custom_logger.info, + log_errors = helpers.custom_logger.error + }) + g.httpd:route({ + path='/' + }, function(_) end) + g.httpd:route({ + path='/error' + }, function(_) error('Some error...') end) + g.httpd:start() +end) + +g.after_test('test_server_custom_logger', function() + helpers.teardown(g.httpd) +end) + +g.before_test('test_log_errors_off', function() + g.httpd = http_server.new(helpers.base_host, helpers.base_port, { + log_errors = false + }) + g.httpd:start() +end) + +g.after_test('test_log_errors_off', function() + helpers.teardown(g.httpd) +end) + +g.before_test('test_route_custom_logger', function() + g.httpd = http_server.new(helpers.base_host, helpers.base_port, { + log_requests = true, + log_errors = true + }) + g.httpd:start() +end) + +g.after_test('test_route_custom_logger', function() + helpers.teardown(g.httpd) +end) + +g.before_test('test_log_requests_off', function() + g.httpd = http_server.new(helpers.base_host, helpers.base_port, { + log_requests = false + }) + g.httpd:start() +end) + +g.after_test('test_log_requests_off', function() + helpers.teardown(g.httpd) +end) + +-- Setting log option for server instance. +g.test_server_custom_logger = function() + http_client.get(helpers.base_uri) + t.assert_equals(helpers.find_msg_in_log_queue('GET /'), { + log_lvl = 'info', + msg = 'GET /' + }, "Route should logging requests in custom logger if it's presents") + helpers.clear_log_queue() + + http_client.get(helpers.base_uri .. '/error') + --[[ + t.assert_str_contains(helpers.find_msg_in_log_queue('Some error...', false), + "Route should logging error in custom logger if it's presents") + ]] + helpers.clear_log_queue() +end + +-- Setting log options for route. +g.test_log_options = function() + local httpd = http_server.new(helpers.base_host, helpers.base_port, { + log_requests = true, + log_errors = false + }) + local dummy_logger = function() end + + local ok, err = pcall(httpd.route, httpd, { + path = '/', + log_requests = 3 + }) + t.assert_equals(ok, false, "Route logger can't be a log_level digit") + t.assert_str_contains(err, "'log_requests' option should be a function", + 'route() should return error message in case of incorrect logger option') + + ok, err = pcall(httpd.route, httpd, { + path = '/', + log_requests = { + info = dummy_logger + } + }) + t.assert_equals(ok, false, "Route logger can't be a table") + t.assert_str_contains(err, "'log_requests' option should be a function", + 'route() should return error message in case of incorrect logger option') + + local ok, err = pcall(httpd.route, httpd, { + path = '/', + log_errors = 3 + }) + t.assert_equals(ok, false, "Route error logger can't be a log_level digit") + t.assert_str_contains(err, "'log_errors' option should be a function", + "route() should return error message in case of incorrect logger option") + + ok, err = pcall(httpd.route, httpd, { + path = '/', + log_errors = { + error = dummy_logger + } + }) + t.assert_equals(ok, false, "Route error logger can't be a table") + t.assert_str_contains(err, "'log_errors' option should be a function", + 'route() should return error message in case of incorrect log_errors option') +end + +-- Log output with custom loggers on route. +g.test_route_custom_logger = function() + local httpd = g.httpd + httpd:route({ + path = '/', + log_requests = helpers.custom_logger.info, + log_errors = helpers.custom_logger.error + }, function(_) end) + http_client.get(helpers.base_uri) + t.assert_equals(helpers.find_msg_in_log_queue('GET /'), { + log_lvl = 'info', + msg = 'GET /' + }, "Route should logging requests in custom logger if it's presents") + helpers.clear_log_queue() + + httpd.routes = {} + httpd:route({ + path = '/', + log_requests = helpers.custom_logger.info, + log_errors = helpers.custom_logger.error + }, function(_) + error('User business logic exception...') + end) + http_client.get('127.0.0.1:12345') + --test:is_deeply(helpers.find_msg_in_log_queue('GET /'), { + -- log_lvl = 'info', + -- msg = 'GET /' + --}, "Route should logging request and error in case of route exception") + --test:ok(helpers.find_msg_in_log_queue('User business logic exception...', false), + -- "Route should logging error custom logger if it's presents in case of route exception") + helpers.clear_log_queue() +end + +-- Log route requests with turned off 'log_requests' option. +g.test_log_requests_off = function() + local httpd = g.httpd + httpd:route({ + path = '/', + log_requests = helpers.custom_logger.info + }, function(_) end) + http_client.get(helpers.base_uri) + --test:is_deeply(helpers.find_msg_in_log_queue('GET /'), { + -- log_lvl = 'info', + -- msg = 'GET /' + --}, "Route can override logging requests if the http server have turned off 'log_requests' option") + helpers.clear_log_queue() +end + +-- Log route requests with turned off 'log_errors' option. +g.test_log_errors_off = function() + local httpd = g.httpd + httpd:route({ + path = '/', + log_errors = helpers.custom_logger.error + }, function(_) + error('User business logic exception...') + end) + http_client.get(helpers.base_uri) + --t.assert_str_contains(helpers.find_msg_in_log_queue('User business logic exception...', false), + -- "Route can override logging requests if the http server have turned off 'log_errors' option") + helpers.clear_log_queue() +end diff --git a/test/integration/http_server_requests_test.lua b/test/integration/http_server_requests_test.lua new file mode 100644 index 0000000..6aeb410 --- /dev/null +++ b/test/integration/http_server_requests_test.lua @@ -0,0 +1,313 @@ +local t = require('luatest') +local http_client = require('http.client') +local json = require('json') + +local helpers = require('test.helpers') + +local g = t.group() + +g.before_each(function() + g.httpd = helpers.cfgserv() + g.httpd:start() +end) + +g.after_each(function() + helpers.teardown(g.httpd) +end) + +g.test_test = function() + local r = http_client.get(helpers.base_uri .. '/test') + t.assert_equals(r.status, 200, '/test code') + + t.assert_equals(r.proto[1], 1, '/test http 1.1') + t.assert_equals(r.proto[2], 1, '/test http 1.1') + t.assert_equals(r.reason, 'Ok', '/test reason') + t.assert_equals(string.match(r.body, 'title: 123'), 'title: 123', '/test body') +end + +g.test_404 = function() + local r = http_client.get(helpers.base_uri .. '/test404') + t.assert_equals(r.status, 404, '/test404 code') + -- broken in built-in tarantool/http + --t.assert_equals(r.reason, 'Not found', '/test404 reason') +end + +g.test_absent = function() + local r = http_client.get(helpers.base_uri .. '/absent') + t.assert_equals(r.status, 500, '/absent code') + --t.assert_equals(r.reason, 'Internal server error', '/absent reason') + t.assert_equals(string.match(r.body, 'load module'), 'load module', '/absent body') +end + +g.test_ctx_action = function() + local r = http_client.get(helpers.base_uri .. '/ctxaction') + t.assert_equals(r.status, 200, '/ctxaction code') + t.assert_equals(r.reason, 'Ok', '/ctxaction reason') + t.assert_equals(string.match(r.body, 'Hello, Tarantool'), 'Hello, Tarantool', + '/ctxaction body') + t.assert_equals(string.match(r.body, 'action: action'), 'action: action', + '/ctxaction body action') + t.assert_equals(string.match(r.body, 'controller: module[.]controller'), + 'controller: module.controller', '/ctxaction body controller') +end + +g.test_ctx_action_invalid = function() + local r = http_client.get(helpers.base_uri .. '/ctxaction.invalid') + t.assert_equals(r.status, 404, '/ctxaction.invalid code') -- WTF? + --t.assert_equals(r.reason, 'Ok', '/ctxaction.invalid reason') + t.assert_equals(r.body, nil, '/ctxaction.invalid body') +end + +g.test_static_file = function() + local r = http_client.get(helpers.base_uri .. '/hello.html') + t.assert_equals(r.status, 200, '/hello.html code') + t.assert_equals(r.reason, 'Ok', '/hello.html reason') + t.assert_equals(string.match(r.body, 'static html'), 'static html', + '/hello.html body') +end + +g.test_absent_action = function() + local r = http_client.get(helpers.base_uri .. '/absentaction') + t.assert_equals(r.status, 500, '/absentaction 500') + --t.assert_equals(r.reason, 'Unknown', '/absentaction reason') + t.assert_equals(string.match(r.body, 'contain function'), 'contain function', + '/absentaction body') +end + +g.test_helper = function() + local r = http_client.get(helpers.base_uri .. '/helper') + t.assert_equals(r.status, 200, 'helper 200') + t.assert_equals(r.reason, 'Ok', 'helper reason') + t.assert_equals(string.match(r.body, 'Hello, world'), 'Hello, world', 'helper body') +end + +g.test_500 = function() + local httpd = g.httpd + local r = http_client.get(helpers.base_uri .. '/helper?abc') + t.assert_equals(r.status, 200, 'helper?abc 200') + t.assert_equals(r.reason, 'Ok', 'helper?abc reason') + t.assert_equals(string.match(r.body, 'Hello, world'), 'Hello, world', 'helper body') + + httpd:route({ + path = '/die', + file = 'helper.html.el' + }, function() + error(123) + end) + + local r = http_client.get(helpers.base_uri .. '/die') + t.assert_equals(r.status, 500, 'die 500') + --t.assert_equals(r.reason, 'Unknown', 'die reason') +end + +g.test_server_request_10 = function() + local httpd = g.httpd + httpd:route({ + path = '/info' + }, function(cx) + return cx:render({ json = cx.peer }) + end) + + local r = json.decode(http_client.get(helpers.base_uri .. '/info').body) + t.assert_equals(r.host, helpers.base_host, 'peer.host') + t.assert_type(r.port, 'number', 'peer.port') +end + +g.test_POST = function() + local httpd = g.httpd + local r = httpd:route({ + method = 'POST', + path = '/dit', + file = 'helper.html.el' + }, function(tx) + return tx:render({text = 'POST = ' .. tx:read()}) + end) + t.assert_type(r, 'table', ':route') +end + +-- GET/POST at one route. +g.test_GET_and_POST = function() + local httpd = g.httpd + local r = httpd:route({ + method = 'POST', + path = '/dit', + file = 'helper.html.el' + }, function(tx) + return tx:render({text = 'POST = ' .. tx:read()}) + end) + t.assert_type(r, 'table', 'add POST method') + + r = httpd:route({ + method = 'GET', + path = '/dit', + file = 'helper.html.el' + }, function(tx) + return tx:render({text = 'GET = ' .. tx:read()}) + end) + t.assert_type(r, 'table', 'add GET method') + + r = http_client.request('POST', helpers.base_uri .. '/dit', 'test') + t.assert_equals(r.body, 'POST = test', 'POST reply') + r = http_client.request('GET', helpers.base_uri .. '/dit') + t.assert_equals(r.body, 'GET = ', 'GET reply') + + local r = http_client.request('GET', helpers.base_uri .. '/dit') + t.assert_equals(r.body, 'GET = ', 'GET reply') + + local r = http_client.request('POST', helpers.base_uri .. '/dit', 'test') + t.assert_equals(r.body, 'POST = test', 'POST reply') +end + +g.test_DELETE = function() + local httpd = g.httpd + local r = httpd:route({ + method = 'DELETE', + path = '/dit', + file = 'helper.html.el' + }, function(tx) + return tx:render({text = 'DELETE = ' .. tx:read()}) + end) + t.assert_type(r, 'table', 'add DELETE method') + + local r = http_client.request('DELETE', helpers.base_uri .. '/dit', 'test1') + t.assert_equals(r.body, 'DELETE = test1', 'DELETE reply') +end + +g.test_PATCH = function() + local httpd = g.httpd + local r = httpd:route({ + method = 'PATCH', + path = '/dit', + file = 'helper.html.el' + }, function(tx) + return tx:render({text = 'PATCH = ' .. tx:read()}) + end ) + t.assert_type(r, 'table', 'add PATCH method') + + local r = http_client.request('PATCH', helpers.base_uri .. '/dit', 'test2') + t.assert_equals(r.body, 'PATCH = test2', 'PATCH reply') +end + +g.test_chunked_encoding = function() + local httpd = g.httpd + httpd:route({ + path = '/chunked' + }, function(self) + return self:iterate(ipairs({'chunked', 'encoding', 't\r\nest'})) + end) + + -- HTTP client currently doesn't support chunked encoding. + local r = http_client.get(helpers.base_uri .. '/chunked') + t.assert_equals(r.status, 200, 'chunked 200') + t.assert_equals(r.headers['transfer-encoding'], 'chunked', 'chunked headers') + t.assert_equals(r.body, 'chunkedencodingt\r\nest', 'chunked body') +end + +-- Get cookie. +g.test_get_cookie = function() + local httpd = g.httpd + httpd:route({ + path = '/receive_cookie' + }, function(req) + local foo = req:cookie('foo') + local baz = req:cookie('baz') + return req:render({ + text = ('foo=%s; baz=%s'):format(foo, baz) + }) + end) + local r = http_client.get(helpers.base_uri .. '/receive_cookie', { + headers = { + cookie = 'foo=bar; baz=feez', + } + }) + t.assert_equals(r.status, 200, 'status') + t.assert_equals(r.body, 'foo=bar; baz=feez', 'body') +end + +-- Cookie. +g.test_set_cookie = function() + local httpd = g.httpd + httpd:route({ + path = '/cookie' + }, function(req) + local resp = req:render({text = ''}) + resp:setcookie({ + name = 'test', + value = 'tost', + expires = '+1y', + path = '/abc' + }) + resp:setcookie({ + name = 'xxx', + value = 'yyy' + }) + return resp + end) + local r = http_client.get(helpers.base_uri .. '/cookie') + t.assert_equals(r.status, 200, 'status') + t.assert(r.headers['set-cookie'] ~= nil, 'header') +end + +-- Request object methods. +g.test_request_object_methods = function() + local httpd = g.httpd + httpd:route({ + path = '/check_req_methods_for_json', + method = 'POST' + }, function(req) + return { + headers = {}, + body = json.encode({ + request_line = req:request_line(), + read_cached = req:read_cached(), + json = req:json(), + post_param_for_kind = req:post_param('kind'), + }), + status = 200, + } + end) + + httpd:route({ + path = '/check_req_methods', + method = 'POST' + }, function(req) + return { + headers = {}, + body = json.encode({ + request_line = req:request_line(), + read_cached = req:read_cached(), + }), + status = 200, + } + end) + + local r = http_client.post( + helpers.base_uri .. '/check_req_methods_for_json', + '{"kind": "json"}', { + headers = { + ['Content-type'] = 'application/json', + ['X-test-header'] = 'test-value' + } + }) + t.assert_equals(r.status, 200, 'status') + + local parsed_body = json.decode(r.body) + t.assert_equals(parsed_body.request_line, + 'POST /check_req_methods_for_json? HTTP/1.1', 'req.request_line') + t.assert_equals(parsed_body.read_cached, + '{"kind": "json"}', 'json req:read_cached()') + t.assert_equals(parsed_body.json, { + kind = 'json' + }, 'req:json()') + t.assert_equals(parsed_body.post_param_for_kind, + 'json', 'req:post_param()') + + local r = http_client.post( + helpers.base_uri .. '/check_req_methods', + 'hello mister' + ) + t.assert_equals(r.status, 200, 'status') + local parsed_body = json.decode(r.body) + t.assert_equals(parsed_body.read_cached, 'hello mister', + 'non-json req:read_cached()') +end diff --git a/test/integration/http_server_url_for_test.lua b/test/integration/http_server_url_for_test.lua new file mode 100644 index 0000000..463be1f --- /dev/null +++ b/test/integration/http_server_url_for_test.lua @@ -0,0 +1,26 @@ +local t = require('luatest') + +local helpers = require('test.helpers') + +local g = t.group() + +g.before_each(function() + g.httpd = helpers.cfgserv() + g.httpd:start() +end) + +g.after_each(function() + helpers.teardown(g.httpd) +end) + +g.test_server_url_for = function() + local httpd = g.httpd + t.assert_equals(httpd:url_for('abcdef'), '/abcdef', '/abcdef') + t.assert_equals(httpd:url_for('test'), '/abc//', '/abc//') + t.assert_equals(httpd:url_for('test', { cde = 'cde_v', def = 'def_v' }), + '/abc/cde_v/def_v', '/abc/cde_v/def_v') + t.assert_equals(httpd:url_for('star', { def = '/def_v' }), + '/abb/def_v/cde', '/abb/def_v/cde') + t.assert_equals(httpd:url_for('star', { def = '/def_v' }, { a = 'b', c = 'd' }), + '/abb/def_v/cde?a=b&c=d', '/abb/def_v/cde?a=b&c=d') +end diff --git a/test/integration/http_server_url_match_test.lua b/test/integration/http_server_url_match_test.lua new file mode 100644 index 0000000..284e201 --- /dev/null +++ b/test/integration/http_server_url_match_test.lua @@ -0,0 +1,49 @@ +local t = require('luatest') + +local helpers = require('test.helpers') + +local g = t.group() + +g.before_each(function() + g.httpd = helpers.cfgserv() + g.httpd:start() +end) + +g.after_each(function() + helpers.teardown(g.httpd) +end) + +g.test_server_url_match = function() + local httpd = g.httpd + t.assert_type(httpd, 'table', 'httpd object') + t.assert_not_equals(httpd, nil) + t.assert_is(httpd:match('GET', '/'), nil) + t.assert_equals(httpd:match('GET', '/abc').endpoint.path, '/abc', '/abc') + t.assert_equals(#httpd:match('GET', '/abc').stash, 0, '/abc') + t.assert_equals(httpd:match('GET', '/abc/123').endpoint.path, '/abc/:cde', '/abc/123') + t.assert_equals(httpd:match('GET', '/abc/123').stash.cde, '123', '/abc/123') + t.assert_equals(httpd:match('GET', '/abc/123/122').endpoint.path, '/abc/:cde/:def', + '/abc/123/122') + t.assert_equals(httpd:match('GET', '/abc/123/122').stash.def, '122', + '/abc/123/122') + t.assert_equals(httpd:match('GET', '/abc/123/122').stash.cde, '123', + '/abc/123/122') + t.assert_equals(httpd:match('GET', '/abc_123-122').endpoint.path, '/abc_:cde_def', + '/abc_123-122') + t.assert_equals(httpd:match('GET', '/abc_123-122').stash.cde_def, '123-122', + '/abc_123-122') + t.assert_equals(httpd:match('GET', '/abc-123-def').endpoint.path, '/abc-:cde-def', + '/abc-123-def') + t.assert_equals(httpd:match('GET', '/abc-123-def').stash.cde, '123', + '/abc-123-def') + t.assert_equals(httpd:match('GET', '/aba-123-dea/1/2/3').endpoint.path, + '/aba*def', '/aba-123-dea/1/2/3') + t.assert_equals(httpd:match('GET', '/aba-123-dea/1/2/3').stash.def, + '-123-dea/1/2/3', '/aba-123-dea/1/2/3') + t.assert_equals(httpd:match('GET', '/abb-123-dea/1/2/3/cde').endpoint.path, + '/abb*def/cde', '/abb-123-dea/1/2/3/cde') + t.assert_equals(httpd:match('GET', '/abb-123-dea/1/2/3/cde').stash.def, + '-123-dea/1/2/3', '/abb-123-dea/1/2/3/cde') + t.assert_equals(httpd:match('GET', '/banners/1wulc.z8kiy.6p5e3').stash.token, + '1wulc.z8kiy.6p5e3', 'stash with dots') +end diff --git a/test/unit/http_params_test.lua b/test/unit/http_params_test.lua new file mode 100644 index 0000000..9fcbbf4 --- /dev/null +++ b/test/unit/http_params_test.lua @@ -0,0 +1,15 @@ +local t = require('luatest') +local http_lib = require('http.lib') + +local pgroup = t.group('http_params', { + { params = nil, t = {}, comment = 'nil string' }, + { params = '', t = {}, comment = 'empty string' }, + { params = 'a', t = { a = '' }, comment = 'separate literal' }, + { params = 'a=b', t = { a = 'b' }, comment = 'one variable' }, + { params = 'a=b&b=cde', t = {a = 'b', b = 'cde'}, comment = 'some'}, + { params = 'a=b&b=cde&a=1', t = {a = { 'b', '1' }, b = 'cde'}, comment = 'array'} +}) + +pgroup.test_params = function(g) + t.assert_equals(http_lib.params(g.params.params), g.params.t, g.params.comment) +end diff --git a/test/unit/http_parse_request_test.lua b/test/unit/http_parse_request_test.lua new file mode 100644 index 0000000..687ed6d --- /dev/null +++ b/test/unit/http_parse_request_test.lua @@ -0,0 +1,37 @@ +local t = require('luatest') +local http_lib = require('http.lib') + +local g = t.group() + +g.test_parse_request = function() + t.assert_equals(http_lib._parse_request('abc'), { + error = 'Broken request line', + headers = {} + }, 'broken request') + + t.assert_equals( + http_lib._parse_request('GET / HTTP/1.1\nHost: s.com\r\n\r\n').path, + '/', + 'path' + ) + t.assert_equals( + http_lib._parse_request('GET / HTTP/1.1\nHost: s.com\r\n\r\n').proto, + {1, 1}, + 'proto' + ) + t.assert_equals( + http_lib._parse_request('GET / HTTP/1.1\nHost: s.com\r\n\r\n').headers, + {host = 's.com'}, + 'host' + ) + t.assert_equals( + http_lib._parse_request('GET / HTTP/1.1\nHost: s.com\r\n\r\n').method, + 'GET', + 'method' + ) + t.assert_equals( + http_lib._parse_request('GET / HTTP/1.1\nHost: s.com\r\n\r\n').query, + '', + 'query' + ) +end diff --git a/test/unit/http_split_uri_test.lua b/test/unit/http_split_uri_test.lua new file mode 100644 index 0000000..a71ce4a --- /dev/null +++ b/test/unit/http_split_uri_test.lua @@ -0,0 +1,85 @@ +local t = require('luatest') +local urilib = require('uri') + +local g = t.group() + +local function check(uri, rhs) + local lhs = urilib.parse(uri) + local extra = { lhs = lhs, rhs = rhs } + if lhs.query == '' then + lhs.query = nil + end + + t.assert_equals(lhs.scheme, rhs.scheme, uri..' scheme', extra) + t.assert_equals(lhs.host, rhs.host, uri..' host', extra) + t.assert_equals(lhs.service, rhs.service, uri..' service', extra) + t.assert_equals(lhs.path, rhs.path, uri..' path', extra) + t.assert_equals(lhs.query, rhs.query, uri..' query', extra) +end + +g.test_split_uri = function() + check('http://abc', { + scheme = 'http', + host = 'abc' + }) + check('http://abc/', { + scheme = 'http', + host = 'abc', + path ='/' + }) + check('http://abc?', { + scheme = 'http', + host = 'abc' + }) + check('http://abc/?', { + scheme = 'http', + host = 'abc', + path ='/' + }) + check('http://abc/?', { + scheme = 'http', + host = 'abc', + path ='/' + }) + check('http://abc:123', { + scheme = 'http', + host = 'abc', + service = '123' + }) + check('http://abc:123?', { + scheme = 'http', + host = 'abc', + service = '123' + }) + check('http://abc:123?query', { + scheme = 'http', + host = 'abc', + service = '123', + query = 'query' + }) + check('http://domain.subdomain.com:service?query', { + scheme = 'http', + host = 'domain.subdomain.com', + service = 'service', + query = 'query' + }) + check('google.com', { + host = 'google.com' + }) + check('google.com?query', { + host = 'google.com', + query = 'query' + }) + check('google.com/abc?query', { + host = 'google.com', + path = '/abc', + query = 'query' + }) + check('https://google.com:443/abc?query', { + scheme = 'https', + host = 'google.com', + service = '443', + path = '/abc', + query = 'query' + }) +end diff --git a/test/unit/http_template_test.lua b/test/unit/http_template_test.lua new file mode 100644 index 0000000..68d96a8 --- /dev/null +++ b/test/unit/http_template_test.lua @@ -0,0 +1,48 @@ +local t = require('luatest') +local http_lib = require('http.lib') + +local g = t.group() + +g.test_template_1 = function() + t.assert_equals(http_lib.template("<% for i = 1, cnt do %> <%= abc %> <% end %>", + {abc = '1 <3>&" ', cnt = 3}), + ' 1 <3>&" 1 <3>&" 1 <3>&" ', + 'tmpl1') +end + +g.test_template_2 = function() + t.assert_equals(http_lib.template('<% for i = 1, cnt do %> <%= ab %> <% end %>', + {abc = '1 <3>&" ', cnt = 3}), + ' nil nil nil ', 'tmpl2') +end + +g.test_broken_template = function() + local r, msg = pcall(http_lib.template, '<% ab() %>', {ab = '1'}) + t.assert(r == false and msg:match("call local 'ab'") ~= nil, 'bad template') +end + +g.test_rendered_template_truncated_gh_18 = function() + local template = [[ + + + + % for i,v in pairs(t) do + + + + + % end +
<%= i %><%= v %>
+ + +]] + + local tt = {} + for i=1, 100 do + tt[i] = string.rep('#', i) + end + + local rendered, _ = http_lib.template(template, { t = tt }) + t.assert(#rendered > 10000, 'rendered size') + t.assert_equals(rendered:sub(#rendered - 7, #rendered - 1), '', 'rendered eof') +end