kong/spec/01-unit/01-db/01-schema/06-routes_spec.lua (1,060 lines of code) (raw):

local routes = require "kong.db.schema.entities.routes" local routes_subschemas = require "kong.db.schema.entities.routes_subschemas" local services = require "kong.db.schema.entities.services" local Schema = require "kong.db.schema" local certificates = require "kong.db.schema.entities.certificates" local Entity = require "kong.db.schema.entity" assert(Schema.new(certificates)) assert(Schema.new(services)) local Routes = assert(Entity.new(routes)) for name, subschema in pairs(routes_subschemas) do Routes:new_subschema(name, subschema) end describe("routes schema", function() local a_valid_uuid = "cbb297c0-a956-486d-ad1d-f9b42df9465a" local another_uuid = "64a8670b-900f-44e7-a900-6ec7ef5aa4d3" local uuid_pattern = "^" .. ("%x"):rep(8) .. "%-" .. ("%x"):rep(4) .. "%-" .. ("%x"):rep(4) .. "%-" .. ("%x"):rep(4) .. "%-" .. ("%x"):rep(12) .. "$" it("validates a valid route", function() local route = { id = a_valid_uuid, name = "my_route", protocols = { "http" }, methods = { "GET", "POST" }, hosts = { "example.com" }, headers = { location = { "location-1" } }, paths = { "/ovo" }, regex_priority = 1, strip_path = false, preserve_host = true, service = { id = another_uuid }, } route = Routes:process_auto_fields(route, "insert") assert.truthy(route.created_at) assert.truthy(route.updated_at) assert.same(route.created_at, route.updated_at) assert.truthy(Routes:validate(route)) assert.falsy(route.strip_path) end) it("it does not fail when service is null", function() local route = { service = ngx.null, paths = {"/"} } route = Routes:process_auto_fields(route, "insert") local ok, errs = Routes:validate_insert(route) assert.truthy(ok) assert.is_nil(errs) end) it("it does not fail when service is missing", function() local route = { paths = {"/"} } route = Routes:process_auto_fields(route, "insert") local ok, errs = Routes:validate_insert(route) assert.truthy(ok) assert.is_nil(errs) end) it("fails when service.id is null", function() local route = { service = { id = ngx.null }, paths = {"/"} } route = Routes:process_auto_fields(route, "insert") local ok, errs = Routes:validate_insert(route) assert.falsy(ok) assert.truthy(errs["service"]) end) it("fails when protocol is missing", function() local route = { protocols = ngx.null } route = Routes:process_auto_fields(route, "insert") local ok, errs = Routes:validate_insert(route) assert.falsy(ok) assert.truthy(errs["protocols"]) end) it("fails given an invalid method", function() local route = { protocols = { "http" }, methods = { "get" }, } route = Routes:process_auto_fields(route, "insert") local ok, errs = Routes:validate(route) assert.falsy(ok) assert.truthy(errs["methods"]) end) it("missing method, host, headers, path & service produces error", function() local s = { id = "a4fbd24e-6a52-4937-bd78-2536713072d2" } local tests = { { 1, { protocols = { "http" }, }, {} }, { true, { protocols = { "http" }, service = s, methods = {"GET"} }, {"hosts", "paths"} }, { true, { protocols = { "http" }, service = s, hosts = {"x.y"} }, {"methods", "paths"} }, { true, { protocols = { "http" }, service = s, paths = {"/foo"} }, {"methods", "hosts"} }, { true, { protocols = { "http" }, service = s, headers = { location = { "location-1" } } }, {"methods", "hosts", "paths"} }, } for i, test in ipairs(tests) do test[2] = Routes:process_auto_fields(test[2], "insert") local ok, errs = Routes:validate(test[2]) if test[1] == true then assert.truthy(ok, "case " .. tostring(i) .. " failed") assert.falsy(errs) else assert.falsy(ok) for _, name in ipairs(test[3]) do assert.truthy(errs[name], "case " .. tostring(i) .. " failed: " .. name) end end end end) it("invalid protocols produces error", function() local route = Routes:process_auto_fields({ protocols = {} }, "insert") local ok, errs = Routes:validate(route) assert.falsy(ok) assert.truthy(errs["protocols"]) local route2 = Routes:process_auto_fields({ protocols = { "ftp" } }, "insert") local ok, errs = Routes:validate(route2) assert.falsy(ok) assert.truthy(errs["protocols"]) end) it("conflicting protocols produces error", function() local protocols_tests = { { {"http", "tcp"}, "('http', 'https'), ('tcp', 'tls', 'udp')" }, { {"http", "tls"}, "('http', 'https'), ('tcp', 'tls', 'udp')" }, { {"https", "tcp"}, "('http', 'https'), ('tcp', 'tls', 'udp')" }, { {"https", "tls"}, "('http', 'https'), ('tcp', 'tls', 'udp')" }, } for _, test in ipairs(protocols_tests) do local route = Routes:process_auto_fields({ protocols = test[1] }, "insert") local ok, errs = Routes:validate(route) assert.falsy(ok) assert.truthy(errs["protocols"]) assert.same(("these sets are mutually exclusive: %s"):format(test[2]), errs["protocols"]) end end) it("invalid https_redirect_status_code produces error", function() local route = Routes:process_auto_fields({ protocols = { "http" }, https_redirect_status_code = 404, }, "insert") local ok, errs = Routes:validate(route) assert.falsy(ok) assert.truthy(errs["https_redirect_status_code"]) end) it("produces defaults", function() local route = { protocols = { "http" }, service = { id = "5abfe322-9fc1-4e0e-bfa3-007f5b9ac4b4" }, paths = { "/foo" } } route = Routes:process_auto_fields(route, "insert") local ok, err = Routes:validate(route) assert.is_nil(err) assert.truthy(ok) assert.match(uuid_pattern, route.id) assert.same(ngx.null, route.name) assert.same({ "http" }, route.protocols) assert.same(ngx.null, route.methods) assert.same(ngx.null, route.hosts) assert.same(ngx.null, route.headers) assert.same({ "/foo" }, route.paths) assert.same(0, route.regex_priority) assert.same(true, route.strip_path) assert.same(false, route.preserve_host) assert.same(426, route.https_redirect_status_code) end) it("validates the foreign key in entities", function() local route = { protocols = { "http" }, paths = { "/foo" }, service = { id = "blergh", } } route = Routes:process_auto_fields(route, "insert") local ok, errs = Routes:validate(route) assert.falsy(ok) assert.truthy(errs["service"]) assert.truthy(errs["service"]["id"]) end) it("gives access to foreign schemas", function() assert.truthy(Routes.fields.service) assert.truthy(Routes.fields.service.schema) assert.truthy(Routes.fields.service.schema.fields) end) describe("paths attribute", function() -- refusals it("must be a string", function() local route = { paths = { false }, protocols = {"http"} } local ok, err = Routes:validate(route) assert.falsy(ok) assert.equal("expected a string", err.paths[1]) end) it("must be a non-empty string", function() local route = { paths = { "" }, protocols = { "http" }, } local ok, err = Routes:validate(route) assert.falsy(ok) assert.equal("length must be at least 1", err.paths[1]) end) it("must start with /", function() local route = { paths = { "foo" }, protocols = { "http" }, } local ok, err = Routes:validate(route) assert.falsy(ok) assert.equal("should start with: / (fixed path) or ~/ (regex path)", err.paths[1]) end) it("must not have empty segments (/foo//bar)", function() local route = { paths = { "/foo//bar", "/foo/bar//", "//foo/bar", }, protocols = { "http" }, } local ok, err = Routes:validate(route) assert.falsy(ok) assert.equal("must not have empty segments", err.paths[1]) assert.equal("must not have empty segments", err.paths[2]) assert.equal("must not have empty segments", err.paths[3]) end) it("must dry-run values that are considered regexes", function() local u = require("spec.helpers").unindent local invalid_paths = { [[~/users/(foo/profile]], } for i = 1, #invalid_paths do local route = { paths = { invalid_paths[i] }, protocols = { "http" }, } local ok, err = Routes:validate(route) assert.falsy(ok) assert.equal(u([[invalid regex: '/users/(foo/profile' (PCRE returned: pcre_compile() failed: missing ) in "/users/(foo/profile")]], true, true), err.paths[1]) end end) it("rejects badly percent-encoded values", function() local invalid_paths = { "/some%2words", "/some%0Xwords", "/some%2Gwords", "/some%20words%", "/some%20words%a", "/some%20words%ax", } local errstr = { "%2w", "%0X", "%2G", "%", "%a", "%ax" } for i = 1, #invalid_paths do local route = { paths = { invalid_paths[i] }, protocols = { "http" }, } local ok, err = Routes:validate(route) assert.falsy(ok) assert.matches("invalid url-encoded value: '" .. errstr[i] .. "'", err.paths[1], nil, true) end end) -- acceptance it("accepts an apex '/'", function() local route = Routes:process_auto_fields({ protocols = { "http" }, service = { id = a_valid_uuid }, methods = {}, hosts = {}, paths = { "/" }, }, "insert") local ok, err = Routes:validate(route) assert.is_nil(err) assert.is_true(ok) end) it("accepts unreserved characters from RFC 3986", function() local route = Routes:process_auto_fields({ protocols = { "http" }, service = { id = a_valid_uuid }, methods = {}, hosts = {}, paths = { "/abcd~user~2" }, }, "insert") local ok, err = Routes:validate(route) assert.is_nil(err) assert.is_true(ok) end) it("accepts properly percent-encoded values", function() local valid_paths = { "/abcd%aa%10%ff%AA%FF" } for i = 1, #valid_paths do local route = Routes:process_auto_fields({ protocols = { "http" }, service = { id = a_valid_uuid }, methods = {}, hosts = {}, paths = { valid_paths[i] }, }, "insert") local ok, err = Routes:validate(route) assert.is_nil(err) assert.is_true(ok) end end) it("accepts trailing slash", function() local route = Routes:process_auto_fields({ protocols = { "http" }, service = { id = a_valid_uuid }, methods = {}, hosts = {}, paths = { "/ovo/" }, }, "insert") local ok, err = Routes:validate(route) assert.is_nil(err) assert.is_true(ok) end) end) describe("hosts attribute", function() -- refusals it("must be a string", function() local route = { hosts = { false }, protocols = { "http" }, } local ok, err = Routes:validate(route) assert.falsy(ok) assert.equal("expected a string", err.hosts[1]) end) it("must be a non-empty string", function() local route = { hosts = { "" }, protocols = { "http" }, } local ok, err = Routes:validate(route) assert.falsy(ok) assert.equal("length must be at least 1", err.hosts[1]) end) it("rejects invalid hostnames", function() local invalid_hosts = { "/example", ".example", "example:", "mock;bin", "example.com/org", "example-.org", "example.org-", "hello..example.com", "hello-.example.com", } for i = 1, #invalid_hosts do local route = { hosts = { invalid_hosts[i] }, protocols = { "http" }, } local ok, err = Routes:validate(route) assert.falsy(ok) assert.equal("invalid hostname: " .. invalid_hosts[i], err.hosts[1]) end end) it("rejects values with an invalid port", function() local route = { hosts = { "example.com:1000000" }, protocols = { "http" }, } local ok, err = Routes:validate(route) assert.falsy(ok) assert.equal("invalid port number", err.hosts[1]) end) it("rejects invalid wildcard placement", function() local invalid_hosts = { "*example.com", "www.example*", "mock*bin.com", } for i = 1, #invalid_hosts do local route = { hosts = { invalid_hosts[i] }, protocols = { "http" }, } local ok, err = Routes:validate(route) assert.falsy(ok) assert.equal("invalid wildcard: must be placed at leftmost or " .. "rightmost label", err.hosts[1]) end end) it("rejects host with too many wildcards", function() local invalid_hosts = { "*.example.*", "**.example.com", "*.example*.*", } for i = 1, #invalid_hosts do local route = { hosts = { invalid_hosts[i] }, protocols = { "http" }, } local ok, err = Routes:validate(route) assert.falsy(ok) assert.equal("invalid wildcard: must have at most one wildcard", err.hosts[1]) end end) -- acceptance it("accepts valid hosts", function() local valid_hosts = { "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.com:80", "example.com:8080", -- below: -- 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", } for i = 1, #valid_hosts do local route = Routes:process_auto_fields({ protocols = { "http" }, service = { id = a_valid_uuid }, methods = {}, paths = {}, hosts = { valid_hosts[i] }, }, "insert") local ok, err = Routes:validate(route) assert.is_nil(err) assert.is_true(ok) end end) it("accepts hosts with valid wildcard", function() local valid_hosts = { "example.*", "*.example.org", "*.example.org:321", } for i = 1, #valid_hosts do local route = Routes:process_auto_fields({ protocols = { "http" }, service = { id = a_valid_uuid }, methods = {}, paths = {}, hosts = { valid_hosts[i] }, }, "insert") local ok, err = Routes:validate(route) assert.is_nil(err) assert.is_true(ok) end end) end) describe("headers attribute", function() -- refusals it("key must be a string", function() local route = { headers = { false }, protocols = { "http" }, } local ok, err = Routes:validate(route) assert.falsy(ok) assert.equal("expected a string", err.headers) end) it("cannot contain 'host' key", function() local values = { "host", "Host", "HoSt" } for _, v in ipairs(values) do local route = { headers = { [v] = { "example.com" } }, protocols = { "http" }, } local ok, err = Routes:validate(route) assert.falsy(ok) assert.equal("cannot contain 'host' header, which must be specified " .. "in the 'hosts' attribute", err.headers) end end) it("value must be an array", function() local route = { headers = { location = true }, protocols = { "http" }, } local ok, err = Routes:validate(route) assert.falsy(ok) assert.equal("expected an array", err.headers) end) it("values must be a string", function() local route = { headers = { location = { true } }, protocols = { "http" }, } local ok, err = Routes:validate(route) assert.falsy(ok) assert.equal("expected a string", err.headers[1]) end) it("values must be non-empty string", function() local route = { headers = { location = { "" } }, protocols = { "http" }, } local ok, err = Routes:validate(route) assert.falsy(ok) assert.equal("length must be at least 1", err.headers[1]) end) end) describe("methods attribute", function() -- refusals it("must be a string", function() local route = { methods = { false }, protocols = { "http" }, } local ok, err = Routes:validate(route) assert.falsy(ok) assert.equal("expected a string", err.methods[1]) end) it("must be a non-empty string", function() local route = { methods = { "" }, protocols = { "http" }, } local ok, err = Routes:validate(route) assert.falsy(ok) assert.equal("length must be at least 1", err.methods[1]) end) it("rejects invalid values", function() local invalid_methods = { "HELLO WORLD", " GET", } for i = 1, #invalid_methods do local route = { methods = { invalid_methods[i] }, protocols = { "http" }, } local ok, err = Routes:validate(route) assert.falsy(ok) assert.equal("invalid value: " .. invalid_methods[i], err.methods[1]) end end) it("rejects non-uppercased values", function() local route = { methods = { "get" }, protocols = { "http" }, } local ok, err = Routes:validate(route) assert.falsy(ok) assert.equal("invalid value: get", err.methods[1]) end) -- acceptance it("accepts valid HTTP methods", function() local valid_methods = { "GET", "POST", "CUSTOM", } for i = 1, #valid_methods do local route = Routes:process_auto_fields({ protocols = { "http" }, service = { id = a_valid_uuid }, paths = {}, hosts = {}, methods = { valid_methods[i] }, }, "insert") local ok, err = Routes:validate(route) assert.is_nil(err) assert.is_true(ok) end end) end) describe("name attribute", function() -- refusals it("must be a string", function() local route = { name = false, protocols = {"http"} } local ok, err = Routes:validate(route) assert.falsy(ok) assert.equal("expected a string", err.name) end) it("must be a non-empty string", function() local route = { name = "", protocols = {"http"} } local ok, err = Routes:validate(route) assert.falsy(ok) assert.equal("length must be at least 1", err.name) end) it("rejects invalid names", function() local invalid_names = { "examp:le", "examp;le", "examp/le", "examp le", -- see tests for utils.validate_utf8 for more invalid values string.char(105, 213, 205, 149), } for i = 1, #invalid_names do local route = { name = invalid_names[i], protocols = {"http"} } local ok, err = Routes:validate(route) assert.falsy(ok) assert.matches("invalid", err.name) end end) -- acceptance it("accepts valid names", function() local valid_names = { "example", "EXAMPLE", "exa.mp.le", "3x4mp13", "3x4-mp-13", "3x4_mp_13", "~3x4~mp~13", "~3..x4~.M-p~1__3_", "孔", "Конг", "🦍", } for i = 1, #valid_names do local route = Routes:process_auto_fields({ protocols = { "http" }, paths = { "/" }, name = valid_names[i], service = { id = a_valid_uuid } }, "insert") local ok, err = Routes:validate(route) assert.is_nil(err) assert.is_true(ok) end end) end) describe("#stream context", function() it("'protocol' accepts 'tcp'", function() local s = { id = "a4fbd24e-6a52-4937-bd78-2536713072d2" } local route = Routes:process_auto_fields({ protocols = { "tcp" }, sources = {{ ip = "127.0.0.1" }}, service = s, }, "insert") local ok, errs = Routes:validate(route) assert.is_nil(errs) assert.truthy(ok) assert.same({ "tcp" }, route.protocols) end) it("'protocol' accepts 'tls'", function() local s = { id = "a4fbd24e-6a52-4937-bd78-2536713072d2" } local route = Routes:process_auto_fields({ protocols = { "tls" }, sources = {{ ip = "127.0.0.1" }}, service = s, }, "insert") local ok, errs = Routes:validate(route) assert.is_nil(errs) assert.truthy(ok) assert.same({ "tls" }, route.protocols) end) it("'protocol' accepts 'udp'", function() local s = { id = "a4fbd24e-6a52-4937-bd78-2536713072d2" } local route = Routes:process_auto_fields({ protocols = { "udp" }, sources = {{ ip = "127.0.0.1" }}, service = s, }, "insert") local ok, errs = Routes:validate(route) assert.is_nil(errs) assert.truthy(ok) assert.same({ "udp" }, route.protocols) end) it("if 'protocol = tcp/tls/udp', then 'paths' is empty", function() local s = { id = "a4fbd24e-6a52-4937-bd78-2536713072d2" } for _, v in ipairs({ "tcp", "tls", "udp" }) do local route = Routes:process_auto_fields({ protocols = { v }, sources = {{ ip = "127.0.0.1" }}, paths = { "/" }, service = s, }, "insert") local ok, errs = Routes:validate(route) assert.falsy(ok) assert.same({ paths = "cannot set 'paths' when 'protocols' is 'tcp', 'tls', 'tls_passthrough' or 'udp'", }, errs) end end) it("if 'protocol = tcp/tls/udp', then 'methods' is empty", function() local s = { id = "a4fbd24e-6a52-4937-bd78-2536713072d2" } for _, v in ipairs({ "tcp", "tls", "udp" }) do local route = Routes:process_auto_fields({ protocols = { v }, sources = {{ ip = "127.0.0.1" }}, methods = { "GET" }, service = s, }, "insert") local ok, errs = Routes:validate(route) assert.falsy(ok) assert.same({ methods = "cannot set 'methods' when 'protocols' is 'tcp', 'tls', 'tls_passthrough' or 'udp'", }, errs) end end) describe("'sources' and 'destinations' matching attributes", function() local s = { id = "a4fbd24e-6a52-4937-bd78-2536713072d2" } for _, v in ipairs({ "sources", "destinations" }) do it("'" .. v .. "' accepts valid IPs and ports", function() for _, protocol in ipairs({ "tcp", "tls", "udp" }) do local route = Routes:process_auto_fields({ protocols = { protocol }, [v] = { { ip = "127.75.78.72", port = 8000 }, }, service = s, }, "insert") local ok, errs = Routes:validate(route) assert.is_nil(errs) assert.truthy(ok) assert.same({ protocol }, route.protocols) assert.same({ ip = "127.75.78.72", port = 8000 }, route[v][1]) end end) it("'" .. v .. "' accepts valid IPs (no port)", function() for _, protocol in ipairs({ "tcp", "tls", "udp" }) do local route = Routes:process_auto_fields({ protocols = { protocol }, [v] = { { ip = "127.0.0.1" }, { ip = "127.75.78.72", port = 8000 }, }, service = s, }, "insert") local ok, errs = Routes:validate(route) assert.is_nil(errs) assert.truthy(ok) assert.same({ protocol }, route.protocols) assert.same({ ip = "127.0.0.1", port = ngx.null }, route[v][1]) end end) it("'" .. v .. "' accepts valid ports (no IP)", function() for _, protocol in ipairs({ "tcp", "tls", "udp" }) do local route = Routes:process_auto_fields({ protocols = { protocol }, [v] = { { port = 8000 }, { ip = "127.75.78.72", port = 8000 }, }, service = s, }, "insert") local ok, errs = Routes:validate(route) assert.is_nil(errs) assert.truthy(ok) assert.same({ protocol }, route.protocols) assert.same({ ip = ngx.null, port = 8000 }, route[v][1]) end end) it("'" .. v .. "' rejects invalid 'port' values", function() for _, protocol in ipairs({ "tcp", "tls", "udp" }) do local route = Routes:process_auto_fields({ protocols = { protocol }, [v] = { { ip = "127.0.0.1" }, { ip = "127.75.78.72", port = 65536 }, }, service = s, }, "insert") local ok, errs = Routes:validate(route) assert.falsy(ok) assert.same({ [v] = { [2] = { port = "value should be between 0 and 65535" } }, }, errs) end end) it("'" .. v .. "' rejects invalid 'ip' values", function() -- invalid IPs for _, ip_val in ipairs({ "127.", ":::1", "-1", "localhost", "foo" }) do for _, protocol in ipairs({ "tcp", "tls", "udp" }) do local route = Routes:process_auto_fields({ protocols = { protocol }, [v] = { { ip = ip_val }, { ip = "127.75.78.72", port = 8000 }, }, service = s, }, "insert") local ok, errs = Routes:validate(route) assert.falsy(ok, "ip test value was valid: " .. ip_val) assert.equal("invalid ip or cidr range: '" .. ip_val .. "'", errs[v][1].ip) end end -- hostnames for _, ip_val in ipairs({ "f", "example.org" }) do for _, protocol in ipairs({ "tcp", "tls", "udp" }) do local route = Routes:process_auto_fields({ protocols = { protocol }, [v] = { { ip = ip_val }, { ip = "127.75.78.72", port = 8000 }, }, service = s, }, "insert") local ok, errs = Routes:validate(route) assert.falsy(ok, "ip test value was valid: " .. ip_val) assert.equal("invalid ip or cidr range: '" .. ip_val .. "'", errs[v][1].ip) end end end) it("'" .. v .. "' accepts valid 'ip cidr' values", function() -- valid CIDRs for _, ip_val in ipairs({ "0.0.0.0/0", "::/0", "0.0.0.0/1", "::/1", "0.0.0.0/32", "::/128" }) do for _, protocol in ipairs({ "tcp", "tls", "udp" }) do local route = Routes:process_auto_fields({ protocols = { protocol }, [v] = { { ip = ip_val }, { ip = "127.75.78.72", port = 8000 }, }, service = s, }, "insert") local ok, errs = Routes:validate(route) assert.truthy(ok, "ip test value was valid: " .. ip_val) assert.is_nil(errs) end end end) it("'" .. v .. "' rejects invalid 'ip cidr' values", function() -- invalid CIDRs for _, ip_val in ipairs({ "1/0", "2130706433/2", "4294967295/3", "-1/0", "4294967296/2", "0.0.0.0/a", "::/a", "0.0.0.0/-1", "::/-1", "0.0.0.0/33", "::/129" }) do for _, protocol in ipairs({ "tcp", "tls", "udp" }) do local route = Routes:process_auto_fields({ protocols = { protocol }, [v] = { { ip = ip_val }, { ip = "127.75.78.72", port = 8000 }, }, service = s, }, "insert") local ok, errs = Routes:validate(route) assert.falsy(ok, "ip test value was valid: " .. ip_val) assert.equal("invalid ip or cidr range: '" .. ip_val .. "'", errs[v][1].ip) end end end) end end) describe("'snis' matching attribute", function() local s = { id = "a4fbd24e-6a52-4937-bd78-2536713072d2" } for _, protocol in ipairs { "tls", "https", "grpcs" } do it("accepts valid SNIs for " .. protocol .. " Routes", function() for _, sni in ipairs({ "example.org", "www.example.org" }) do local route = Routes:process_auto_fields({ protocols = { protocol }, snis = { sni }, service = s, }, "insert") local ok, errs = Routes:validate(route) assert.is_nil(errs) assert.truthy(ok) end end) end it("rejects invalid SNIs", function() for _, sni in ipairs({ "127.0.0.1", "example.org:80" }) do local route = Routes:process_auto_fields({ protocols = { "tcp", "tls" }, snis = { sni }, service = s, }, "insert") local ok, errs = Routes:validate(route) assert.falsy(ok, "sni test value was valid: " .. sni) if not pcall(function() assert.matches("must not be an IP", errs.snis[1], nil, true) end) then assert.matches("must not have a port", errs.snis[1], nil, true) end end end) it("rejects specifying 'snis' if 'protocols' does not have 'https', 'tls' or 'tls_passthrough'", function() local route = Routes:process_auto_fields({ protocols = { "tcp", "udp" }, snis = { "example.org" }, service = s, }, "insert") local ok, errs = Routes:validate(route) assert.falsy(ok) assert.same({ ["@entity"] = { "'snis' can only be set when 'protocols' is 'grpcs', 'https', 'tls' or 'tls_passthrough'", }, snis = "length must be 0", }, errs) end) end) it("errors if no L4 matching attribute set", function() local s = { id = "a4fbd24e-6a52-4937-bd78-2536713072d2" } for _, v in ipairs({ "tcp", "tls", "udp" }) do local route = Routes:process_auto_fields({ protocols = { v }, service = s, }, "insert") local ok, errs = Routes:validate(route) assert.falsy(ok) assert.same({ ["@entity"] = { "must set one of 'sources', 'destinations', 'snis' when 'protocols' is 'tcp', 'tls' or 'udp'" } }, errs) end end) end) it("errors if no L7 matching attribute set", function() local s = { id = "a4fbd24e-6a52-4937-bd78-2536713072d2" } local route = Routes:process_auto_fields({ protocols = { "http" }, service = s, }, "insert") local ok, errs = Routes:validate(route) assert.falsy(ok) assert.same({ ["@entity"] = { "must set one of 'methods', 'hosts', 'headers', 'paths' when 'protocols' is 'http'" } }, errs) route = Routes:process_auto_fields({ protocols = { "https" }, service = s, }, "insert") ok, errs = Routes:validate(route) assert.falsy(ok) assert.same({ ["@entity"] = { "must set one of 'methods', 'hosts', 'headers', 'paths', 'snis' when 'protocols' is 'https'" } }, errs) end) it("errors if no L7 matching attribute set", function() local s = { id = "a4fbd24e-6a52-4937-bd78-2536713072d2" } local route = Routes:process_auto_fields({ protocols = { "grpc" }, service = s, }, "insert") local ok, errs = Routes:validate(route) assert.falsy(ok) assert.same({ ["@entity"] = { "must set one of 'hosts', 'headers', 'paths' when 'protocols' is 'grpc'" } }, errs) route = Routes:process_auto_fields({ protocols = { "grpcs" }, service = s, }, "insert") ok, errs = Routes:validate(route) assert.falsy(ok) assert.same({ ["@entity"] = { "must set one of 'hosts', 'headers', 'paths', 'snis' when 'protocols' is 'grpcs'" } }, errs) end) it("errors if methods attribute is set on grpc/grpcs", function() local s = { id = "a4fbd24e-6a52-4937-bd78-2536713072d2" } local route = Routes:process_auto_fields({ methods = "GET", paths = { "/foo" }, protocols = { "grpc" }, service = s, }, "insert") local ok, errs = Routes:validate(route) assert.falsy(ok) assert.same({ methods = "cannot set 'methods' when 'protocols' is 'grpc' or 'grpcs'" }, errs) route = Routes:process_auto_fields({ methods = "GET", paths = { "/foo" }, protocols = { "grpcs" }, service = s, }, "insert") ok, errs = Routes:validate(route) assert.falsy(ok) assert.same({ methods = "cannot set 'methods' when 'protocols' is 'grpc' or 'grpcs'" }, errs) end) it("errors if methods attribute is set on grpc/grpcs", function() local s = { id = "a4fbd24e-6a52-4937-bd78-2536713072d2" } local route = Routes:process_auto_fields({ methods = "GET", paths = { "/foo" }, protocols = { "grpc" }, service = s, }, "insert") local ok, errs = Routes:validate(route) assert.falsy(ok) assert.same({ methods = "cannot set 'methods' when 'protocols' is 'grpc' or 'grpcs'" }, errs) route = Routes:process_auto_fields({ methods = "GET", paths = { "/foo" }, protocols = { "grpcs" }, service = s, }, "insert") ok, errs = Routes:validate(route) assert.falsy(ok) assert.same({ methods = "cannot set 'methods' when 'protocols' is 'grpc' or 'grpcs'" }, errs) end) it("errors if strip_path is set on grpc/grpcs", function() local s = { id = "a4fbd24e-6a52-4937-bd78-2536713072d2" } local route = Routes:process_auto_fields({ hosts = { "foo.grpc.com" }, protocols = { "grpc" }, strip_path = true, service = s, }, "insert") local ok, errs = Routes:validate(route) assert.falsy(ok) assert.same({ strip_path = "cannot set 'strip_path' when 'protocols' is 'grpc' or 'grpcs'" }, errs) route = Routes:process_auto_fields({ hosts = { "foo.grpc.com" }, protocols = { "grpcs" }, strip_path = true, service = s, }, "insert") ok, errs = Routes:validate(route) assert.falsy(ok) assert.same({ strip_path = "cannot set 'strip_path' when 'protocols' is 'grpc' or 'grpcs'" }, errs) end) it("errors if tls and tls_passthrough set on a same route", function() local s = { id = "a4fbd24e-6a52-4937-bd78-2536713072d2" } local route = Routes:process_auto_fields({ snis = { "foo.grpc.com" }, protocols = { "tls", "tls_passthrough" }, service = s, }, "insert") local ok, errs = Routes:validate(route) assert.falsy(ok) assert.same({ protocols = "these sets are mutually exclusive: ('tcp', 'tls', 'udp'), ('tls_passthrough')", }, errs) end) it("errors if snis is not set on tls_pasthrough", function() local s = { id = "a4fbd24e-6a52-4937-bd78-2536713072d2" } local route = Routes:process_auto_fields({ sources = {{ ip = "127.0.0.1" }}, protocols = { "tls_passthrough" }, service = s, }, "insert") local ok, errs = Routes:validate(route) assert.falsy(ok) assert.same({ ["@entity"] = { "must set snis when 'protocols' is 'tls_passthrough'" }, }, errs) end) end)