local kong_table = require "kong.tools.table" local pl_path = require "pl.path" local tools_ip = require "kong.tools.ip" local tools_http = require "kong.tools.http" describe("Utils", function() describe("get_system_infos()", function() local utils = require "kong.tools.system" it("retrieves various host infos", function() local infos = utils.get_system_infos() assert.is_number(infos.cores) assert.is_string(infos.uname) assert.not_matches("\n$", infos.uname) end) it("caches the result", function() assert.equal( utils.get_system_infos(), utils.get_system_infos() ) end) end) describe("get_system_trusted_certs_filepath()", function() local utils = require "kong.tools.system" local old_exists = pl_path.exists after_each(function() pl_path.exists = old_exists end) local tests = { Debian = "/etc/ssl/certs/ca-certificates.crt", Fedora = "/etc/pki/tls/certs/ca-bundle.crt", OpenSuse = "/etc/ssl/ca-bundle.pem", OpenElec = "/etc/pki/tls/cacert.pem", CentOS = "/etc/pki/ca-trust/extracted/pem/tls-ca-bundle.pem", Alpine = "/etc/ssl/cert.pem", } for distro, test_path in pairs(tests) do it("retrieves the default filepath in " .. distro, function() pl_path.exists = function(path) return path == test_path end assert.same(test_path, utils.get_system_trusted_certs_filepath()) end) end it("errors if file is somewhere else", function() pl_path.exists = function(path) return path == "/some/unknown/location.crt" end local ok, err = utils.get_system_trusted_certs_filepath() assert.is_nil(ok) assert.matches("Could not find trusted certs file", err) end) end) describe("is_valid_uuid()", function() local utils = require "kong.tools.uuid" it("validates UUIDs from jit-uuid", function() assert.True (utils.is_valid_uuid("cbb297c0-a956-486d-ad1d-f9b42df9465a")) assert.False(utils.is_valid_uuid("cbb297c0-a956486d-ad1d-f9b42df9465a")) end) pending("invalidates UUIDs with invalid variants", function() -- this is disabled because existing uuids in the database fail the check upon migrations -- see https://github.com/thibaultcha/lua-resty-jit-uuid/issues/8 assert.False(utils.is_valid_uuid("cbb297c0-a956-486d-dd1d-f9b42df9465a")) -- invalid variant end) it("validates UUIDs with invalid variants for backwards-compatibility reasons", function() -- See pending test just above ^^ -- see https://github.com/thibaultcha/lua-resty-jit-uuid/issues/8 assert.True(utils.is_valid_uuid("cbb297c0-a956-486d-dd1d-f9b42df9465a")) end) it("considers the null UUID a valid one", function() -- we use the null UUID for plugins' consumer_id when none is set assert.True(utils.is_valid_uuid("00000000-0000-0000-0000-000000000000")) end) end) describe("https_check", function() local old_ngx lazy_setup(function() old_ngx = ngx _G.ngx = { var = { scheme = nil, http_x_forwarded_proto = nil, }, } end) lazy_teardown(function() _G.ngx = old_ngx end) describe("without X-Forwarded-Proto header", function() lazy_setup(function() ngx.var.http_x_forwarded_proto = nil end) it("should validate an HTTPS scheme", function() ngx.var.scheme = "hTTps" -- mixed casing to ensure case insensitiveness assert.is.truthy(tools_http.check_https(true, false)) end) it("should invalidate non-HTTPS schemes", function() ngx.var.scheme = "hTTp" assert.is.falsy(tools_http.check_https(true, false)) ngx.var.scheme = "something completely different" assert.is.falsy(tools_http.check_https(true, false)) end) it("should invalidate non-HTTPS schemes with proto header allowed", function() ngx.var.scheme = "hTTp" assert.is.falsy(tools_http.check_https(true, true)) end) end) describe("with X-Forwarded-Proto header", function() lazy_teardown(function() ngx.var.http_x_forwarded_proto = nil end) it("should validate any scheme with X-Forwarded_Proto as HTTPS", function() ngx.var.http_x_forwarded_proto = "hTTPs" -- check mixed casing for case insensitiveness ngx.var.scheme = "hTTps" assert.is.truthy(tools_http.check_https(true, true)) ngx.var.scheme = "hTTp" assert.is.truthy(tools_http.check_https(true, true)) ngx.var.scheme = "something completely different" assert.is.truthy(tools_http.check_https(true, true)) end) it("should validate only https scheme with X-Forwarded_Proto as non-HTTPS", function() ngx.var.http_x_forwarded_proto = "hTTP" ngx.var.scheme = "hTTps" assert.is.truthy(tools_http.check_https(true, true)) ngx.var.scheme = "hTTp" assert.is.falsy(tools_http.check_https(true, true)) ngx.var.scheme = "something completely different" assert.is.falsy(tools_http.check_https(true, true)) end) it("should return an error with multiple X-Forwarded_Proto headers", function() ngx.var.http_x_forwarded_proto = "hTTP, https" ngx.var.scheme = "hTTps" assert.is.truthy(tools_http.check_https(true, true)) ngx.var.scheme = "hTTp" assert.are.same({ nil, "Only one X-Forwarded-Proto header allowed" }, { tools_http.check_https(true, true) }) end) it("should not use X-Forwarded-Proto when the client is untrusted", function() ngx.var.http_x_forwarded_proto = "https" ngx.var.scheme = "http" assert.is_false(tools_http.check_https(false, false)) assert.is_false(tools_http.check_https(false, true)) ngx.var.http_x_forwarded_proto = "https" ngx.var.scheme = "https" assert.is_true(tools_http.check_https(false, false)) assert.is_true(tools_http.check_https(false, true)) end) it("should use X-Forwarded-Proto when the client is trusted", function() ngx.var.http_x_forwarded_proto = "https" ngx.var.scheme = "http" -- trusted client but do not allow terminated assert.is_false(tools_http.check_https(true, false)) assert.is_true(tools_http.check_https(true, true)) ngx.var.http_x_forwarded_proto = "https" ngx.var.scheme = "https" assert.is_true(tools_http.check_https(true, false)) assert.is_true(tools_http.check_https(true, true)) end) it("test parsing directive header", function() -- EMPTY table return by the function with `nil` should be read-only assert.is_false(pcall(function() tools_http.parse_directive_header(nil)["foo"] = "bar" end)) -- test null assert.same(tools_http.parse_directive_header(nil), {}) -- test empty string assert.same(tools_http.parse_directive_header(""), {}) -- test string assert.same(tools_http.parse_directive_header("cache-key=kong-cache,cache-age=300"), { ["cache-age"] = 300, ["cache-key"] = "kong-cache", }) -- test table assert.same(tools_http.parse_directive_header({ "cache-age=300", "cache-key=kong-cache", }), { ["cache-age"] = 300, ["cache-key"] = "kong-cache", }) end) end) end) describe("string", function() it("checks valid UTF8 values", function() local validate_utf8 = require("kong.tools.string").validate_utf8 assert.True(validate_utf8("hello")) assert.True(validate_utf8(123)) assert.True(validate_utf8(true)) assert.False(validate_utf8(string.char(105, 213, 205, 149))) assert.False(validate_utf8(string.char(128))) -- unexpected continuation byte assert.False(validate_utf8(string.char(192, 32))) -- 2-byte sequence 0xc0 followed by space assert.False(validate_utf8(string.char(192))) -- 2-byte sequence with last byte missing assert.False(validate_utf8(string.char(254))) -- impossible byte assert.False(validate_utf8(string.char(255))) -- impossible byte assert.False(validate_utf8(string.char(237, 160, 128))) -- Single UTF-16 surrogate end) it("checks valid nginx size values", function() local parse_ngx_size = require("kong.tools.string").parse_ngx_size assert.equal(1024 * 1024 * 1024, parse_ngx_size("1G")) assert.equal(1024 * 1024 * 1024, parse_ngx_size("1g")) assert.equal(1024 * 1024, parse_ngx_size("1M")) assert.equal(1024 * 1024, parse_ngx_size("1m")) assert.equal(1024, parse_ngx_size("1k")) assert.equal(1024, parse_ngx_size("1K")) assert.equal(10, parse_ngx_size("10")) end) describe("random_string()", function() local utils = require "kong.tools.rand" it("should return a random string", function() local first = utils.random_string() assert.is_string(first) -- build the same length string as previous implementations assert.equals(32, #first) -- ensure we don't find anything that isnt alphanumeric assert.not_matches("^[^%a%d]+$", first) -- at some point in the universe this test will fail ;) local second = utils.random_string() assert.not_equal(first, second) end) end) describe("encode_args()", function() it("should encode a Lua table to a querystring", function() local str = tools_http.encode_args { foo = "bar", hello = "world" } assert.equal("foo=bar&hello=world", str) end) it("should encode multi-value query args", function() local str = tools_http.encode_args { foo = {"bar", "zoo"}, hello = "world" } assert.equal("foo%5b1%5d=bar&foo%5b2%5d=zoo&hello=world", str) end) it("should percent-encode given values", function() local str = tools_http.encode_args { encode = {"abc|def", ",$@|`"} } assert.equal("encode%5b1%5d=abc%7cdef&encode%5b2%5d=%2c%24%40%7c%60", str) end) it("should percent-encode given query args keys", function() local str = tools_http.encode_args { ["hello world"] = "foo" } assert.equal("hello%20world=foo", str) end) it("should support Lua numbers", function() local str = tools_http.encode_args { a = 1, b = 2 } assert.equal("a=1&b=2", str) end) it("should support a boolean argument", function() local str = tools_http.encode_args { a = true, b = 1 } assert.equal("a=true&b=1", str) end) it("should ignore nil and false values", function() local str = tools_http.encode_args { a = nil, b = false } assert.equal("b=false", str) end) it("should encode complex query args", function() local encode = tools_http.encode_args assert.equal("falsy=false", encode({ falsy = false })) assert.equal("multiple%20values=true", encode({ ["multiple values"] = true })) assert.equal("array%5b1%5d=hello%2c%20world", encode({ array = {"hello, world"} })) assert.equal("hash%2eanswer=42", encode({ hash = { answer = 42 } })) assert.equal("hash_array%2earr%5b1%5d=one&hash_array%2earr%5b2%5d=two", encode({ hash_array = { arr = { "one", "two" } } })) assert.equal("array_hash%5b1%5d%2ename=peter", encode({ array_hash = { { name = "peter" } } })) assert.equal("array_array%5b1%5d%5b1%5d=x&array_array%5b1%5d%5b2%5d=y", encode({ array_array = { { "x", "y" } } })) assert.equal("hybrid%5b1%5d=1&hybrid%5b2%5d=2&hybrid%2en=3", encode({ hybrid = { 1, 2, n = 3 } })) end) it("should not interpret the `%` character followed by 2 characters in the [0-9a-f] group as an hexadecimal value", function() local str = tools_http.encode_args { foo = "%bar%" } assert.equal("foo=%25bar%25", str) end) it("does not percent-encode if given a `raw` option", function() local encode = tools_http.encode_args -- this is useful for kong.tools.http_client assert.equal("hello world=foo, bar", encode({ ["hello world"] = "foo, bar" }, true)) assert.equal("hash.answer=42", encode({ hash = { answer = 42 } }, true)) assert.equal("hash_array.arr[1]=one&hash_array.arr[2]=two", encode({ hash_array = { arr = { "one", "two" } } }, true)) assert.equal("array_hash[1].name=peter", encode({ array_hash = { { name = "peter" } } }, true)) assert.equal("array_array[1][1]=x&array_array[1][2]=y", encode({ array_array = { { "x", "y" } } }, true)) assert.equal("hybrid[1]=1&hybrid[2]=2&hybrid.n=3", encode({ hybrid = { 1, 2, n = 3 } }, true)) end) it("does not include index numbers in arrays if given the `no_array_indexes` flag", function() local encode = tools_http.encode_args assert.equal("falsy=false", encode({ falsy = false }, nil, true)) assert.equal("multiple%20values=true", encode({ ["multiple values"] = true }, nil, true)) assert.equal("array%5b%5d=hello%2c%20world", encode({ array = {"hello, world"} }, nil, true)) assert.equal("hash%2eanswer=42", encode({ hash = { answer = 42 } }, nil, true)) assert.equal("hash_array%2earr%5b%5d=one&hash_array%2earr%5b%5d=two", encode({ hash_array = { arr = { "one", "two" } } }, nil, true)) assert.equal("array_hash%5b%5d%2ename=peter", encode({ array_hash = { { name = "peter" } } }, nil, true)) assert.equal("array_array%5b%5d%5b%5d=x&array_array%5b%5d%5b%5d=y", encode({ array_array = { { "x", "y" } } }, nil, true)) assert.equal("hybrid%5b%5d=1&hybrid%5b%5d=2&hybrid%2en=3", encode({ hybrid = { 1, 2, n = 3 } }, nil, true)) end) it("does not percent-encode and does not add index numbers if both `raw` and `no_array_indexes` are active", function() local encode = tools_http.encode_args -- this is useful for kong.tools.http_client assert.equal("hello world=foo, bar", encode({ ["hello world"] = "foo, bar" }, true, true)) assert.equal("hash.answer=42", encode({ hash = { answer = 42 } }, true, true)) assert.equal("hash_array.arr[]=one&hash_array.arr[]=two", encode({ hash_array = { arr = { "one", "two" } } }, true, true)) assert.equal("array_hash[].name=peter", encode({ array_hash = { { name = "peter" } } }, true, true)) assert.equal("array_array[][]=x&array_array[][]=y", encode({ array_array = { { "x", "y" } } }, true, true)) assert.equal("hybrid[]=1&hybrid[]=2&hybrid.n=3", encode({ hybrid = { 1, 2, n = 3 } }, true, true)) end) it("transforms ngx.null into empty string", function() local str = tools_http.encode_args({ x = ngx.null, y = "foo" }) assert.equal("x=&y=foo", str) end) -- while this method's purpose is to mimic 100% the behavior of ngx.encode_args, -- it is also used by Kong specs' http_client, to encode both querystrings and *bodies*. -- Hence, a `raw` parameter allows encoding for bodies. describe("raw", function() it("should not percent-encode values", function() local str = tools_http.encode_args({ foo = "hello world" }, true) assert.equal("foo=hello world", str) end) it("should not percent-encode keys", function() local str = tools_http.encode_args({ ["hello world"] = "foo" }, true) assert.equal("hello world=foo", str) end) it("should plainly include true and false values", function() local str = tools_http.encode_args({ a = true, b = false }, true) assert.equal("a=true&b=false", str) end) it("should prevent double percent-encoding", function() local str = tools_http.encode_args({ foo = "hello%20world" }, true) assert.equal("foo=hello%20world", str) end) end) end) end) describe("table", function() describe("table_contains()", function() it("should return false if a value is not contained in a nil table", function() assert.False(kong_table.table_contains(nil, "foo")) end) it("should return true if a value is contained in a table", function() local t = { foo = "hello", bar = "world" } assert.True(kong_table.table_contains(t, "hello")) end) it("should return false if a value is not contained in a table", function() local t = { foo = "hello", bar = "world" } assert.False(kong_table.table_contains(t, "foo")) end) end) describe("is_array()", function() it("should know when an array (strict)", function() assert.True(kong_table.is_array({ "a", "b", "c", "d" })) assert.False(kong_table.is_array({ "a", "b", nil, "c", "d" })) assert.False(kong_table.is_array({ [-1] = "a", [0] = "b", [1] = "c", [2] = "d" })) assert.False(kong_table.is_array({ [0] = "a", [1] = "b", [2] = "c", [3] = "d" })) assert.True(kong_table.is_array({ [1] = "a", [2] = "b", [3] = "c", [4] = "d" })) assert.True(kong_table.is_array({ [1.0] = "a", [2.0] = "b", [3.0] = "c", [4.0] = "d" })) assert.False(kong_table.is_array({ [1] = "a", [2] = "b", nil, [3] = "c", [4] = "d" })) --luacheck: ignore assert.False(kong_table.is_array({ [1] = "a", [2] = "b", nil, [4] = "c", [5] = "d" })) --luacheck: ignore assert.False(kong_table.is_array({ [1.1] = "a", [2.1] = "b", [3.1] = "c", [4.1] = "d" })) assert.False(kong_table.is_array({ ["1"] = "a", ["2"] = "b", ["3"] = "c", ["4"] = "d" })) assert.False(kong_table.is_array({ "a", "b", "c", foo = "d" })) assert.False(kong_table.is_array()) assert.False(kong_table.is_array(false)) assert.False(kong_table.is_array(true)) end) it("should know when an array (fast)", function() assert.True(kong_table.is_array({ "a", "b", "c", "d" }, "fast")) assert.True(kong_table.is_array({ "a", "b", nil, "c", "d" }, "fast")) assert.True(kong_table.is_array({ [-1] = "a", [0] = "b", [1] = "c", [2] = "d" }, "fast")) assert.True(kong_table.is_array({ [0] = "a", [1] = "b", [2] = "c", [3] = "d" }, "fast")) assert.True(kong_table.is_array({ [1] = "a", [2] = "b", [3] = "c", [4] = "d" }, "fast")) assert.True(kong_table.is_array({ [1.0] = "a", [2.0] = "b", [3.0] = "c", [4.0] = "d" }, "fast")) assert.True(kong_table.is_array({ [1] = "a", [2] = "b", nil, [3] = "c", [4] = "d" }, "fast")) --luacheck: ignore assert.True(kong_table.is_array({ [1] = "a", [2] = "b", nil, [4] = "c", [5] = "d" }, "fast")) --luacheck: ignore assert.False(kong_table.is_array({ [1.1] = "a", [2.1] = "b", [3.1] = "c", [4.1] = "d" }, "fast")) assert.False(kong_table.is_array({ ["1"] = "a", ["2"] = "b", ["3"] = "c", ["4"] = "d" }, "fast")) assert.False(kong_table.is_array({ "a", "b", "c", foo = "d" }, "fast")) assert.False(kong_table.is_array(nil, "fast")) assert.False(kong_table.is_array(false, "fast")) assert.False(kong_table.is_array(true, "fast")) end) it("should know when an array (lapis)", function() assert.True(kong_table.is_array({ "a", "b", "c", "d" }, "lapis")) assert.False(kong_table.is_array({ "a", "b", nil, "c", "d" }, "lapis")) assert.False(kong_table.is_array({ [-1] = "a", [0] = "b", [1] = "c", [2] = "d" }, "lapis")) assert.False(kong_table.is_array({ [0] = "a", [1] = "b", [2] = "c", [3] = "d" }, "lapis")) assert.True(kong_table.is_array({ [1] = "a", [2] = "b", [3] = "c", [4] = "d" }, "lapis")) assert.True(kong_table.is_array({ [1.0] = "a", [2.0] = "b", [3.0] = "c", [4.0] = "d" }, "lapis")) assert.False(kong_table.is_array({ [1] = "a", [2] = "b", nil, [3] = "c", [4] = "d" }, "lapis")) --luacheck: ignore assert.False(kong_table.is_array({ [1] = "a", [2] = "b", nil, [4] = "c", [5] = "d" }, "lapis")) --luacheck: ignore assert.False(kong_table.is_array({ [1.1] = "a", [2.1] = "b", [3.1] = "c", [4.1] = "d" }, "lapis")) assert.True(kong_table.is_array({ ["1"] = "a", ["2"] = "b", ["3"] = "c", ["4"] = "d" }, "lapis")) assert.False(kong_table.is_array({ "a", "b", "c", foo = "d" }, "lapis")) assert.False(kong_table.is_array(nil, "lapis")) assert.False(kong_table.is_array(false, "lapis")) assert.False(kong_table.is_array(true, "lapis")) end) end) describe("add_error()", function() local add_error = kong_table.add_error it("should create a table if given `errors` is nil", function() assert.same({hello = "world"}, add_error(nil, "hello", "world")) end) it("should add a key/value when the key does not exists", function() local errors = {hello = "world"} assert.same({ hello = "world", foo = "bar" }, add_error(errors, "foo", "bar")) end) it("should transform previous values to a list if the same key is given again", function() local e = nil -- initialize for luacheck e = add_error(e, "key1", "value1") e = add_error(e, "key2", "value2") assert.same({key1 = "value1", key2 = "value2"}, e) e = add_error(e, "key1", "value3") e = add_error(e, "key1", "value4") assert.same({key1 = {"value1", "value3", "value4"}, key2 = "value2"}, e) e = add_error(e, "key1", "value5") e = add_error(e, "key1", "value6") e = add_error(e, "key2", "value7") assert.same({key1 = {"value1", "value3", "value4", "value5", "value6"}, key2 = {"value2", "value7"}}, e) end) it("should also list tables pushed as errors", function() local e = nil -- initialize for luacheck e = add_error(e, "key1", "value1") e = add_error(e, "key2", "value2") e = add_error(e, "key1", "value3") e = add_error(e, "key1", "value4") e = add_error(e, "keyO", {message = "some error"}) e = add_error(e, "keyO", {message = "another"}) assert.same({ key1 = {"value1", "value3", "value4"}, key2 = "value2", keyO = {{message = "some error"}, {message = "another"}} }, e) end) end) describe("load_module_if_exists()", function() local load_module_if_exists = require "kong.tools.module".load_module_if_exists it("should return false if the module does not exist", function() local loaded, mod assert.has_no.errors(function() loaded, mod = load_module_if_exists("kong.does.not.exist") end) assert.False(loaded) assert.is.string(mod) end) it("should throw an error with a traceback if the module is invalid", function() local pok, perr = pcall(load_module_if_exists, "spec.fixtures.invalid-module") assert.falsy(pok) assert.match("error loading module 'spec.fixtures.invalid-module'", perr, 1, true) assert.match("./spec/fixtures/invalid-module.lua:", perr, 1, true) end) it("should load a module if it was found and valid", function() local loaded, mod assert.has_no.errors(function() loaded, mod = load_module_if_exists("spec.fixtures.valid-module") end) assert.True(loaded) assert.truthy(mod) assert.are.same("All your base are belong to us.", mod.exposed) end) end) end) describe("hostnames and ip addresses", function() describe("hostname_type", function() -- no check on "name" type as anything not ipv4 and not ipv6 will be labelled as 'name' anyway it("checks valid IPv4 address types", function() assert.are.same("ipv4", tools_ip.hostname_type("123.123.123.123")) assert.are.same("ipv4", tools_ip.hostname_type("1.2.3.4")) assert.are.same("ipv4", tools_ip.hostname_type("1.2.3.4:80")) end) it("checks valid IPv6 address types", function() assert.are.same("ipv6", tools_ip.hostname_type("::1")) assert.are.same("ipv6", tools_ip.hostname_type("2345::6789")) assert.are.same("ipv6", tools_ip.hostname_type("0001:0001:0001:0001:0001:0001:0001:0001")) assert.are.same("ipv6", tools_ip.hostname_type("[2345::6789]:80")) end) end) describe("parsing", function() it("normalizes IPv4 address types", function() assert.are.same({"123.123.123.123"}, {tools_ip.normalize_ipv4("123.123.123.123")}) assert.are.same({"123.123.123.123", 80}, {tools_ip.normalize_ipv4("123.123.123.123:80")}) assert.are.same({"1.1.1.1"}, {tools_ip.normalize_ipv4("1.1.1.1")}) assert.are.same({"1.1.1.1", 80}, {tools_ip.normalize_ipv4("001.001.001.001:00080")}) end) it("fails normalizing bad IPv4 address types", function() assert.is_nil(tools_ip.normalize_ipv4("123.123:80")) assert.is_nil(tools_ip.normalize_ipv4("123.123.123.999")) assert.is_nil(tools_ip.normalize_ipv4("123.123.123.123:80a")) assert.is_nil(tools_ip.normalize_ipv4("123.123.123.123.123:80")) assert.is_nil(tools_ip.normalize_ipv4("localhost:80")) assert.is_nil(tools_ip.normalize_ipv4("[::1]:80")) assert.is_nil(tools_ip.normalize_ipv4("123.123.123.123:99999")) end) it("normalizes IPv6 address types", function() assert.are.same({"0000:0000:0000:0000:0000:0000:0000:0001"}, {tools_ip.normalize_ipv6("::1")}) assert.are.same({"0000:0000:0000:0000:0000:0000:0000:0001"}, {tools_ip.normalize_ipv6("[::1]")}) assert.are.same({"0000:0000:0000:0000:0000:0000:0000:0001", 80}, {tools_ip.normalize_ipv6("[::1]:80")}) assert.are.same({"0000:0000:0000:0000:0000:0000:0000:0001", 80}, {tools_ip.normalize_ipv6("[0000:0000:0000:0000:0000:0000:0000:0001]:80")}) end) it("fails normalizing bad IPv6 address types", function() assert.is_nil(tools_ip.normalize_ipv6("123.123.123.123")) assert.is_nil(tools_ip.normalize_ipv6("localhost:80")) assert.is_nil(tools_ip.normalize_ipv6("::x")) assert.is_nil(tools_ip.normalize_ipv6("[::x]:80")) assert.is_nil(tools_ip.normalize_ipv6("[::1]:80a")) assert.is_nil(tools_ip.normalize_ipv6("1")) assert.is_nil(tools_ip.normalize_ipv6("[::1]:99999")) end) it("validates hostnames", function() local valids = {"hello.com", "hello.fr", "test.hello.com", "1991.io", "hello.COM", "HELLO.com", "123helloWORLD.com", "example.123", "example-api.com", "hello.abcd", "example_api.com", "localhost", "example.", -- punycode examples from RFC3492; https://tools.ietf.org/html/rfc3492#page-14 -- specifically the japanese ones as they mix ascii with escaped characters "3B-ww4c5e180e575a65lsy2b", "-with-SUPER-MONKEYS-pc58ag80a8qai00g7n9n", "Hello-Another-Way--fc4qua05auwb3674vfr0b", "2-u9tlzr9756bt3uc0v", "MajiKoi5-783gue6qz075azm5e", "de-jg4avhby1noc0d", "d9juau41awczczp", } local invalids = {"/example", ".example", "exam;ple", "example.com/org", "example-.org", "example.org-", "hello..example.com", "hello-.example.com", } for _, name in ipairs(valids) do assert.are.same(name, (tools_ip.check_hostname(name))) end for _, name in ipairs(valids) do assert.are.same({ [1] = name, [2] = 80}, { tools_ip.check_hostname(name .. ":80")}) end for _, name in ipairs(valids) do assert.is_nil((tools_ip.check_hostname(name .. ":xx"))) assert.is_nil((tools_ip.check_hostname(name .. ":99999"))) end for _, name in ipairs(invalids) do assert.is_nil((tools_ip.check_hostname(name))) assert.is_nil((tools_ip.check_hostname(name .. ":80"))) end end) it("validates addresses", function() assert.are.same({host = "1.2.3.4", type = "ipv4", port = 80}, tools_ip.normalize_ip("1.2.3.4:80")) assert.are.same({host = "1.2.3.4", type = "ipv4", port = nil}, tools_ip.normalize_ip("1.2.3.4")) assert.are.same({host = "0000:0000:0000:0000:0000:0000:0000:0001", type = "ipv6", port = 80}, tools_ip.normalize_ip("[::1]:80")) assert.are.same({host = "0000:0000:0000:0000:0000:0000:0000:0001", type = "ipv6", port = nil}, tools_ip.normalize_ip("::1")) assert.are.same({host = "localhost", type = "name", port = 80}, tools_ip.normalize_ip("localhost:80")) assert.are.same({host = "mashape.test", type = "name", port = nil}, tools_ip.normalize_ip("mashape.test")) assert.is_nil((tools_ip.normalize_ip("1.2.3.4:8x0"))) assert.is_nil((tools_ip.normalize_ip("1.2.3.400"))) assert.is_nil((tools_ip.normalize_ip("[::1]:8x0"))) assert.is_nil((tools_ip.normalize_ip(":x:1"))) assert.is_nil((tools_ip.normalize_ip("localhost:8x0"))) assert.is_nil((tools_ip.normalize_ip("mashape..test"))) end) end) describe("formatting", function() it("correctly formats addresses", function() assert.are.equal("1.2.3.4", tools_ip.format_host("1.2.3.4")) assert.are.equal("1.2.3.4:80", tools_ip.format_host("1.2.3.4", 80)) assert.are.equal("[0000:0000:0000:0000:0000:0000:0000:0001]", tools_ip.format_host("::1")) assert.are.equal("[0000:0000:0000:0000:0000:0000:0000:0001]:80", tools_ip.format_host("::1", 80)) assert.are.equal("localhost", tools_ip.format_host("localhost")) assert.are.equal("mashape.test:80", tools_ip.format_host("mashape.test", 80)) -- passthrough (string) assert.are.equal("1.2.3.4", tools_ip.format_host(tools_ip.normalize_ipv4("1.2.3.4"))) assert.are.equal("1.2.3.4:80", tools_ip.format_host(tools_ip.normalize_ipv4("1.2.3.4:80"))) assert.are.equal("[0000:0000:0000:0000:0000:0000:0000:0001]", tools_ip.format_host(tools_ip.normalize_ipv6("::1"))) assert.are.equal("[0000:0000:0000:0000:0000:0000:0000:0001]:80", tools_ip.format_host(tools_ip.normalize_ipv6("[::1]:80"))) assert.are.equal("localhost", tools_ip.format_host(tools_ip.check_hostname("localhost"))) assert.are.equal("mashape.test:80", tools_ip.format_host(tools_ip.check_hostname("mashape.test:80"))) -- passthrough general (table) assert.are.equal("1.2.3.4", tools_ip.format_host(tools_ip.normalize_ip("1.2.3.4"))) assert.are.equal("1.2.3.4:80", tools_ip.format_host(tools_ip.normalize_ip("1.2.3.4:80"))) assert.are.equal("[0000:0000:0000:0000:0000:0000:0000:0001]", tools_ip.format_host(tools_ip.normalize_ip("::1"))) assert.are.equal("[0000:0000:0000:0000:0000:0000:0000:0001]:80", tools_ip.format_host(tools_ip.normalize_ip("[::1]:80"))) assert.are.equal("localhost", tools_ip.format_host(tools_ip.normalize_ip("localhost"))) assert.are.equal("mashape.test:80", tools_ip.format_host(tools_ip.normalize_ip("mashape.test:80"))) -- passthrough errors local one, two = tools_ip.format_host(tools_ip.normalize_ipv4("1.2.3.4.5")) assert.are.equal("nilstring", type(one) .. type(two)) local one, two = tools_ip.format_host(tools_ip.normalize_ipv6("not ipv6...")) assert.are.equal("nilstring", type(one) .. type(two)) local one, two = tools_ip.format_host(tools_ip.check_hostname("//bad..name\\:123")) assert.are.equal("nilstring", type(one) .. type(two)) local one, two = tools_ip.format_host(tools_ip.normalize_ip("m a s h a p e.test:80")) assert.are.equal("nilstring", type(one) .. type(two)) end) end) end) it("validate_header_name() validates header names", function() local header_chars = [[_-0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz]] for i = 1, 255 do local c = string.char(i) if string.find(header_chars, c, nil, true) then assert(tools_http.validate_header_name(c) == c, "ascii character '" .. c .. "' (" .. i .. ") should have been allowed") else assert(tools_http.validate_header_name(c) == nil, "ascii character " .. i .. " should not have been allowed") end end end) it("validate_cookie_name() validates cookie names", function() local cookie_chars = [[~`|!#$%&'*+-._-^0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz]] for i = 1, 255 do local c = string.char(i) if string.find(cookie_chars, c, nil, true) then assert(tools_http.validate_cookie_name(c) == c, "ascii character '" .. c .. "' (" .. i .. ") should have been allowed") else assert(tools_http.validate_cookie_name(c) == nil, "ascii character " .. i .. " should not have been allowed") end end end) it("pack() stores results, including nils, properly", function() assert.same({ n = 0 }, kong_table.pack()) assert.same({ n = 1 }, kong_table.pack(nil)) assert.same({ n = 3, "1", "2", "3" }, kong_table.pack("1", "2", "3")) assert.same({ n = 3, [1] = "1", [3] = "3" }, kong_table.pack("1", nil, "3")) end) it("unpack() unwraps results, including nils, properly", function() local a,b,c a,b,c = kong_table.unpack({}) assert.is_nil(a) assert.is_nil(b) assert.is_nil(c) a,b,c = unpack({ n = 1 }) assert.is_nil(a) assert.is_nil(b) assert.is_nil(c) a,b,c = kong_table.unpack({ n = 3, "1", "2", "3" }) assert.equal("1", a) assert.equal("2", b) assert.equal("3", c) a,b,c = kong_table.unpack({ n = 3, [1] = "1", [3] = "3" }) assert.equal("1", a) assert.is_nil(b) assert.equal("3", c) end) describe("bytes_to_str()", function() local bytes_to_str = require("kong.tools.string").bytes_to_str it("converts bytes to the desired unit", function() assert.equal("5497558", bytes_to_str(5497558, "b")) assert.equal("5368.71 KiB", bytes_to_str(5497558, "k")) assert.equal("5.24 MiB", bytes_to_str(5497558, "m")) assert.equal("0.01 GiB", bytes_to_str(5497558, "g")) assert.equal("5.12 GiB", bytes_to_str(5497558998, "g")) end) it("defaults unit arg to bytes", function() assert.equal("5497558", bytes_to_str(5497558)) assert.equal("5497558", bytes_to_str(5497558, "")) end) it("unit arg is case-insensitive", function() assert.equal("5497558", bytes_to_str(5497558, "B")) assert.equal("5368.71 KiB", bytes_to_str(5497558, "K")) assert.equal("5.24 MiB", bytes_to_str(5497558, "M")) assert.equal("0.01 GiB", bytes_to_str(5497558, "G")) assert.equal("5.12 GiB", bytes_to_str(5497558998, "G")) end) it("scale arg", function() -- 3 assert.equal("5497558", bytes_to_str(5497558, "b", 3)) assert.equal("5368.709 KiB", bytes_to_str(5497558, "k", 3)) assert.equal("5.243 MiB", bytes_to_str(5497558, "m", 3)) assert.equal("0.005 GiB", bytes_to_str(5497558, "g", 3)) assert.equal("5.120 GiB", bytes_to_str(5497558998, "g", 3)) -- 0 assert.equal("5 GiB", bytes_to_str(5497558998, "g", 0)) -- decimals assert.equal("5.12 GiB", bytes_to_str(5497558998, "g", 2.2)) end) it("errors on invalid unit arg", function() assert.has_error(function() bytes_to_str(1234, "V") end, "invalid unit 'V' (expected 'k/K', 'm/M', or 'g/G')") end) it("errors on invalid scale arg", function() assert.has_error(function() bytes_to_str(1234, "k", -1) end, "scale must be equal or greater than 0") assert.has_error(function() bytes_to_str(1234, "k", "") end, "scale must be equal or greater than 0") end) end) describe("gzip_[de_in]flate()", function() local utils = require "kong.tools.gzip" it("empty string", function() local gz = assert(utils.deflate_gzip("")) assert.equal(utils.inflate_gzip(gz), "") end) it("small string (< 1 buffer)", function() local gz = assert(utils.deflate_gzip("aabbccddeeffgg")) assert.equal(utils.inflate_gzip(gz), "aabbccddeeffgg") end) it("long string (> 1 buffer)", function() local s = string.rep("a", 70000) -- > 64KB local gz = assert(utils.deflate_gzip(s)) assert(#gz < #s) assert.equal(utils.inflate_gzip(gz), s) end) it("bad gzipped data", function() local res, err = utils.inflate_gzip("bad") assert.is_nil(res) assert.equal(err, "INFLATE: data error") end) end) describe("get_mime_type()", function() it("with valid mime types", function() assert.equal("application/json; charset=utf-8", tools_http.get_mime_type("application/json")) assert.equal("application/json; charset=utf-8", tools_http.get_mime_type("application/json; charset=utf-8")) assert.equal("application/json; charset=utf-8", tools_http.get_mime_type("application/*")) assert.equal("application/json; charset=utf-8", tools_http.get_mime_type("application/*; charset=utf-8")) assert.equal("text/html; charset=utf-8", tools_http.get_mime_type("text/html")) assert.equal("text/plain; charset=utf-8", tools_http.get_mime_type("text/plain")) assert.equal("text/plain; charset=utf-8", tools_http.get_mime_type("text/*")) assert.equal("text/plain; charset=utf-8", tools_http.get_mime_type("text/*; charset=utf-8")) assert.equal("application/xml; charset=utf-8", tools_http.get_mime_type("application/xml")) assert.equal("application/json; charset=utf-8", tools_http.get_mime_type("*/*; charset=utf-8")) assert.equal("application/json; charset=utf-8", tools_http.get_mime_type("*/*")) assert.equal("", tools_http.get_mime_type("application/grpc")) end) it("with unsupported or invalid mime types", function() assert.equal("application/json; charset=utf-8", tools_http.get_mime_type("audio/*", true)) assert.equal("application/json; charset=utf-8", tools_http.get_mime_type("text/css")) assert.equal("application/json; charset=utf-8", tools_http.get_mime_type("default")) assert.is_nil(tools_http.get_mime_type("video/json", false)) assert.is_nil(tools_http.get_mime_type("text/javascript", false)) end) end) describe("nginx_conf_time_to_seconds()", function() local nginx_conf_time_to_seconds = require("kong.tools.time").nginx_conf_time_to_seconds it("returns value in seconds", function() assert.equal(5, nginx_conf_time_to_seconds("5")) assert.equal(5, nginx_conf_time_to_seconds("5s")) assert.equal(60, nginx_conf_time_to_seconds("60s")) assert.equal(60, nginx_conf_time_to_seconds("1m")) assert.equal(120, nginx_conf_time_to_seconds("2m")) assert.equal(7200, nginx_conf_time_to_seconds("2h")) assert.equal(172800, nginx_conf_time_to_seconds("2d")) assert.equal(1209600, nginx_conf_time_to_seconds("2w")) assert.equal(5184000, nginx_conf_time_to_seconds("2M")) assert.equal(63072000, nginx_conf_time_to_seconds("2y")) end) it("throws an error on bad argument", function() assert.has_error(function() nginx_conf_time_to_seconds("abcd") end, "bad argument #1 'str'") end) end) describe("topological_sort", function() local get_neighbors = function(x) return x end local ts = require("kong.db.utils").topological_sort it("it puts destinations first", function() local a = { id = "a" } local b = { id = "b", a } local c = { id = "c", a, b } local d = { id = "d", c } local x = ts({ c, d, a, b }, get_neighbors) assert.same({ a, b, c, d }, x) end) it("returns an error if cycles are found", function() local a = { id = "a" } local b = { id = "b", a } a[1] = b local x, err = ts({ a, b }, get_neighbors) assert.is_nil(x) assert.equals("Cycle detected, cannot sort topologically", err) end) end) local function count_keys(t, n) n = n or 0 if type(t) ~= "table" then return n end for k, v in pairs(t) do n = count_keys(k, n) n = count_keys(v, n) n = n + 1 end return n end describe("deep_copy(t)", function() it("copies values, keys and metatables and sets metatables", function() local meta = {} local meta2 = {} local ref = {} local ref2 = setmetatable({}, meta) ref[1] = 1 ref[2] = ref2 ref[3] = nil ref[4] = 4 local a = setmetatable({ a = setmetatable({ a = "clone", }, meta2), b = ref, }, meta) local b = { [a] = a, a = a, b = ref, } local c = kong_table.deep_copy(b) assert.not_same(b, c) assert.not_equal(b, c) assert.equal(b[a], b.a) assert.not_equal(c[a], c.a) assert.equal(b.b, ref) assert.not_equal(c.b, ref) assert.equal(b.b[1], 1) assert.equal(c.b[1], 1) assert.equal(b.b[2], ref2) assert.not_equal(c.b[2], ref2) assert.equal(getmetatable(b.b[2]), meta) assert.not_equal(getmetatable(c.b[2]), meta) assert.equal(b.b[3], nil) assert.equal(c.b[3], nil) assert.equal(b.b[4], 4) assert.equal(c.b[4], 4) assert.equal(getmetatable(b[a]), meta) assert.is_nil(getmetatable(c[a])) assert.equal(getmetatable(b.a), meta) assert.not_equal(getmetatable(c.a), meta) assert.is_table(getmetatable(c.a), meta) assert.not_equal(getmetatable(b[a]), getmetatable(c[a])) assert.not_equal(getmetatable(b.a), getmetatable(c.a)) assert.is_table(getmetatable(b[a])) assert.is_nil(getmetatable(c[a])) assert.is_table(getmetatable(b.a)) assert.is_table(getmetatable(c.a)) assert.equal(getmetatable(b[a].a), meta2) assert.is_nil(getmetatable(c[a] and c[a].a or nil)) assert.not_equal(getmetatable(b[a].a), getmetatable(c[a] and c[a].a or nil)) assert.equal(getmetatable(b.a.a), meta2) assert.not_equal(getmetatable(c.a.a), meta2) assert.not_equal(getmetatable(b.a.a), getmetatable(c.a.a)) assert.not_equal(b[a], c[a]) assert.not_equal(b.a, c.a) assert.not_equal(b[a].a, c[a] and c[a].a or nil) assert.not_equal(b.a.a, c.a.a) assert.not_equal(b[a].a.a, c[a] and c[a].a and c[a].a.a or nil) assert.equal(b.a.a.a, c.a.a.a) local key_found for k in pairs(b) do key_found = nil for k2 in pairs(c) do if k == k2 then key_found = true break end end if type(k) == "table" then assert.is_nil(key_found) else assert.is_true(key_found) end end key_found = nil for k in pairs(b) do if k == a then key_found = true break end end assert.is_true(key_found) key_found = nil for k in pairs(c) do if k == a then key_found = true break end end assert.is_nil(key_found) assert.equal(count_keys(b), 24) assert.equal(count_keys(c), 24) end) end) describe("deep_copy(t, false)", function() it("copies values and keys and removes metatables", function() local meta = {} local meta2 = {} local ref = {} local ref2 = setmetatable({}, meta) ref[1] = 1 ref[2] = ref2 ref[3] = nil ref[4] = 4 local a = setmetatable({ a = setmetatable({ a = "clone", }, meta2), b = ref, }, meta) local b = { [a] = a, a = a, b = ref, } local c = kong_table.deep_copy(b, false) assert.not_same(b, c) assert.not_equal(b, c) assert.equal(b[a], b.a) assert.not_equal(c[a], c.a) assert.equal(b.b, ref) assert.not_equal(c.b, ref) assert.equal(b.b[1], 1) assert.equal(c.b[1], 1) assert.equal(b.b[2], ref2) assert.not_equal(c.b[2], ref2) assert.equal(getmetatable(b.b[2]), meta) assert.not_equal(getmetatable(c.b[2]), meta) assert.equal(b.b[3], nil) assert.equal(c.b[3], nil) assert.equal(b.b[4], 4) assert.equal(c.b[4], 4) assert.equal(getmetatable(b[a]), meta) assert.is_nil(getmetatable(c[a])) assert.equal(getmetatable(b.a), meta) assert.not_equal(getmetatable(c.a), meta) assert.is_nil(getmetatable(c.a), meta) assert.not_equal(getmetatable(b[a]), getmetatable(c[a])) assert.not_equal(getmetatable(b.a), getmetatable(c.a)) assert.is_table(getmetatable(b[a])) assert.is_nil(getmetatable(c[a])) assert.is_table(getmetatable(b.a)) assert.is_nil(getmetatable(c.a)) assert.equal(getmetatable(b[a].a), meta2) assert.is_nil(getmetatable(c[a] and c[a].a or nil)) assert.not_equal(getmetatable(b[a].a), getmetatable(c[a] and c[a].a or nil)) assert.equal(getmetatable(b.a.a), meta2) assert.not_equal(getmetatable(c.a.a), meta2) assert.not_equal(getmetatable(b.a.a), getmetatable(c.a.a)) assert.not_equal(b[a], c[a]) assert.not_equal(b.a, c.a) assert.not_equal(b[a].a, c[a] and c[a].a or nil) assert.not_equal(b.a.a, c.a.a) assert.not_equal(b[a].a.a, c[a] and c[a].a and c[a].a.a or nil) assert.equal(b.a.a.a, c.a.a.a) local key_found for k in pairs(b) do key_found = nil for k2 in pairs(c) do if k == k2 then key_found = true break end end if type(k) == "table" then assert.is_nil(key_found) else assert.is_true(key_found) end end key_found = nil for k in pairs(b) do if k == a then key_found = true break end end assert.is_true(key_found) key_found = nil for k in pairs(c) do if k == a then key_found = true break end end assert.is_nil(key_found) assert.equal(count_keys(b), 24) assert.equal(count_keys(c), 24) end) end) describe("cycle_aware_deep_copy(t)", function() it("cycle aware copies values and sets the metatables but does not copy keys or metatables", function() local meta = {} local meta2 = {} local ref = {} local ref2 = setmetatable({}, meta) ref[1] = 1 ref[2] = ref2 ref[3] = nil ref[4] = 4 local a = setmetatable({ a = setmetatable({ a = "clone", }, meta2), b = ref, }, meta) local b = { [a] = a, a = a, b = ref, } local c = kong_table.cycle_aware_deep_copy(b) assert.same(b, c) assert.not_equal(b, c) assert.equal(b[a], b.a) assert.equal(c[a], c.a) assert.equal(b.b, ref) assert.not_equal(c.b, ref) assert.equal(b.b[1], 1) assert.equal(c.b[1], 1) assert.equal(b.b[2], ref2) assert.not_equal(c.b[2], ref2) assert.equal(getmetatable(b.b[2]), meta) assert.equal(getmetatable(c.b[2]), meta) assert.equal(b.b[3], nil) assert.equal(c.b[3], nil) assert.equal(b.b[4], 4) assert.equal(c.b[4], 4) assert.equal(getmetatable(b[a]), meta) assert.equal(getmetatable(c[a]), meta) assert.equal(getmetatable(b.a), meta) assert.equal(getmetatable(c.a), meta) assert.equal(getmetatable(b[a]), getmetatable(c[a])) assert.equal(getmetatable(b.a), getmetatable(c.a)) assert.equal(getmetatable(b[a].a), meta2) assert.equal(getmetatable(c[a].a), meta2) assert.equal(getmetatable(b[a].a), getmetatable(c[a].a)) assert.equal(getmetatable(b.a.a), meta2) assert.equal(getmetatable(c.a.a), meta2) assert.equal(getmetatable(b.a.a), getmetatable(c.a.a)) assert.not_equal(b[a], c[a]) assert.not_equal(b.a, c.a) assert.not_equal(b[a].a, c[a].a) assert.not_equal(b.a.a, c.a.a) assert.equal(b[a].a.a, c[a].a.a) assert.equal(b.a.a.a, c.a.a.a) local key_found for k in pairs(b) do key_found = nil for k2 in pairs(c) do if k == k2 then key_found = true break end end assert.is_true(key_found) end key_found = nil for k in pairs(b) do if k == a then key_found = true break end end assert.is_true(key_found) key_found = nil for k in pairs(c) do if k == a then key_found = true break end end assert.is_true(key_found) assert.equal(count_keys(b), 24) assert.equal(count_keys(c), 24) end) end) describe("cycle_aware_deep_copy(t, true)", function() it("cycle aware copies values and removes metatables but does not copy keys", function() local meta = {} local meta2 = {} local ref = {} local ref2 = setmetatable({}, meta) ref[1] = 1 ref[2] = ref2 ref[3] = nil ref[4] = 4 local a = setmetatable({ a = setmetatable({ a = "clone", }, meta2), b = ref, }, meta) local b = { [a] = a, a = a, b = ref, } local c = kong_table.cycle_aware_deep_copy(b, true) assert.same(b, c) assert.not_equal(b, c) assert.equal(b[a], b.a) assert.equal(c[a], c.a) assert.equal(b.b, ref) assert.not_equal(c.b, ref) assert.equal(b.b[1], 1) assert.equal(c.b[1], 1) assert.equal(b.b[2], ref2) assert.not_equal(c.b[2], ref2) assert.equal(getmetatable(b.b[2]), meta) assert.is_nil(getmetatable(c.b[2])) assert.equal(b.b[3], nil) assert.equal(c.b[3], nil) assert.equal(b.b[4], 4) assert.equal(c.b[4], 4) assert.equal(getmetatable(b[a]), meta) assert.is_nil(getmetatable(c[a]), meta) assert.equal(getmetatable(b.a), meta) assert.is_nil(getmetatable(c.a)) assert.not_equal(getmetatable(b[a]), getmetatable(c[a])) assert.not_equal(getmetatable(b.a), getmetatable(c.a)) assert.equal(getmetatable(b[a].a), meta2) assert.is_nil(getmetatable(c[a].a)) assert.not_equal(getmetatable(b[a].a), getmetatable(c[a].a)) assert.equal(getmetatable(b.a.a), meta2) assert.is_nil(getmetatable(c.a.a)) assert.not_equal(getmetatable(b.a.a), getmetatable(c.a.a)) assert.not_equal(b[a], c[a]) assert.not_equal(b.a, c.a) assert.not_equal(b[a].a, c[a].a) assert.not_equal(b.a.a, c.a.a) assert.equal(b[a].a.a, c[a].a.a) assert.equal(b.a.a.a, c.a.a.a) local key_found for k in pairs(b) do key_found = nil for k2 in pairs(c) do if k == k2 then key_found = true break end end assert.is_true(key_found) end key_found = nil for k in pairs(b) do if k == a then key_found = true break end end assert.is_true(key_found) key_found = nil for k in pairs(c) do if k == a then key_found = true break end end assert.is_true(key_found) assert.equal(count_keys(b), 24) assert.equal(count_keys(c), 24) end) end) describe("cycle_aware_deep_copy(t, nil, true)", function() it("cycle aware copies values and keys, and sets metatables", function() local meta = {} local meta2 = {} local ref = {} local ref2 = setmetatable({}, meta) ref[1] = 1 ref[2] = ref2 ref[3] = nil ref[4] = 4 local a = setmetatable({ a = setmetatable({ a = "clone", }, meta2), b = ref, }, meta) local b = { [a] = a, a = a, b = ref, } local c = kong_table.cycle_aware_deep_copy(b, nil, true) assert.not_same(b, c) assert.not_equal(b, c) assert.equal(b[a], b.a) assert.is_nil(c[a]) assert.equal(b.b, ref) assert.not_equal(c.b, ref) assert.equal(b.b[1], 1) assert.equal(c.b[1], 1) assert.equal(b.b[2], ref2) assert.not_equal(c.b[2], ref2) assert.equal(getmetatable(b.b[2]), meta) assert.equal(getmetatable(c.b[2]), meta) assert.equal(b.b[3], nil) assert.equal(c.b[3], nil) assert.equal(b.b[4], 4) assert.equal(c.b[4], 4) assert.equal(getmetatable(b[a]), meta) assert.is_nil(getmetatable(c[a])) assert.equal(getmetatable(b.a), meta) assert.equal(getmetatable(c.a), meta) assert.not_equal(getmetatable(b[a]), getmetatable(c[a])) assert.equal(getmetatable(b.a), getmetatable(c.a)) assert.equal(getmetatable(b[a].a), meta2) assert.is_nil(getmetatable(c[a] and c[a].a)) assert.not_equal(getmetatable(b[a].a), getmetatable(c[a] and c[a].a or nil)) assert.equal(getmetatable(b.a.a), meta2) assert.equal(getmetatable(c.a.a), meta2) assert.equal(getmetatable(b.a.a), getmetatable(c.a.a)) assert.not_equal(b[a], c[a]) assert.not_equal(b.a, c.a) assert.not_equal(b[a].a, c[a] and c[a].a or nil) assert.not_equal(b.a.a, c.a.a) assert.not_equal(b[a].a.a, c[a] and c[a].a and c[a].a.a or nil) assert.equal(b.a.a.a, c.a.a.a) local key_found for k in pairs(b) do key_found = nil for k2 in pairs(c) do if k == k2 then key_found = true break end end if type(k) == "table" then assert.is_nil(key_found) else assert.is_true(key_found) end end key_found = nil for k in pairs(b) do if k == a then key_found = true break end end assert.is_true(key_found) key_found = nil for k in pairs(c) do if k == a then key_found = true break end end assert.is_nil(key_found) assert.equal(count_keys(b), 24) assert.equal(count_keys(c), 24) end) end) describe("cycle_aware_deep_copy(t, nil, nil, cache)", function() it("cycle aware copies values that are not already cached and sets metatables but does not copy keys or metatables", function() local cache = {} local meta = {} local meta2 = {} local ref = {} local ref2 = setmetatable({}, meta) cache[ref] = ref cache[ref2] = ref2 ref[1] = 1 ref[2] = ref2 ref[3] = nil ref[4] = 4 local a = setmetatable({ a = setmetatable({ a = "clone", }, meta2), b = ref, }, meta) cache[a] = a local b = { [a] = a, a = a, b = ref, } local c = kong_table.cycle_aware_deep_copy(b, nil, nil, cache) assert.same(b, c) assert.not_equal(b, c) assert.equal(b[a], b.a) assert.equal(c[a], c.a) assert.equal(b.b, ref) assert.equal(c.b, ref) assert.equal(b.b[1], 1) assert.equal(c.b[1], 1) assert.equal(b.b[2], ref2) assert.equal(c.b[2], ref2) assert.equal(getmetatable(b.b[2]), meta) assert.equal(getmetatable(c.b[2]), meta) assert.equal(b.b[3], nil) assert.equal(c.b[3], nil) assert.equal(b.b[4], 4) assert.equal(c.b[4], 4) assert.equal(getmetatable(b[a]), meta) assert.equal(getmetatable(c[a]), meta) assert.equal(getmetatable(b.a), meta) assert.equal(getmetatable(c.a), meta) assert.equal(getmetatable(b[a]), getmetatable(c[a])) assert.equal(getmetatable(b.a), getmetatable(c.a)) assert.equal(getmetatable(b[a].a), meta2) assert.equal(getmetatable(c[a].a), meta2) assert.equal(getmetatable(b[a].a), getmetatable(c[a].a)) assert.equal(getmetatable(b.a.a), meta2) assert.equal(getmetatable(c.a.a), meta2) assert.equal(getmetatable(b.a.a), getmetatable(c.a.a)) assert.equal(b[a], c[a]) assert.equal(b.a, c.a) assert.equal(b[a].a, c[a].a) assert.equal(b.a.a, c.a.a) assert.equal(b[a].a.a, c[a].a.a) assert.equal(b.a.a.a, c.a.a.a) local key_found for k in pairs(b) do key_found = nil for k2 in pairs(c) do if k == k2 then key_found = true break end end assert.is_true(key_found) end key_found = nil for k in pairs(b) do if k == a then key_found = true break end end assert.is_true(key_found) key_found = nil for k in pairs(c) do if k == a then key_found = true break end end assert.is_true(key_found) assert.equal(count_keys(b), 24) assert.equal(count_keys(c), 24) end) end) describe("deep_merge(t1, t2)", function() it("deep merges t2 into copy of t1", function() local meta = {} local ref = setmetatable({ a = "ref", }, meta) local t1 = { a = "t1", b = { a = ref, }, c = { a = "t1", }, } local t2 = { a = "t2", b = { a = ref, b = "t2", }, c = "t2", } local t3 = kong_table.deep_merge(t1, t2) assert.not_equal(t3, t1) assert.not_equal(t3, t2) assert.same({ a = "t2", b = { a = ref, b = "t2", }, c = "t2", }, t3) assert.not_equal(meta, getmetatable(t3.b.a)) assert.is_table(getmetatable(t3.b.a)) end) end) describe("cycle_aware_deep_merge(t1, t2)", function() it("cycle aware deep merges t2 into copy of t1", function() local meta = {} local ref = setmetatable({ a = "ref", }, meta) local t1 = { a = "t1", b = { a = ref, }, c = { a = "t1", }, } local t2 = { a = "t2", b = { a = ref, b = "t2", }, c = "t2", } local t3 = kong_table.cycle_aware_deep_merge(t1, t2) assert.not_equal(t3, t1) assert.not_equal(t3, t2) assert.same({ a = "t2", b = { a = ref, b = "t2", }, c = "t2", }, t3) assert.equal(meta, getmetatable(t3.b.a)) end) end) describe("table_path(t, path)", function() local t = { x = 1, a = { b = { c = 200 }, }, z = 2 } it("retrieves value from table based on path - single level", function() local path = { "x" } assert.equal(1, kong_table.table_path(t, path)) end) it("retrieves value from table based on path - deep value", function() local path = { "a", "b", "c" } assert.equal(200, kong_table.table_path(t, path)) end) it("returns nil if element is not found - leaf not found", function() local path = { "a", "b", "x" } assert.equal(nil, kong_table.table_path(t, path)) end) it("returns nil if element is not found - root branch not found", function() local path = { "o", "j", "k" } assert.equal(nil, kong_table.table_path(t, path)) end) end) it("test parse_directive_header function", function() -- test null assert.same(tools_http.parse_directive_header(nil), {}) -- test empty string assert.same(tools_http.parse_directive_header(""), {}) -- test string assert.same(tools_http.parse_directive_header("cache-key=kong-cache,cache-age=300"), { ["cache-age"] = 300, ["cache-key"] = "kong-cache", }) end) it("test calculate_resource_ttl function", function() -- test max-age header _G.ngx = { var = { sent_http_expires = "60", }, } local access_control_header = tools_http.parse_directive_header("cache-key=kong-cache,max-age=300") assert.same(tools_http.calculate_resource_ttl(access_control_header), 300) -- test s-maxage header _G.ngx = { var = { sent_http_expires = "60", }, } local access_control_header = tools_http.parse_directive_header("cache-key=kong-cache,s-maxage=310") assert.same(tools_http.calculate_resource_ttl(access_control_header), 310) -- test empty headers local expiry_year = os.date("%Y") + 1 _G.ngx = { var = { sent_http_expires = os.date("!%a, %d %b ") .. expiry_year .. " " .. os.date("!%X GMT") -- format: "Thu, 18 Nov 2099 11:27:35 GMT", }, } -- chop the last digit to avoid flaky tests (clock skew) assert.same(string.sub(tools_http.calculate_resource_ttl(), 0, -2), "3153600") end) end)