local helpers = require "spec.helpers" local cjson = require "cjson" local spy = require "luassert.spy" local splitlines = require("pl.stringx").splitlines for _, strategy in helpers.each_strategy() do describe("helpers [#" .. strategy .. "]: assertions and modifiers", function() local proxy_client local env = { database = strategy, nginx_conf = "spec/fixtures/custom_nginx.template", } lazy_setup(function() local bp = helpers.get_db_utils(strategy, { "routes", "services", }) local service = bp.services:insert { host = helpers.mock_upstream_host, port = helpers.mock_upstream_port, protocol = helpers.mock_upstream_protocol, } bp.routes:insert { hosts = { "mock_upstream" }, protocols = { "http" }, service = service } assert(helpers.start_kong(env)) end) lazy_teardown(function() helpers.stop_kong() end) before_each(function() proxy_client = helpers.proxy_client(5000) end) after_each(function() if proxy_client then proxy_client:close() end end) describe("http_client", function() it("encodes nested tables and arrays in Kong-compatible way when using form-urlencoded content-type", function() local tests = { { input = { names = { "alice", "bob", "casius" } }, expected = { ["names[1]"] = "alice", ["names[2]"] = "bob", ["names[3]"] = "casius" } }, { input = { headers = { location = { "here", "there", "everywhere" } } }, expected = { ["headers.location[1]"] = "here", ["headers.location[2]"] = "there", ["headers.location[3]"] = "everywhere" } }, { input = { ["hello world"] = "foo, bar" } , expected = { ["hello world"] = "foo, bar" } }, { input = { hash = { answer = 42 } }, expected = { ["hash.answer"] = "42" } }, { input = { hash_array = { arr = { "one", "two" } } }, expected = { ["hash_array.arr[1]"] = "one", ["hash_array.arr[2]"] = "two" } }, { input = { array_hash = { { name = "peter" } } }, expected = { ["array_hash[1].name"] = "peter" } }, { input = { array_array = { { "x", "y" } } }, expected = { ["array_array[1][1]"] = "x", ["array_array[1][2]"] = "y" } }, { input = { hybrid = { 1, 2, n = 3 } }, expected = { ["hybrid[1]"] = "1", ["hybrid[2]"] = "2", ["hybrid.n"] = "3" } }, } for i = 1, #tests do local r = proxy_client:get("/", { headers = { ["Content-type"] = "application/x-www-form-urlencoded", host = "mock_upstream", }, body = tests[i].input }) local json = assert.response(r).has.jsonbody() assert.same(tests[i].expected, json.post_data.params) end end) describe("reopen", function() local client local function restart_kong() assert(helpers.restart_kong(env)) -- ensure we can make at least one successful request after restarting assert.eventually(function() local httpc = helpers.proxy_client(1000, 15555) local res = httpc:get("/") res:read_body() httpc:close() assert.res_status(200, res) end) .has_no_error("Kong responds to proxy requests after restart") end before_each(function() client = helpers.proxy_client(1000, 15555) end) after_each(function() if client then client:close() end end) describe("(disabled)", function() it("is the default behavior", function() assert.falsy(client.reopen) end) it("does not retry requests when the connection is closed by the server", function() -- sanity local res, err = client:get("/") assert.res_status(200, res, err) restart_kong() res, err = client:send({ method = "GET", path = "/" }) assert.is_nil(res, "expected request to fail") assert.equals("closed", err) end) it("does not retry requests when the connection is closed by the client", function() -- sanity local res, err = client:get("/") assert.res_status(200, res, err) client:close() res, err = client:send({ method = "GET", path = "/" }) assert.is_nil(res, "expected request to fail") assert.equals("closed", err) end) end) describe("(enabled)", function() it("retries requests when a connection is closed by the server", function() client.reopen = true -- sanity local res, err = client:get("/") assert.res_status(200, res, err) restart_kong() res, err = client:get("/") assert.res_status(200, res, err) restart_kong() res, err = client:head("/") assert.res_status(200, res, err) end) it("retries requests when a connection is closed by the client", function() client.reopen = true -- sanity local res, err = client:get("/") assert.res_status(200, res, err) client:close() res, err = client:head("/") assert.res_status(200, res, err) end) it("does not retry unsafe requests", function() client.reopen = true -- sanity local res, err = client:get("/") assert.res_status(200, res, err) restart_kong() res, err = client:send({ method = "POST", path = "/" }) assert.is_nil(res, "expected request to fail") assert.equals("closed", err) end) it("raises an exception when reconnection fails", function() client.reopen = true -- sanity local res, err = client:get("/") assert.res_status(200, res, err) helpers.stop_kong(nil, true, true) finally(function() helpers.start_kong(env, nil, true) end) assert.error_matches(function() -- using send() instead of get() because get() has an extra -- assert() call that might muddy the waters a little bit client:send({ method = "GET", path = "/" }) end, "connection refused") end) end) end) end) describe("response modifier", function() it("fails with bad input", function() assert.error(function() assert.response().True(true) end) assert.error(function() assert.response(true).True(true) end) assert.error(function() assert.response("bad...").True(true) end) end) it("succeeds with a mock_upstream response", function() local r = assert(proxy_client:send { method = "GET", path = "/request", headers = { host = "mock_upstream", }, }) assert.response(r).True(true) end) it("succeeds with a mock upstream response", function() local r = assert(proxy_client:send { method = "GET", path = "/anything", headers = { host = "mock_upstream", }, }) assert.response(r).True(true) end) end) describe("request modifier", function() it("fails with bad input", function() assert.error(function() assert.request().True(true) end) assert.error(function() assert.request(true).True(true) end) assert.error(function() assert.request("bad... ").True(true) end) end) it("succeeds with a mock_upstream response", function() local r = assert(proxy_client:send { method = "GET", path = "/request", headers = { host = "mock_upstream", }, }) assert.request(r).True(true) end) it("succeeds with a mock_upstream response", function() -- GET request local r = assert(proxy_client:send { method = "GET", path = "/get", headers = { host = "mock_upstream", }, }) assert.request(r).True(true) -- POST request local r = assert(proxy_client:send { method = "POST", path = "/post", body = { v1 = "v2", }, headers = { host = "mock_upstream", ["Content-Type"] = "www-form-urlencoded", }, }) assert.request(r).True(true) end) it("fails with a non mock_upstream response", function() local r = assert(proxy_client:send { method = "GET", path = "/headers", -- this path is not supported, but should yield valid json for the test headers = { host = "127.0.0.1:15555", }, }) assert.error(function() assert.request(r).True(true) end) end) end) describe("contains assertion", function() it("verifies content properly", function() local arr = { "one", "three" } assert.equals(1, assert.contains("one", arr)) assert.not_contains("two", arr) assert.equals(2, assert.contains("ee$", arr, true)) end) end) describe("status assertion", function() it("succeeds with a response", function() local r = assert(proxy_client:send { method = "GET", path = "/get", headers = { host = "mock_upstream", }, }) assert.status(200, r) local body = assert.response(r).has.status(200) assert(cjson.decode(body)) local r = assert(proxy_client:send { method = "GET", path = "/status/404", headers = { host = "mock_upstream", }, }) assert.response(r).has.status(404) end) it("fails with bad input", function() assert.error(function() assert.status(200, nil) end) assert.error(function() assert.status(200, {}) end) end) end) describe("jsonbody assertion", function() it("fails with explicit or no parameters", function() assert.error(function() assert.jsonbody({}) end) assert.error(function() assert.jsonbody() end) end) it("succeeds on a response object on /request", function() local r = assert(proxy_client:send { method = "GET", path = "/request", headers = { host = "mock_upstream", }, }) local json = assert.response(r).has.jsonbody() assert(json.url:find(helpers.mock_upstream_host), "expected a mock_upstream response") end) it("succeeds on a mock_upstream request object on /request", function() local r = assert(proxy_client:send { method = "GET", path = "/request", body = { hello = "world" }, headers = { host = "mock_upstream", ["Content-Type"] = "application/json", }, }) local json = assert.request(r).has.jsonbody() assert.equals("world", json.params.hello) end) it("succeeds on a mock_upstream request object on /post", function() local r = assert(proxy_client:send { method = "POST", path = "/post", body = { hello = "world" }, headers = { host = "mock_upstream", ["Content-Type"] = "application/json", }, }) local json = assert.request(r).has.jsonbody() assert.equals("world", json.params.hello) end) end) describe("header assertion", function() it("checks appropriate response headers", function() local r = assert(proxy_client:send { method = "GET", path = "/request", body = { hello = "world" }, headers = { host = "mock_upstream", ["Content-Type"] = "application/json", }, }) local v1 = assert.response(r).has.header("x-powered-by") local v2 = assert.response(r).has.header("X-POWERED-BY") assert.equals(v1, v2) assert.error(function() assert.response(r).has.header("does not exists") end) end) it("checks appropriate mock_upstream request headers", function() local r = assert(proxy_client:send { method = "GET", path = "/request", headers = { host = "mock_upstream", ["just-a-test-header"] = "just-a-test-value" } }) local v1 = assert.request(r).has.header("just-a-test-header") local v2 = assert.request(r).has.header("just-a-test-HEADER") assert.equals("just-a-test-value", v1) assert.equals(v1, v2) assert.error(function() assert.response(r).has.header("does not exists") end) end) it("checks appropriate mock_upstream request headers", function() local r = assert(proxy_client:send { method = "GET", path = "/get", headers = { host = "mock_upstream", ["just-a-test-header"] = "just-a-test-value" } }) local v1 = assert.request(r).has.header("just-a-test-header") local v2 = assert.request(r).has.header("just-a-test-HEADER") assert.equals("just-a-test-value", v1) assert.equals(v1, v2) assert.error(function() assert.response(r).has.header("does not exists") end) end) end) describe("queryParam assertion", function() it("checks appropriate mock_upstream query parameters", function() local r = assert(proxy_client:send { method = "GET", path = "/request", query = { hello = "world", }, headers = { host = "mock_upstream", }, }) local v1 = assert.request(r).has.queryparam("hello") local v2 = assert.request(r).has.queryparam("HELLO") assert.equals("world", v1) assert.equals(v1, v2) assert.error(function() assert.response(r).has.queryparam("notHere") end) end) it("checks appropriate mock_upstream query parameters", function() local r = assert(proxy_client:send { method = "POST", path = "/post", query = { hello = "world", }, body = { hello2 = "world2", }, headers = { host = "mock_upstream", ["Content-Type"] = "application/json", }, }) local v1 = assert.request(r).has.queryparam("hello") local v2 = assert.request(r).has.queryparam("HELLO") assert.equals("world", v1) assert.equals(v1, v2) assert.error(function() assert.response(r).has.queryparam("notHere") end) end) end) describe("formparam assertion", function() it("checks appropriate mock_upstream url-encoded form parameters", function() local r = assert(proxy_client:send { method = "POST", path = "/request", body = { hello = "world", }, headers = { host = "mock_upstream", ["Content-Type"] = "application/x-www-form-urlencoded", }, }) local v1 = assert.request(r).has.formparam("hello") local v2 = assert.request(r).has.formparam("HELLO") assert.equals("world", v1) assert.equals(v1, v2) assert.error(function() assert.request(r).has.queryparam("notHere") end) end) it("fails with mock_upstream non-url-encoded form data", function() local r = assert(proxy_client:send { method = "POST", path = "/request", body = { hello = "world", }, headers = { host = "mock_upstream", ["Content-Type"] = "application/json", }, }) assert.error(function() assert.request(r).has.formparam("hello") end) end) it("checks appropriate mock_upstream url-encoded form parameters", function() local r = assert(proxy_client:send { method = "POST", path = "/post", body = { hello = "world", }, headers = { host = "mock_upstream", ["Content-Type"] = "application/x-www-form-urlencoded", }, }) local v1 = assert.request(r).has.formparam("hello") local v2 = assert.request(r).has.formparam("HELLO") assert.equals("world", v1) assert.equals(v1, v2) assert.error(function() assert.request(r).has.queryparam("notHere") end) end) it("fails with mock_upstream non-url-encoded form parameters", function() local r = assert(proxy_client:send { method = "POST", path = "/post", body = { hello = "world" }, headers = { host = "mock_upstream", ["Content-Type"] = "application/json", }, }) assert.error(function() assert.request(r).has.formparam("hello") end) end) end) describe("certificates,", function() local function get_cert(server_name) local _, _, stdout = assert(helpers.execute( string.format("echo 'GET /' | openssl s_client -connect 0.0.0.0:%d -servername %s", helpers.get_proxy_port(true), server_name) )) return stdout end it("cn assertion with 2 parameters, positive success", function() local cert = get_cert("ssl1.com") assert.has.cn("localhost", cert) end) it("cn assertion with 2 parameters, positive failure", function() local cert = get_cert("ssl1.com") assert.has.error(function() assert.has.cn("some.other.host.org", cert) end) end) it("cn assertion with 2 parameters, negative success", function() local cert = get_cert("ssl1.com") assert.Not.cn("some.other.host.org", cert) end) it("cn assertion with 2 parameters, negative failure", function() local cert = get_cert("ssl1.com") assert.has.error(function() assert.Not.cn("localhost", cert) end) end) it("cn assertion with modifier and 1 parameter", function() local cert = get_cert("ssl1.com") assert.certificate(cert).has.cn("localhost") end) it("cn assertion with modifier and 2 parameters fails", function() local cert = get_cert("ssl1.com") assert.has.error(function() assert.certificate(cert).has.cn("localhost", cert) end) end) end) describe("eventually()", function() local function returns(v) return function() return v end end local function raises(e) return function() error(e) end end local function noop() end local function after_n(n, callback, before) local i = 0 before = before or noop return function() i = i + 1 if i > n then return callback() end return before() end end -- returns a value on the nth time the function is called -- otherwise, returns pre_n local function return_after_n(n, value, pre_n) return after_n(n, returns(value), returns(pre_n)) end --- raise an error until the function is called n times local function raise_for_n(n) return after_n(n, noop, raises("not yet")) end --- raise an error until the function is called n times local function raise_after(n) return after_n(n, raises("done"), noop) end local function new_timer() local start local timer = {} timer.start = function() ngx.update_time() start = ngx.now() return timer end timer.elapsed = function() ngx.update_time() return ngx.now() - start end return timer end it("calls a function until a condition is met", function() assert.has_no_error(function() assert.eventually(return_after_n(10, true)) .is_truthy() end) end) it("returns on the first success", function() local fn = spy.new(returns(true)) assert.has_no_error(function() assert.eventually(fn) .is_truthy() end) assert.spy(fn).was.called(1) fn = spy.new(return_after_n(5, true, nil)) assert.has_no_error(function() assert.eventually(fn) .is_truthy() end) assert.spy(fn).was.called(6) end) it("gives up after a timeout", function() local timer = new_timer().start() local timeout = 0.5 local sleep = timeout / 10 assert.has_error(function() assert.eventually(function() ngx.sleep(sleep) end) .with_timeout(timeout) .is_truthy() end) assert.near(timeout, timer.elapsed(), 0.1) end) describe("max_tries", function() it("can limit the number of tries until giving up", function() local n = 10 local fn = spy.new(return_after_n(n, true)) assert.has_error(function() assert.eventually(fn) .with_max_tries(n) .is_truthy() end) assert.spy(fn).was.called(n) end) end) describe("ignore_exceptions", function() it("causes raised errors to be treated as a normal failure", function() local maybe_raise = raise_for_n(2) local eventually_succeed = return_after_n(3, true) local fn = spy.new(function() maybe_raise() return eventually_succeed() end) assert.has_no_error(function() assert.eventually(fn) .ignore_exceptions(true) .is_truthy() end) assert.spy(fn).was.called(6) end) it("is turned off by default", function() local maybe_raise = raise_for_n(5) local eventually_succeed = return_after_n(3, true) local fn = spy.new(function() maybe_raise() return eventually_succeed() end) assert.has_error(function() assert.eventually(fn) .is_truthy() end) assert.spy(fn).was.called(1) end) end) describe("conditions", function() it("is_truthy() requires a truthy return value", function() assert.has_no_error(function() assert.eventually(returns("yup")) .is_truthy() end) -- it's common knowledge that null is truthy, but let's -- test it anyways assert.has_no_error(function() assert.eventually(returns(ngx.null)) .is_truthy() end) assert.has_error(function() assert.eventually(returns(false)) .with_timeout(0.001) .is_truthy() end) assert.has_error(function() assert.eventually(returns(nil)) .with_timeout(0.001) .is_truthy() end) end) it("is_falsy() requires a falsy return value", function() assert.has_no_error(function() assert.eventually(returns(nil)) .is_falsy() end) assert.has_no_error(function() assert.eventually(returns(false)) .is_falsy() end) assert.has_error(function() assert.eventually(returns(true)) .with_timeout(0.001) .is_falsy() end) assert.has_error(function() assert.eventually(returns("yup")) .with_timeout(0.001) .is_falsy() end) -- it's common knowledge that null is truthy, but let's -- test it anyways assert.has_error(function() assert.eventually(returns(ngx.null)) .with_timeout(0.001) .is_falsy() end) end) it("has_no_error() requires the function not to raise an error()", function() assert.has_no_error(function() assert.eventually(returns(true)) .has_no_error() end) assert.has_no_error(function() -- note: raise_until() does not return any value in the success case assert.eventually(raise_for_n(5)) .has_no_error() end) assert.has_error(function() assert.eventually(error) .with_timeout(0.001) .has_no_error() end) assert.has_error(function() assert.eventually(error) .with_timeout(0.001) .has_no_error() end) end) it("has_error() requires the function to raise an error()", function() assert.has_no_error(function() assert.eventually(error) .has_error() end) assert.has_no_error(function() assert.eventually(raise_after(5)) .has_error() end) assert.has_error(function() assert.eventually(returns(true)) .with_timeout(0.001) .has_error() end) assert.has_error(function() assert.eventually(returns(false)) .with_timeout(0.001) .has_error() end) end) end) describe("failure messages", function() -- for reference, here's an example of an assertion that will fail: --[[ local n = 0 assert .eventually(function() n = n + 1 if n < 3 then error("I have raised an error, n = " .. tostring(n)) end -- yup, you can return non-string error values return nil, { n = n, err = "nope, not yet" } end) .with_timeout(0.1) .ignore_exceptions(true) .is_truthy("something should have worked by now") ]]-- -- ...and here is the assertion failure/output produced: --[[ spec/02-integration/01-helpers/01-helpers_spec.lua:20: Failed to assert eventual condition: "something should have worked by now" Result: timed out after 0.10000014305115s Last raised error: "spec/02-integration/01-helpers/01-helpers_spec.lua:13: I have raised an error, n = 2" stack traceback: [C]: in function 'error' spec/02-integration/01-helpers/01-helpers_spec.lua:13: in function [C]: in function 'xpcall' ./spec/helpers/wait.lua:187: in function 'wait' ./spec/helpers/wait.lua:315: in function 'callback' ...ild/bin/build/kong-dev/share/lua/5.1/luassert/assert.lua:43: in function 'is_truthy' spec/02-integration/01-helpers/01-helpers_spec.lua:20: in function [C]: in function 'xpcall' ...stbuild/bin/build/kong-dev/share/lua/5.1/busted/core.lua:178: in function 'safe' ...stbuild/bin/build/kong-dev/share/lua/5.1/busted/init.lua:40: in function 'executor' ... ...stbuild/bin/build/kong-dev/share/lua/5.1/busted/core.lua:314: in function <...stbuild/bin/build/kong-dev/share/lua/5.1/busted/core.lua:314> [C]: in function 'xpcall' ...stbuild/bin/build/kong-dev/share/lua/5.1/busted/core.lua:178: in function 'safe' ...stbuild/bin/build/kong-dev/share/lua/5.1/busted/core.lua:314: in function 'execute' ...uild/bin/build/kong-dev/share/lua/5.1/busted/execute.lua:58: in function 'execute' ...build/bin/build/kong-dev/share/lua/5.1/busted/runner.lua:186: in function <...build/bin/build/kong-dev/share/lua/5.1/busted/runner.lua:11> /home/michaelm/git/kong/kong/bin/busted:63: in function 'file_gen' init_worker_by_lua:52: in function [C]: in function 'xpcall' init_worker_by_lua:59: in function Last returned error: { err = "nope, not yet", n = 3 } --- Timeout = 0.1 Step = 0.05 Elapsed = 0.10000014305115 Tries = 3 Raised = true ]]-- it("truthy", function() local e = assert.has_error(function() assert.eventually(function() return false, "nope!" end) .with_timeout(0.01) .is_truthy("foo condition") end) assert.is_string(e.message) local lines = splitlines(e.message) assert.contains("Failed to assert eventual condition", lines, true) assert.contains("foo condition", lines, true) assert.contains("Result: timed out", lines, true) assert.contains("Last returned error:", lines, true) assert.contains("nope!", lines, true) assert.contains("Last returned value:", lines, true) assert.contains("false", lines, true) assert.not_contains("Last raised error:", lines, true) assert.contains("Timeout%s+= ", lines, true) assert.contains("Step%s+=", lines, true) assert.contains("Elapsed%s+=", lines, true) assert.contains("Tries%s+=", lines, true) assert.contains("Raised%s+= false", lines, true) end) it("truthy + error() + ignore_exceptions=false", function() local e = assert.has_error(function() assert.eventually(raises("my error")) .with_timeout(0.01) .is_truthy("foo condition") end) assert.is_string(e.message) local lines = splitlines(e.message) assert.contains("Failed to assert eventual condition", lines, true) assert.contains("foo condition", lines, true) assert.contains("Result: error() raised", lines, true) assert.not_contains("Last returned error:", lines, true) assert.not_contains("Last returned value:", lines, true) assert.contains("Last raised error:", lines, true) assert.contains("my error", lines, true) assert.contains("stack traceback:", lines, true) assert.contains("Timeout%s+= ", lines, true) assert.contains("Step%s+=", lines, true) assert.contains("Elapsed%s+=", lines, true) -- note: only one try because of the uncaught exception assert.contains("Tries%s+= 1", lines, true) assert.contains("Raised%s+= true", lines, true) end) it("truthy + error() + ignore_execptions=true", function() local e = assert.has_error(function() local n = 0 assert.eventually(function() n = n + 1 if n < 2 then return nil, "some error" end error("some exception") end) .ignore_exceptions(true) .with_timeout(0.01) .is_truthy("foo condition") end) assert.is_string(e.message) local lines = splitlines(e.message) assert.contains("Failed to assert eventual condition", lines, true) assert.contains("foo condition", lines, true) assert.contains("Result: timed out", lines, true) assert.contains("Last returned error:", lines, true) assert.contains("some error", lines, true) assert.not_contains("Last returned value:", lines, true) assert.contains("Last raised error:", lines, true) assert.contains("some exception", lines, true) assert.contains("stack traceback:", lines, true) assert.contains("Timeout%s+= ", lines, true) assert.contains("Step%s+=", lines, true) assert.contains("Elapsed%s+=", lines, true) assert.contains("Tries%s+=", lines, true) -- note: raised = true assert.contains("Raised%s+= true", lines, true) end) it("falsy", function() local e = assert.has_error(function() assert.eventually(returns("yup")) .with_timeout(0.01) .is_falsy("foo condition") end) assert.is_string(e.message) local lines = splitlines(e.message) assert.contains("Failed to assert eventual condition", lines, true) assert.contains("foo condition", lines, true) assert.contains("Result: timed out", lines, true) assert.contains("Last returned value:", lines, true) assert.contains("yup", lines, true) assert.not_contains("Last raised error:", lines, true) assert.contains("Timeout%s+= ", lines, true) assert.contains("Step%s+=", lines, true) assert.contains("Elapsed%s+=", lines, true) assert.contains("Tries%s+=", lines, true) assert.contains("Raised%s+= false", lines, true) end) it("falsy + error() + ignore_exceptions=false", function() local e = assert.has_error(function() assert.eventually(raises("my error")) .with_timeout(0.01) .is_falsy("foo condition") end) assert.is_string(e.message) local lines = splitlines(e.message) assert.contains("Failed to assert eventual condition", lines, true) assert.contains("foo condition", lines, true) assert.contains("Result: error() raised", lines, true) assert.not_contains("Last returned error:", lines, true) assert.not_contains("Last returned value:", lines, true) assert.contains("Last raised error:", lines, true) assert.contains("my error", lines, true) assert.contains("stack traceback:", lines, true) assert.contains("Timeout%s+= ", lines, true) assert.contains("Step%s+=", lines, true) assert.contains("Elapsed%s+=", lines, true) -- note: only one try because of the uncaught exception assert.contains("Tries%s+= 1", lines, true) assert.contains("Raised%s+= true", lines, true) end) it("falsy + error() + ignore_execptions=true", function() local e = assert.has_error(function() local n = 0 assert.eventually(function() n = n + 1 if n < 2 then return "maybe" end error("some exception") end) .ignore_exceptions(true) .with_timeout(0.01) .is_falsy("foo condition") end) assert.is_string(e.message) local lines = splitlines(e.message) assert.contains("Failed to assert eventual condition", lines, true) assert.contains("foo condition", lines, true) assert.contains("Result: timed out", lines, true) assert.not_contains("Last returned error:", lines, true) assert.contains("Last returned value:", lines, true) assert.contains("maybe", lines, true) assert.contains("Last raised error:", lines, true) assert.contains("some exception", lines, true) assert.contains("stack traceback:", lines, true) assert.contains("Timeout%s+= ", lines, true) assert.contains("Step%s+=", lines, true) assert.contains("Elapsed%s+=", lines, true) assert.contains("Tries%s+=", lines, true) assert.contains("Raised%s+= true", lines, true) end) it("has_no_error", function() local e = assert.has_error(function() assert.eventually(raises("don't raise me, bro")) .with_timeout(0.01) .has_no_error("foo condition") end) assert.is_string(e.message) local lines = splitlines(e.message) assert.contains("Failed to assert eventual condition", lines, true) assert.contains("foo condition", lines, true) assert.contains("Result: timed out", lines, true) assert.contains("Last raised error:", lines, true) assert.contains("don't raise me, bro", lines, true) assert.contains("stack traceback:", lines, true) assert.not_contains("Last returned value:", lines, true) assert.not_contains("Last returned error:", lines, true) assert.contains("Timeout%s+= ", lines, true) assert.contains("Step%s+=", lines, true) assert.contains("Elapsed%s+=", lines, true) assert.contains("Tries%s+=", lines, true) assert.contains("Raised%s+= true", lines, true) end) it("has_error", function() local e = assert.has_error(function() local n = 0 assert.eventually(function() n = n + 1 if n < 2 then return false, "my returned error" else return "my return value" end end) .with_timeout(0.01) .has_error("foo condition") end) assert.is_string(e.message) local lines = splitlines(e.message) assert.contains("Failed to assert eventual condition", lines, true) assert.contains("foo condition", lines, true) assert.contains("Result: timed out", lines, true) assert.not_contains("Last raised error:", lines, true) assert.contains("Last returned value:", lines, true) assert.contains("my return value", lines, true) assert.contains("Last returned error:", lines, true) assert.contains("my returned error", lines, true) assert.contains("Timeout%s+= ", lines, true) assert.contains("Step%s+=", lines, true) assert.contains("Elapsed%s+=", lines, true) assert.contains("Tries%s+=", lines, true) assert.contains("Raised%s+= false", lines, true) end) end) end) end) end describe("helpers: utilities", function() describe("get_version()", function() it("gets the version of Kong running", function() local meta = require 'kong.meta' local version = require 'version' assert.equal(version(meta._VERSION), helpers.get_version()) end) end) describe("wait_until()", function() it("does not raise an error when the function returns truth-y", function() assert.has_no_error(function() local i = 0 helpers.wait_until(function() i = i + 1 return i > 1 end, 3) end) end) it("raises an error if the function does not return truth-y within the timeout", function() assert.error_matches(function() helpers.wait_until(function() return false, "thing still not done" end, 1) end, "timed out") end) it("fails when test function raises an error()", function() local e = assert.has_error(function() helpers.wait_until(function() error("oops") end, 1) end) assert.is_string(e.message) assert.matches("oops", e.message) end) it("fails when test function raised an assertion error", function() assert.has_error(function() helpers.wait_until(function() assert.is_true(false) end, 1) end) end) end) describe("pwait_until()", function() it("succeeds when a function does not raise an error()", function() assert.has_no_error(function() local i = 0 helpers.pwait_until(function() i = i + 1 if i < 5 then error("i is less than 5") end assert.is_true(i > 6) end, 1) end) end) end) describe("wait_for_file_contents()", function() local function time() ngx.update_time() return ngx.now() end it("returns the file contents when the file is readable and non-empty", function() local fname = assert(helpers.path.tmpname()) assert(helpers.file.write(fname, "test")) assert.equals("test", helpers.wait_for_file_contents(fname)) end) it("waits for the file if need be", function() local fname = assert(helpers.path.tmpname()) assert(os.remove(fname)) local timeout = 2 local delay = 1 local start, duration local sema = require("ngx.semaphore").new() local ok, res ngx.timer.at(0, function() ngx.update_time() start = time() ok, res = pcall(helpers.wait_for_file_contents, fname, timeout) ngx.update_time() duration = time() - start sema:post(1) end) ngx.sleep(delay) assert(helpers.file.write(fname, "test")) assert.truthy(sema:wait(timeout), "timed out waiting for timer to finish") assert.truthy(ok, "timer raised an error: " .. tostring(res)) assert.equals("test", res) assert.near(delay, duration, delay * 0.25, "expected wait_for_file_contents to return in " .. "~" .. delay .. " seconds") end) it("doesn't wait longer than the timeout in the failure case", function() local fname = assert(helpers.path.tmpname()) local timeout = 1 local start, duration local sema = require("ngx.semaphore").new() local ok, err ngx.timer.at(0, function() ngx.update_time() start = time() ok, err = pcall(helpers.wait_for_file_contents, fname, timeout) ngx.update_time() duration = time() - start sema:post(1) end) assert.truthy(sema:wait(timeout * 1.5), "timed out waiting for timer to finish") assert.falsy(ok, "expected wait_for_file_contents to fail") assert.not_nil(err) assert.near(timeout, duration, timeout * 0.25, "expected wait_for_file_contents to timeout after " .. "~" .. timeout .. " seconds") end) it("raises an assertion error if the file does not exist", function() assert.error_matches(function() helpers.wait_for_file_contents("/i/do/not/exist", 0) end, "does not exist or is not readable") end) it("raises an assertion error if the file is empty", function() local fname = assert(helpers.path.tmpname()) assert.error_matches(function() helpers.wait_for_file_contents(fname, 0) end, "exists but is empty") end) end) describe("clean_logfile()", function() it("truncates a file", function() local fname = assert(os.tmpname()) assert(helpers.file.write(fname, "some data\nand some more data\n")) assert(helpers.path.getsize(fname) > 0) finally(function() os.remove(fname) end) helpers.clean_logfile(fname) assert(helpers.path.getsize(fname) == 0) end) it("truncates the test conf error.log file if no input is given", function() local log_dir = helpers.path.join(helpers.test_conf.prefix, "logs") if not helpers.path.exists(log_dir) then assert(helpers.dir.makepath(log_dir)) finally(function() finally(function() helpers.dir.rmtree(log_dir) end) end) end local fname = helpers.path.join(log_dir, "error.log") assert(helpers.file.write(fname, "some data\nand some more data\n")) assert(helpers.path.getsize(fname) > 0) helpers.clean_logfile(fname) assert(helpers.path.getsize(fname) == 0) end) it("creates an empty file if one does not exist", function() local fname = assert(os.tmpname()) assert(os.remove(fname)) assert(not helpers.path.exists(fname)) helpers.clean_logfile(fname) finally(function() os.remove(fname) end) assert(helpers.path.isfile(fname)) assert(helpers.path.getsize(fname) == 0) end) it("doesn't raise an error if the parent directory does not exist", function() local fname = "/tmp/i-definitely/do-not-exist." .. ngx.worker.pid() assert(not helpers.path.exists(fname)) assert.has_no_error(function() helpers.clean_logfile(fname) end) end) it("raises an error if the path is not a file", function() local path = os.tmpname() os.remove(path) assert(helpers.dir.makepath(path)) assert(helpers.path.isdir(path)) finally(function() helpers.dir.rmtree(path) end) assert.error_matches(function() helpers.clean_logfile(path) end, "Is a directory") end) end) describe("partial_match()", function() describe("positive mod", function() it("allows to match to tables paritally", function() local partial_table = { x = 100, y = { z = 200 } } local full_table = { x = 100, a = "test1", y = { b = "test2", z = 200 } } assert.partial_match(partial_table, full_table) end) it("fails if tables do not match paritally", function() local partial_table = { x = 100, y = { z = 77 } } local full_table = { x = 100, a = "test1", y = { b = "test2", z = 200 } } local ok, err_actual = pcall(function() assert.partial_match(partial_table, full_table) end) assert.falsy(ok) assert.matches(".*Values at key %(string%) 'y%.z' should be equal but are not.\nExpected: %(number%) 77, given: %(number%) 200\n", err_actual.message) end) end) describe("negative mod", function() it("allows to verify if tables do not match", function() local partial_table = { x = 77, y = { z = 88 } } local full_table = { x = 100, a = "test1", y = { b = "test2", z = 200 } } assert.does_not.partial_match(partial_table, full_table) end) it("fails if tables do match paritally", function() local partial_table = { x = 100, y = { z = 77 } } local full_table = { x = 100, a = "test1", y = { b = "test2", z = 200 } } local ok, err_actual = pcall(function() assert.does_not.partial_match(partial_table, full_table) end) assert.falsy(ok) assert.matches(".*Values at key %(string%) 'x' should not be equal", err_actual.message) end) end) end) end)