kong/spec/01-unit/01-db/01-schema/11-declarative_config/03-flatten_spec.lua (2,149 lines of code) (raw):
local declarative_config = require "kong.db.schema.others.declarative_config"
local helpers = require "spec.helpers"
local lyaml = require "lyaml"
local cjson = require "cjson"
local tablex = require "pl.tablex"
local utils = require "kong.tools.utils"
local null = ngx.null
local function sort_by_key(t)
return function(a, b)
for _, k in ipairs({"name", "username", "host", "scope"}) do
local ka = t[a][k] ~= null and t[a][k]
local kb = t[b][k] ~= null and t[b][k]
if ka and kb then
return ka < kb
end
end
end
end
local function sortedpairs(t, fn)
local ks = tablex.keys(t)
table.sort(ks, fn and fn(t))
local i = 0
return function()
i = i + 1
return ks[i], t[ks[i]]
end
end
assert:set_parameter("TableFormatLevel", 10)
local function idempotent(tbl, err)
assert.table(tbl, err)
for entity, items in sortedpairs(tbl) do
local new = {}
for _, item in sortedpairs(items, sort_by_key) do
table.insert(new, item)
end
tbl[entity] = new
end
local function recurse_fields(t)
helpers.deep_sort(t)
for k,v in sortedpairs(t) do
if k == "id" and utils.is_valid_uuid(v) then
t[k] = "UUID"
end
if k == "client_id" or k == "client_secret" or k == "access_token" then
t[k] = "RANDOM"
end
if type(v) == "table" then
recurse_fields(v)
end
if k == "created_at" or k == "updated_at" then
t[k] = 1234567890
end
end
end
recurse_fields(tbl)
table.sort(tbl)
return tbl
end
-- To generate the expected output of a test case, use the following.
-- Verify that the output is correct, then paste it back to the test file.
local function print_assert(config) -- luacheck: ignore
local inspect = require("inspect")
local remove_all_metatables = function(item, path)
if path[#path] ~= inspect.METATABLE then return item end
end
local opts = { process = remove_all_metatables }
print("assert.same(", require"inspect"(idempotent(config), opts):gsub("<userdata 1>", "null"), ", idempotent(config))")
end
describe("declarative config: flatten", function()
local DeclarativeConfig
lazy_setup(function()
DeclarativeConfig = assert(declarative_config.load(helpers.test_conf.loaded_plugins))
end)
describe("core entities", function()
describe("services:", function()
it("accepts an empty list", function()
local config = lyaml.load([[
_format_version: "1.1"
services:
]])
config = DeclarativeConfig:flatten(config)
assert.same({}, idempotent(config))
end)
it("accepts entities", function()
local config = assert(lyaml.load([[
_format_version: "1.1"
services:
- name: foo
host: example.com
protocol: https
enabled: false
_comment: my comment
_ignore:
- foo: bar
- name: bar
host: example.test
port: 3000
_comment: my comment
_ignore:
- foo: bar
tags: [hello, world]
]]))
config = DeclarativeConfig:flatten(config)
assert.same({
services = {
{
id = "UUID",
created_at = 1234567890,
updated_at = 1234567890,
name = "bar",
protocol = "http",
host = "example.test",
path = null,
port = 3000,
connect_timeout = 60000,
read_timeout = 60000,
write_timeout = 60000,
retries = 5,
tags = {"hello", "world"},
client_certificate = null,
tls_verify_depth = null,
tls_verify = null,
ca_certificates = null,
enabled = true,
},
{
id = "UUID",
created_at = 1234567890,
updated_at = 1234567890,
name = "foo",
protocol = "https",
host = "example.com",
path = null,
port = 80,
connect_timeout = 60000,
read_timeout = 60000,
write_timeout = 60000,
retries = 5,
tags = null,
client_certificate = null,
tls_verify_depth = null,
tls_verify = null,
ca_certificates = null,
enabled = false,
},
}
}, idempotent(config))
end)
it("accepts field names with the same name as entities", function()
-- "snis" is also an entity
local config = assert(lyaml.load([[
_format_version: "1.1"
routes:
- name: foo
path_handling: v0
protocols: ["tls"]
snis:
- "example.com"
]]))
config = DeclarativeConfig:flatten(config)
assert.same({
routes = {
{
tags = null,
created_at = 1234567890,
destinations = null,
hosts = null,
headers = null,
id = "UUID",
methods = null,
name = "foo",
paths = null,
preserve_host = false,
https_redirect_status_code = 426,
protocols = { "tls" },
regex_priority = 0,
service = null,
snis = { "example.com" },
sources = null,
strip_path = true,
path_handling = "v0",
updated_at = 1234567890,
request_buffering = true,
response_buffering = true,
}
}
}, idempotent(config))
end)
it("allows url shorthand", function()
local config = lyaml.load([[
_format_version: "1.1"
services:
- name: foo
# url shorthand also works, and expands into multiple fields
url: https://example.com:8000/hello/world
]])
config = DeclarativeConfig:flatten(config)
assert.same({
services = {
{
id = "UUID",
created_at = 1234567890,
updated_at = 1234567890,
tags = null,
name = "foo",
protocol = "https",
host = "example.com",
port = 8000,
path = "/hello/world",
connect_timeout = 60000,
read_timeout = 60000,
write_timeout = 60000,
retries = 5,
client_certificate = null,
tls_verify_depth = null,
tls_verify = null,
ca_certificates = null,
enabled = true,
}
}
}, idempotent(config))
end)
end)
describe("plugins:", function()
it("accepts an empty list", function()
local config = lyaml.load([[
_format_version: "1.1"
plugins:
]])
config = DeclarativeConfig:flatten(config)
assert.same({}, idempotent(config))
end)
it("accepts entities", function()
local config = assert(lyaml.load([[
_format_version: "1.1"
plugins:
- name: key-auth
_comment: my comment
_ignore:
- foo: bar
- name: http-log
config:
http_endpoint: https://example.com
_comment: my comment
_ignore:
- foo: bar
]]))
config = DeclarativeConfig:flatten(config)
assert.same({
plugins = {
{
id = "UUID",
tags = null,
created_at = 1234567890,
consumer = null,
service = null,
route = null,
name = "http-log",
enabled = true,
protocols = { "grpc", "grpcs", "http", "https" },
config = {
http_endpoint = "https://example.com",
content_type = "application/json",
flush_timeout = 2,
keepalive = 60000,
method = "POST",
queue_size = 1,
retry_count = 10,
timeout = 10000,
headers = null,
custom_fields_by_lua = null,
}
},
{
id = "UUID",
tags = null,
created_at = 1234567890,
consumer = null,
service = null,
route = null,
name = "key-auth",
enabled = true,
protocols = { "grpc", "grpcs", "http", "https" },
config = {
anonymous = null,
hide_credentials = false,
key_in_header = true,
key_in_query = true,
key_in_body = false,
key_names = { "apikey" },
run_on_preflight = true,
}
},
}
}, idempotent(config))
end)
it("fails with missing foreign relationships", function()
local config = lyaml.load([[
_format_version: "1.1"
plugins:
- name: http-log
service: svc1
consumer: my-consumer
config:
http_endpoint: https://example.com
]])
local _, err = DeclarativeConfig:flatten(config)
assert.same({
plugins = {
[1] = {
"invalid reference 'consumer: my-consumer' (no such entry in 'consumers')",
"invalid reference 'service: svc1' (no such entry in 'services')",
}
}
}, idempotent(err))
end)
it("succeeds with present foreign relationships", function()
local config = lyaml.load([[
_format_version: "1.1"
services:
- name: svc1
host: example.com
routes:
- name: r1
path_handling: v0
paths: [/]
service: svc1
consumers:
- username: my-consumer
plugins:
- name: key-auth
route: r1
- name: http-log
service: svc1
consumer: my-consumer
config:
http_endpoint: https://example.com
]])
config = DeclarativeConfig:flatten(config)
assert.same({
consumers = {
{
tags = null,
created_at = 1234567890,
custom_id = null,
id = "UUID",
username = "my-consumer",
}
},
plugins = {
{
tags = null,
config = {
content_type = "application/json",
flush_timeout = 2,
http_endpoint = "https://example.com",
keepalive = 60000,
method = "POST",
queue_size = 1,
retry_count = 10,
timeout = 10000,
headers = null,
custom_fields_by_lua = null,
},
consumer = {
id = "UUID"
},
created_at = 1234567890,
enabled = true,
id = "UUID",
name = "http-log",
route = null,
protocols = { "grpc", "grpcs", "http", "https" },
service = {
id = "UUID"
}
},
{
tags = null,
config = {
anonymous = null,
hide_credentials = false,
key_in_header = true,
key_in_query = true,
key_in_body = false,
key_names = { "apikey" },
run_on_preflight = true
},
consumer = null,
created_at = 1234567890,
enabled = true,
id = "UUID",
name = "key-auth",
route = {
id = "UUID"
},
protocols = { "grpc", "grpcs", "http", "https" },
service = null
},
},
routes = {
{
tags = null,
created_at = 1234567890,
destinations = null,
hosts = null,
headers = null,
id = "UUID",
methods = null,
name = "r1",
paths = { "/" },
preserve_host = false,
protocols = { "http", "https" },
https_redirect_status_code = 426,
regex_priority = 0,
service = {
id = "UUID"
},
snis = null,
sources = null,
strip_path = true,
path_handling = "v0",
updated_at = 1234567890,
request_buffering = true,
response_buffering = true,
}
},
services = {
{
tags = null,
connect_timeout = 60000,
created_at = 1234567890,
host = "example.com",
id = "UUID",
name = "svc1",
path = null,
port = 80,
protocol = "http",
read_timeout = 60000,
retries = 5,
updated_at = 1234567890,
write_timeout = 60000,
client_certificate = null,
tls_verify_depth = null,
tls_verify = null,
ca_certificates = null,
enabled = true,
}
}
}, idempotent(config))
end)
end)
describe("nested relationships:", function()
describe("plugins in services", function()
it("accepts an empty list", function()
local config = lyaml.load([[
_format_version: "1.1"
services:
- name: foo
plugins: []
host: example.com
]])
config = DeclarativeConfig:flatten(config)
assert.same({
services = {
{
tags = null,
connect_timeout = 60000,
created_at = 1234567890,
host = "example.com",
id = "UUID",
name = "foo",
path = null,
port = 80,
protocol = "http",
read_timeout = 60000,
retries = 5,
updated_at = 1234567890,
write_timeout = 60000,
client_certificate = null,
tls_verify_depth = null,
tls_verify = null,
ca_certificates = null,
enabled = true,
}
}
}, idempotent(config))
end)
it("accepts entities", function()
local config = assert(lyaml.load([[
_format_version: "1.1"
services:
- name: foo
host: example.com
protocol: https
_comment: my comment
_ignore:
- foo: bar
plugins:
- name: key-auth
_comment: my comment
_ignore:
- foo: bar
- name: http-log
config:
http_endpoint: https://example.com
- name: bar
host: example.test
port: 3000
plugins:
- name: basic-auth
- name: tcp-log
config:
host: 127.0.0.1
port: 10000
]]))
config = DeclarativeConfig:flatten(config)
assert.same({
plugins = { {
config = {
anonymous = null,
hide_credentials = false
},
consumer = null,
created_at = 1234567890,
enabled = true,
id = "UUID",
name = "basic-auth",
protocols = { "grpc", "grpcs", "http", "https" },
route = null,
service = {
id = "UUID"
},
tags = null,
}, {
config = {
content_type = "application/json",
flush_timeout = 2,
http_endpoint = "https://example.com",
keepalive = 60000,
method = "POST",
queue_size = 1,
retry_count = 10,
timeout = 10000,
headers = null,
custom_fields_by_lua = null,
},
consumer = null,
created_at = 1234567890,
enabled = true,
id = "UUID",
name = "http-log",
protocols = { "grpc", "grpcs", "http", "https" },
route = null,
service = {
id = "UUID"
},
tags = null,
}, {
config = {
anonymous = null,
hide_credentials = false,
key_in_header = true,
key_in_query = true,
key_in_body = false,
key_names = { "apikey" },
run_on_preflight = true
},
consumer = null,
created_at = 1234567890,
enabled = true,
id = "UUID",
name = "key-auth",
protocols = { "grpc", "grpcs", "http", "https" },
route = null,
service = {
id = "UUID"
},
tags = null,
}, {
config = {
host = "127.0.0.1",
keepalive = 60000,
port = 10000,
timeout = 10000,
tls = false,
tls_sni = null,
custom_fields_by_lua = null,
},
consumer = null,
created_at = 1234567890,
enabled = true,
id = "UUID",
name = "tcp-log",
protocols = { "grpc", "grpcs", "http", "https" },
route = null,
service = {
id = "UUID"
},
tags = null,
} },
services = { {
connect_timeout = 60000,
created_at = 1234567890,
host = "example.test",
id = "UUID",
name = "bar",
path = null,
port = 3000,
protocol = "http",
read_timeout = 60000,
retries = 5,
tags = null,
updated_at = 1234567890,
write_timeout = 60000,
client_certificate = null,
tls_verify_depth = null,
tls_verify = null,
ca_certificates = null,
enabled = true,
}, {
connect_timeout = 60000,
created_at = 1234567890,
host = "example.com",
id = "UUID",
name = "foo",
path = null,
port = 80,
protocol = "https",
read_timeout = 60000,
retries = 5,
tags = null,
updated_at = 1234567890,
write_timeout = 60000,
client_certificate = null,
tls_verify_depth = null,
tls_verify = null,
ca_certificates = null,
enabled = true,
} }
}, idempotent(config))
end)
end)
describe("routes in services", function()
it("accepts an empty list", function()
local config = lyaml.load([[
_format_version: "1.1"
services:
- name: foo
routes: []
host: example.com
]])
config = DeclarativeConfig:flatten(config)
assert.same({
services = {
{
tags = null,
connect_timeout = 60000,
created_at = 1234567890,
host = "example.com",
id = "UUID",
name = "foo",
path = null,
port = 80,
protocol = "http",
read_timeout = 60000,
retries = 5,
updated_at = 1234567890,
write_timeout = 60000,
client_certificate = null,
tls_verify_depth = null,
tls_verify = null,
ca_certificates = null,
enabled = true,
}
}
}, idempotent(config))
end)
it("accepts a single entity", function()
local config = assert(lyaml.load([[
_format_version: "1.1"
services:
- name: foo
host: example.com
protocol: https
routes:
- path_handling: v0
paths:
- /path
]]))
config = DeclarativeConfig:flatten(config)
assert.same({
routes = { {
created_at = 1234567890,
destinations = null,
hosts = null,
headers = null,
id = "UUID",
methods = null,
name = null,
paths = { "/path" },
preserve_host = false,
protocols = { "http", "https" },
https_redirect_status_code = 426,
regex_priority = 0,
service = {
id = "UUID"
},
snis = null,
sources = null,
strip_path = true,
path_handling = "v0",
tags = null,
updated_at = 1234567890,
request_buffering = true,
response_buffering = true,
} },
services = { {
connect_timeout = 60000,
created_at = 1234567890,
host = "example.com",
id = "UUID",
name = "foo",
path = null,
port = 80,
protocol = "https",
read_timeout = 60000,
retries = 5,
tags = null,
updated_at = 1234567890,
write_timeout = 60000,
client_certificate = null,
tls_verify_depth = null,
tls_verify = null,
ca_certificates = null,
enabled = true,
} }
}, idempotent(config))
end)
it("accepts multiple entities", function()
local config = assert(lyaml.load([[
_format_version: "1.1"
services:
- name: foo
host: example.com
protocol: https
routes:
- path_handling: v0
paths:
- /path
name: r1
- path_handling: v0
hosts:
- example.com
name: r2
- path_handling: v0
methods: ["GET", "POST"]
name: r3
- name: bar
host: example.test
port: 3000
routes:
- path_handling: v0
paths:
- /path
hosts:
- example.com
methods: ["GET", "POST"]
name: r4
]]))
config = DeclarativeConfig:flatten(config)
assert.same({
routes = { {
created_at = 1234567890,
destinations = null,
hosts = null,
headers = null,
id = "UUID",
methods = null,
name = "r1",
paths = { "/path" },
preserve_host = false,
protocols = { "http", "https" },
https_redirect_status_code = 426,
regex_priority = 0,
service = {
id = "UUID"
},
snis = null,
sources = null,
strip_path = true,
path_handling = "v0",
tags = null,
updated_at = 1234567890,
request_buffering = true,
response_buffering = true,
}, {
created_at = 1234567890,
destinations = null,
hosts = { "example.com" },
headers = null,
id = "UUID",
methods = null,
name = "r2",
paths = null,
preserve_host = false,
protocols = { "http", "https" },
https_redirect_status_code = 426,
regex_priority = 0,
service = {
id = "UUID"
},
snis = null,
sources = null,
strip_path = true,
path_handling = "v0",
tags = null,
updated_at = 1234567890,
request_buffering = true,
response_buffering = true,
}, {
created_at = 1234567890,
destinations = null,
hosts = null,
headers = null,
id = "UUID",
methods = { "GET", "POST" },
name = "r3",
paths = null,
preserve_host = false,
protocols = { "http", "https" },
https_redirect_status_code = 426,
regex_priority = 0,
service = {
id = "UUID"
},
snis = null,
sources = null,
strip_path = true,
path_handling = "v0",
tags = null,
updated_at = 1234567890,
request_buffering = true,
response_buffering = true,
}, {
created_at = 1234567890,
destinations = null,
hosts = { "example.com" },
headers = null,
id = "UUID",
methods = { "GET", "POST" },
name = "r4",
paths = { "/path" },
preserve_host = false,
protocols = { "http", "https" },
https_redirect_status_code = 426,
regex_priority = 0,
service = {
id = "UUID"
},
snis = null,
sources = null,
strip_path = true,
path_handling = "v0",
tags = null,
updated_at = 1234567890,
request_buffering = true,
response_buffering = true,
} },
services = { {
connect_timeout = 60000,
created_at = 1234567890,
host = "example.test",
id = "UUID",
name = "bar",
path = null,
port = 3000,
protocol = "http",
read_timeout = 60000,
retries = 5,
tags = null,
updated_at = 1234567890,
write_timeout = 60000,
client_certificate = null,
tls_verify_depth = null,
tls_verify = null,
ca_certificates = null,
enabled = true,
}, {
connect_timeout = 60000,
created_at = 1234567890,
host = "example.com",
id = "UUID",
name = "foo",
path = null,
port = 80,
protocol = "https",
read_timeout = 60000,
retries = 5,
tags = null,
updated_at = 1234567890,
write_timeout = 60000,
client_certificate = null,
tls_verify_depth = null,
tls_verify = null,
ca_certificates = null,
enabled = true,
} }
}, idempotent(config))
end)
end)
describe("plugins in routes in services", function()
it("accepts an empty list", function()
local config = assert(lyaml.load([[
_format_version: "1.1"
services:
- name: foo
host: example.com
protocol: https
routes:
- name: foo
path_handling: v0
methods: ["GET"]
plugins:
]]))
config = DeclarativeConfig:flatten(config)
assert.same({
routes = {
{
tags = null,
created_at = 1234567890,
destinations = null,
hosts = null,
headers = null,
id = "UUID",
methods = { "GET" },
name = "foo",
paths = null,
preserve_host = false,
protocols = { "http", "https" },
https_redirect_status_code = 426,
regex_priority = 0,
service = {
id = "UUID"
},
snis = null,
sources = null,
strip_path = true,
path_handling = "v0",
updated_at = 1234567890,
request_buffering = true,
response_buffering = true,
}
},
services = {
{
tags = null,
connect_timeout = 60000,
created_at = 1234567890,
host = "example.com",
id = "UUID",
name = "foo",
path = null,
port = 80,
protocol = "https",
read_timeout = 60000,
retries = 5,
updated_at = 1234567890,
write_timeout = 60000,
client_certificate = null,
tls_verify_depth = null,
tls_verify = null,
ca_certificates = null,
enabled = true,
}
}
}, idempotent(config))
end)
it("accepts entities", function()
local config = assert(lyaml.load([[
_format_version: "1.1"
services:
- name: foo
host: example.com
protocol: https
routes:
- name: foo
path_handling: v0
methods: ["GET"]
plugins:
- name: key-auth
- name: http-log
config:
http_endpoint: https://example.com
- name: bar
host: example.test
port: 3000
routes:
- name: bar
path_handling: v0
paths:
- /
plugins:
- name: basic-auth
- name: tcp-log
config:
host: 127.0.0.1
port: 10000
]]))
config = DeclarativeConfig:flatten(config)
assert.same({
plugins = { {
config = {
anonymous = null,
hide_credentials = false
},
consumer = null,
created_at = 1234567890,
enabled = true,
id = "UUID",
name = "basic-auth",
protocols = { "grpc", "grpcs", "http", "https" },
route = {
id = "UUID"
},
service = null,
tags = null,
}, {
config = {
content_type = "application/json",
flush_timeout = 2,
http_endpoint = "https://example.com",
keepalive = 60000,
method = "POST",
queue_size = 1,
retry_count = 10,
timeout = 10000,
headers = null,
custom_fields_by_lua = null,
},
consumer = null,
created_at = 1234567890,
enabled = true,
id = "UUID",
name = "http-log",
protocols = { "grpc", "grpcs", "http", "https" },
route = {
id = "UUID"
},
service = null,
tags = null,
}, {
config = {
anonymous = null,
hide_credentials = false,
key_in_header = true,
key_in_query = true,
key_in_body = false,
key_names = { "apikey" },
run_on_preflight = true
},
consumer = null,
created_at = 1234567890,
enabled = true,
id = "UUID",
name = "key-auth",
protocols = { "grpc", "grpcs", "http", "https" },
route = {
id = "UUID"
},
service = null,
tags = null,
}, {
config = {
host = "127.0.0.1",
keepalive = 60000,
port = 10000,
timeout = 10000,
tls = false,
tls_sni = null,
custom_fields_by_lua = null,
},
consumer = null,
created_at = 1234567890,
enabled = true,
id = "UUID",
name = "tcp-log",
protocols = { "grpc", "grpcs", "http", "https" },
route = {
id = "UUID"
},
service = null,
tags = null,
} },
routes = { {
created_at = 1234567890,
destinations = null,
hosts = null,
headers = null,
id = "UUID",
methods = null,
name = "bar",
paths = { "/" },
preserve_host = false,
protocols = { "http", "https" },
https_redirect_status_code = 426,
regex_priority = 0,
service = {
id = "UUID"
},
snis = null,
sources = null,
strip_path = true,
path_handling = "v0",
tags = null,
updated_at = 1234567890,
request_buffering = true,
response_buffering = true,
}, {
created_at = 1234567890,
destinations = null,
hosts = null,
headers = null,
id = "UUID",
methods = { "GET" },
name = "foo",
paths = null,
preserve_host = false,
protocols = { "http", "https" },
https_redirect_status_code = 426,
regex_priority = 0,
service = {
id = "UUID"
},
snis = null,
sources = null,
strip_path = true,
path_handling = "v0",
tags = null,
updated_at = 1234567890,
request_buffering = true,
response_buffering = true,
} },
services = { {
connect_timeout = 60000,
created_at = 1234567890,
host = "example.test",
id = "UUID",
name = "bar",
path = null,
port = 3000,
protocol = "http",
read_timeout = 60000,
retries = 5,
tags = null,
updated_at = 1234567890,
write_timeout = 60000,
client_certificate = null,
tls_verify_depth = null,
tls_verify = null,
ca_certificates = null,
enabled = true,
}, {
connect_timeout = 60000,
created_at = 1234567890,
host = "example.com",
id = "UUID",
name = "foo",
path = null,
port = 80,
protocol = "https",
read_timeout = 60000,
retries = 5,
tags = null,
updated_at = 1234567890,
write_timeout = 60000,
client_certificate = null,
tls_verify_depth = null,
tls_verify = null,
ca_certificates = null,
enabled = true,
} }
}, idempotent(config))
end)
end)
end)
describe("upstream:", function()
it("identical targets", function()
local config = assert(lyaml.load([[
_format_version: '1.1'
upstreams:
- name: first-upstream
targets:
- target: 127.0.0.1:6661
weight: 1
- name: second-upstream
targets:
- target: 127.0.0.1:6661
weight: 1
]]))
config = DeclarativeConfig:flatten(config)
assert.same({
targets = {
{
created_at = 1234567890,
id = "UUID",
tags = null,
target = '127.0.0.1:6661',
upstream = { id = 'UUID' },
weight = 1,
},
{
created_at = 1234567890,
id = "UUID",
tags = null,
target = '127.0.0.1:6661',
upstream = { id = 'UUID' },
weight = 1,
},
},
}, idempotent({targets = config.targets}))
end)
end)
end)
describe("custom entities", function()
describe("basicauth_credentials:", function()
it("accepts an empty list", function()
local config = assert(lyaml.load([[
_format_version: "1.1"
basicauth_credentials:
]]))
config = DeclarativeConfig:flatten(config)
assert.same({}, idempotent(config))
local config = assert(lyaml.load([[
_format_version: "1.1"
consumers:
basicauth_credentials:
]]))
config = DeclarativeConfig:flatten(config)
assert.same({}, idempotent(config))
local config = assert(lyaml.load([[
_format_version: "1.1"
consumers:
- username: consumer
basicauth_credentials:
]]))
config = DeclarativeConfig:flatten(config)
assert.same({
consumers = {
{
id = 'UUID',
username = 'consumer',
custom_id = null,
created_at = 1234567890,
tags = null,
},
},
}, idempotent(config))
end)
it("accepts as a nested entity", function()
local config = assert(lyaml.load([[
_format_version: "1.1"
consumers:
- username: consumer
basicauth_credentials:
- username: username
password: password
]]))
config = DeclarativeConfig:flatten(config)
assert.same({
consumers = {
{
id = 'UUID',
username = 'consumer',
custom_id = null,
created_at = 1234567890,
tags = null,
},
},
basicauth_credentials = {
{
id = 'UUID',
consumer = {
id = 'UUID',
},
username = 'username',
password = 'password',
created_at = 1234567890,
tags = null,
},
},
}, idempotent(config))
end)
it("accepts as a nested entity by id", function()
local config = assert(lyaml.load([[
_format_version: "1.1"
consumers:
- id: 0fe87b4a-ce29-515a-88ec-8547e66550b9
username: consumer
basicauth_credentials:
- username: username
password: password
consumer: 0fe87b4a-ce29-515a-88ec-8547e66550b9
]]))
config = DeclarativeConfig:flatten(config)
assert.same({
consumers = {
{
id = 'UUID',
username = 'consumer',
custom_id = null,
created_at = 1234567890,
tags = null,
},
},
basicauth_credentials = {
{
id = 'UUID',
consumer = {
id = 'UUID',
},
username = 'username',
password = 'password',
created_at = 1234567890,
tags = null,
},
},
}, idempotent(config))
end)
it("fails as a nested entity by incorrect id", function()
local config = assert(lyaml.load([[
_format_version: "1.1"
consumers:
- id: 0fe87b4a-ce29-515a-88ec-8547e66550b9
username: consumer
basicauth_credentials:
- username: username
password: password
consumer: 00000000-0000-0000-0000-000000000000
]]))
local config, err = DeclarativeConfig:flatten(config)
assert.equal(nil, config)
assert.same({
consumers = {
{
basicauth_credentials = {
{
["@entity"] = {
"all or none of these fields must be set: 'password', 'consumer.id'",
},
consumer = 'value must be null',
},
},
},
},
}, idempotent(err))
end)
it("accepts as a nested entity by username", function()
local config = assert(lyaml.load([[
_format_version: "1.1"
consumers:
- username: consumer
basicauth_credentials:
- username: username
password: password
consumer: consumer
]]))
config = DeclarativeConfig:flatten(config)
assert.same({
consumers = {
{
id = 'UUID',
username = 'consumer',
custom_id = null,
created_at = 1234567890,
tags = null,
},
},
basicauth_credentials = {
{
id = 'UUID',
consumer = {
id = 'UUID',
},
username = 'username',
password = 'password',
created_at = 1234567890,
tags = null,
},
},
}, idempotent(config))
end)
it("fails as a nested entity by incorrect username", function()
local config = assert(lyaml.load([[
_format_version: "1.1"
consumers:
- username: consumer
basicauth_credentials:
- username: username
password: password
consumer: incorrect
]]))
local config, err = DeclarativeConfig:flatten(config)
assert.equal(nil, config)
assert.same({
consumers = {
{
basicauth_credentials = {
{
["@entity"] = {
"all or none of these fields must be set: 'password', 'consumer.id'",
},
consumer = 'value must be null',
},
},
},
},
}, idempotent(err))
end)
it("accepts as an unnested entity by id", function()
local config = assert(lyaml.load([[
_format_version: "1.1"
consumers:
- id: 0fe87b4a-ce29-515a-88ec-8547e66550b9
username: consumer
basicauth_credentials:
- consumer: 0fe87b4a-ce29-515a-88ec-8547e66550b9
username: username
password: password
]]))
config = DeclarativeConfig:flatten(config)
assert.same({
consumers = {
{
id = 'UUID',
username = 'consumer',
custom_id = null,
created_at = 1234567890,
tags = null,
},
},
basicauth_credentials = {
{
id = 'UUID',
consumer = {
id = 'UUID',
},
username = 'username',
password = 'password',
created_at = 1234567890,
tags = null,
},
},
}, idempotent(config))
end)
it("fails as an unnested entity by incorrect id", function()
local config = assert(lyaml.load([[
_format_version: "1.1"
consumers:
- id: 0fe87b4a-ce29-515a-88ec-8547e66550b9
username: consumer
basicauth_credentials:
- consumer: 00000000-0000-0000-0000-000000000000
username: username
password: password
]]))
local config, err = DeclarativeConfig:flatten(config)
assert.equal(nil, config)
assert.same({
basicauth_credentials = {
{
["@entity"] = {
"all or none of these fields must be set: 'password', 'consumer.id'",
},
["consumer"] = {
["id"] = "missing primary key"
},
},
},
}, idempotent(err))
end)
it("accepts as an unnested entity by username", function()
local config = assert(lyaml.load([[
_format_version: "1.1"
consumers:
- username: consumer
basicauth_credentials:
- consumer: consumer
username: username
password: password
]]))
config = DeclarativeConfig:flatten(config)
assert.same({
consumers = {
{
id = 'UUID',
username = 'consumer',
custom_id = null,
created_at = 1234567890,
tags = null,
},
},
basicauth_credentials = {
{
id = 'UUID',
consumer = {
id = 'UUID',
},
username = 'username',
password = 'password',
created_at = 1234567890,
tags = null,
},
},
}, idempotent(config))
end)
it("fails as an unnested entity by incorrect username", function()
local config = assert(lyaml.load([[
_format_version: "1.1"
consumers:
- username: consumer
basicauth_credentials:
- consumer: incorrect
username: username
password: password
]]))
local config, err = DeclarativeConfig:flatten(config)
assert.equal(nil, config)
assert.same({
basicauth_credentials = {
{
["@entity"] = {
"all or none of these fields must be set: 'password', 'consumer.id'",
},
["consumer"] = {
["id"] = "missing primary key"
},
},
},
}, idempotent(err))
end)
end)
describe("oauth2_credentials:", function()
it("accepts an empty list", function()
local config = assert(lyaml.load([[
_format_version: "1.1"
oauth2_credentials:
]]))
config = DeclarativeConfig:flatten(config)
assert.same({}, idempotent(config))
end)
it("fails with invalid foreign key references", function()
local config = assert(lyaml.load([[
_format_version: "1.1"
oauth2_credentials:
- name: my-credential
consumer: foo
redirect_uris:
- https://example.com
- name: another-credential
consumer: foo
redirect_uris:
- https://example.test
]]))
local _, err = DeclarativeConfig:flatten(config)
err = idempotent(err)
assert.same({
oauth2_credentials = {
[1] = {
[1] = "invalid reference 'consumer: foo' (no such entry in 'consumers')"
},
[2] = {
[1] = "invalid reference 'consumer: foo' (no such entry in 'consumers')"
},
}
}, err)
end)
it("accepts entities", function()
local config = assert(lyaml.load([[
_format_version: "1.1"
consumers:
- username: bob
oauth2_credentials:
- name: my-credential
redirect_uris:
- https://example.com
tags:
- tag1
- name: another-credential
redirect_uris:
- https://example.test
tags:
- tag2
]]))
config = DeclarativeConfig:flatten(config)
config.consumers = nil
assert.same({
oauth2_credentials = { {
client_id = "RANDOM",
client_secret = "RANDOM",
client_type = "confidential",
hash_secret = false,
consumer = {
id = "UUID"
},
created_at = 1234567890,
id = "UUID",
name = "another-credential",
redirect_uris = { "https://example.test" },
tags = { "tag2" },
}, {
client_id = "RANDOM",
client_secret = "RANDOM",
client_type = "confidential",
hash_secret = false,
consumer = {
id = "UUID",
},
created_at = 1234567890,
id = "UUID",
name = "my-credential",
redirect_uris = { "https://example.com" },
tags = { "tag1" },
} }
}, idempotent(config))
end)
end)
describe("flat relationships:", function()
describe("jwt_secrets (globally unique) to consumers", function()
it("accepts entities", function()
local key = "-----BEGIN PUBLIC KEY-----\\n" ..
"MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQDMYfnvWtC8Id5bPKae5yXSxQTt\\n" ..
"+Zpul6AnnZWfI2TtIarvjHBFUtXRo96y7hoL4VWOPKGCsRqMFDkrbeUjRrx8iL91\\n" ..
"4/srnyf6sh9c8Zk04xEOpK1ypvBz+Ks4uZObtjnnitf0NBGdjMKxveTq+VE7BWUI\\n" ..
"yQjtQ8mbDOsiLLvh7wIDAQAB\\n" ..
"-----END PUBLIC KEY-----"
local config = assert(lyaml.load([[
_format_version: "1.1"
consumers:
- username: foo
jwt_secrets:
- consumer: foo
key: "https://keycloak/auth/realms/foo"
algorithm: RS256
rsa_public_key: "]] .. key .. [["
]]))
config = DeclarativeConfig:flatten(config)
config.jwt_secrets[next(config.jwt_secrets)].secret = nil
assert.same({
consumers = { {
created_at = 1234567890,
custom_id = null,
id = "UUID",
tags = null,
username = "foo",
} },
jwt_secrets = { {
algorithm = "RS256",
consumer = {
id = "UUID"
},
created_at = 1234567890,
id = "UUID",
key = "https://keycloak/auth/realms/foo",
rsa_public_key = key:gsub("\\n", "\n"),
tags = null,
} }
}, idempotent(config))
end)
end)
describe("targets (not globally unique) to upstreams", function()
it("accepts entities", function()
local config = assert(lyaml.load([[
_format_version: '1.1'
upstreams:
- name: first-upstream
- name: second-upstream
targets:
- upstream: first-upstream
target: 127.0.0.1:6661
weight: 1
- upstream: second-upstream
target: 127.0.0.1:6661
weight: 1
]]))
config = DeclarativeConfig:flatten(config)
assert.same(helpers.deep_sort{
targets = { {
created_at = 1234567890,
id = "UUID",
tags = null,
target = "127.0.0.1:6661",
upstream = {
id = "UUID"
},
weight = 1,
}, {
created_at = 1234567890,
id = "UUID",
tags = null,
target = "127.0.0.1:6661",
upstream = {
id = "UUID"
},
weight = 1,
} },
upstreams = { {
algorithm = "round-robin",
client_certificate = null,
created_at = 1234567890,
hash_fallback = "none",
hash_fallback_header = null,
hash_on = "none",
hash_on_cookie = null,
hash_on_cookie_path = "/",
hash_on_header = null,
hash_on_query_arg = null,
hash_fallback_query_arg = null,
hash_on_uri_capture = null,
hash_fallback_uri_capture = null,
healthchecks = {
active = {
concurrency = 10,
healthy = {
http_statuses = { 200, 302 },
interval = 0,
successes = 0
},
http_path = "/",
https_sni = null,
https_verify_certificate = true,
headers = null,
timeout = 1,
type = "http",
unhealthy = {
http_failures = 0,
http_statuses = { 429, 404, 500, 501, 502, 503, 504, 505 },
interval = 0,
tcp_failures = 0,
timeouts = 0
}
},
passive = {
healthy = {
http_statuses = { 200, 201, 202, 203, 204, 205, 206, 207, 208, 226, 300, 301, 302, 303, 304, 305, 306, 307, 308 },
successes = 0
},
type = "http",
unhealthy = {
http_failures = 0,
http_statuses = { 429, 500, 503 },
tcp_failures = 0,
timeouts = 0
}
},
threshold = 0
},
host_header = null,
id = "UUID",
name = "first-upstream",
slots = 10000,
tags = null,
}, {
algorithm = "round-robin",
client_certificate = null,
created_at = 1234567890,
hash_fallback = "none",
hash_fallback_header = null,
hash_on = "none",
hash_on_cookie = null,
hash_on_cookie_path = "/",
hash_on_header = null,
hash_on_query_arg = null,
hash_fallback_query_arg = null,
hash_on_uri_capture = null,
hash_fallback_uri_capture = null,
healthchecks = {
active = {
concurrency = 10,
healthy = {
http_statuses = { 200, 302 },
interval = 0,
successes = 0
},
http_path = "/",
https_sni = null,
https_verify_certificate = true,
headers = null,
timeout = 1,
type = "http",
unhealthy = {
http_failures = 0,
http_statuses = { 429, 404, 500, 501, 502, 503, 504, 505 },
interval = 0,
tcp_failures = 0,
timeouts = 0
}
},
passive = {
healthy = {
http_statuses = { 200, 201, 202, 203, 204, 205, 206, 207, 208, 226, 300, 301, 302, 303, 304, 305, 306, 307, 308 },
successes = 0
},
type = "http",
unhealthy = {
http_failures = 0,
http_statuses = { 429, 500, 503 },
tcp_failures = 0,
timeouts = 0
}
},
threshold = 0
},
host_header = null,
id = "UUID",
name = "second-upstream",
slots = 10000,
tags = null,
} }
}, idempotent(config))
end)
end)
end)
describe("nested relationships:", function()
describe("oauth2_credentials in consumers", function()
it("accepts an empty list", function()
local config = assert(lyaml.load([[
_format_version: "1.1"
consumers:
- username: bob
oauth2_credentials:
]]))
config = DeclarativeConfig:flatten(config)
assert.same({
consumers = { {
created_at = 1234567890,
custom_id = null,
id = "UUID",
tags = null,
username = "bob",
} }
}, idempotent(config))
end)
it("accepts entities", function()
local config = assert(lyaml.load([[
_format_version: "1.1"
consumers:
- username: bob
oauth2_credentials:
- name: my-credential
redirect_uris:
- https://example.com
- name: another-credential
redirect_uris:
- https://example.test
]]))
config = DeclarativeConfig:flatten(config)
assert.same({
consumers = { {
created_at = 1234567890,
custom_id = null,
id = "UUID",
tags = null,
username = "bob",
} },
oauth2_credentials = { {
client_id = "RANDOM",
client_secret = "RANDOM",
client_type = "confidential",
hash_secret = false,
consumer = {
id = "UUID"
},
created_at = 1234567890,
id = "UUID",
name = "another-credential",
redirect_uris = { "https://example.test" },
tags = null,
}, {
client_id = "RANDOM",
client_secret = "RANDOM",
client_type = "confidential",
hash_secret = false,
consumer = {
id = "UUID"
},
created_at = 1234567890,
id = "UUID",
name = "my-credential",
redirect_uris = { "https://example.com" },
tags = null,
} }
}, idempotent(config))
end)
end)
describe("oauth2_tokens in oauth2_credentials", function()
it("accepts an empty list", function()
local config = assert(lyaml.load([[
_format_version: "1.1"
consumers:
- username: bob
oauth2_credentials:
- name: my-credential
consumer: bob
redirect_uris:
- https://example.com
oauth2_tokens:
]]))
config = DeclarativeConfig:flatten(config)
config.consumers = nil
assert.same({
oauth2_credentials = { {
client_id = "RANDOM",
client_secret = "RANDOM",
client_type = "confidential",
hash_secret = false,
consumer = {
id = "UUID"
},
created_at = 1234567890,
id = "UUID",
name = "my-credential",
redirect_uris = { "https://example.com" },
tags = null,
} }
}, idempotent(config))
end)
it("accepts entities", function()
local config = assert(lyaml.load([[
_format_version: "1.1"
consumers:
- username: bob
oauth2_credentials:
- name: my-credential
consumer: bob
redirect_uris:
- https://example.com
oauth2_tokens:
- expires_in: 1
scope: "bar"
- expires_in: 10
scope: "foo"
]]))
local config = DeclarativeConfig:flatten(config)
config.consumers = nil
assert.same({
oauth2_credentials = { {
client_id = "RANDOM",
client_secret = "RANDOM",
client_type = "confidential",
hash_secret = false,
consumer = {
id = "UUID"
},
created_at = 1234567890,
id = "UUID",
name = "my-credential",
redirect_uris = { "https://example.com" },
tags = null,
} },
oauth2_tokens = {
{
access_token = "RANDOM",
authenticated_userid = null,
created_at = 1234567890,
credential = {
id = "UUID"
},
expires_in = 1,
id = "UUID",
refresh_token = null,
scope = "bar",
service = null,
token_type = "bearer",
}, {
access_token = "RANDOM",
authenticated_userid = null,
created_at = 1234567890,
credential = {
id = "UUID"
},
expires_in = 10,
id = "UUID",
refresh_token = null,
scope = "foo",
service = null,
token_type = "bearer",
}
}
}, idempotent(config))
end)
end)
end)
end)
describe("issues", function()
it("fixes #5750 - misleading error messages", function()
local config = assert(cjson.decode([[
{
"_format_version": "1.1",
"consumers": [
{
"username": "consumer-test",
"basicauth_credentials": [
{
"username": "some_user",
"password": "some_password"
}
]
}
],
"plugins": [
{
"name": "request-transformer",
"config": {
"add": {
"body": [],
"headers": [],
"querystring": []
},
"append": {
"body": [],
"headers": [
"X-Another-Header:foo"
],
"querystring": []
},
"remove": {
"body": [],
"headers": [
"X-Another-Header"
],
"querystring": []
},
"rename": {
"body": [],
"headers": [],
"querystring": []
},
"replace": {
"body": [],
"headers": [],
"querystring": [],
"uri": null
}
},
"route": "does-not-exist",
"enabled": true,
"protocols": [
"http",
"https"
]
}
]
}
]]))
local err
config, err = DeclarativeConfig:flatten(config)
assert.equal(nil, config)
assert.same({
plugins = {
{
["route"] = {
["id"] = "missing primary key"
},
},
},
}, idempotent(err))
end)
it("fixes #5920 - validation error on valid input", function()
local config = assert(lyaml.load([[
_format_version: "1.1"
services:
- name: test-service
routes:
- paths:
- /test/path
plugins:
- name: key-auth
url: https://example.com
consumers:
- username: test-user
basicauth_credentials:
- username: test-username
password: test-password
]]))
local _, err = DeclarativeConfig:flatten(config)
assert.equal(nil, err)
end)
it("fixes #7696 - incorrect foreign reference type produce useful error message", function()
local config = assert(lyaml.load([[
_format_version: "2.1"
services:
- name: my-service-1
url: http://localhost:8001/status
routes:
- name: my-route-1
service:
id: "769bdf51-16df-5476-9830-ef26800b5448"
paths:
- /status
]]))
local _, err = DeclarativeConfig:flatten(config)
assert.same({
routes = {
["my-route-1"] = { "invalid reference 'service: {\"id\":\"769bdf51-16df-5476-9830-ef26800b5448\"}' (no such entry in 'services')" }
}
}, err)
end)
it("fixes #7620 - yaml anchors work as expected", function()
local config = assert(lyaml.load([[
_format_version: "1.1"
services:
- name: service1
url: http://example.com
plugins:
- &correlation-plugin
name: correlation-id
config:
header_name: X-Request-Id
generator: uuid
echo_downstream: true
- &rate-limiting-plugin
name: rate-limiting
config:
second: 5
policy: local
routes:
- name: foo
strip_path: false
paths:
- /foo
- name: service2
url: http://example.com
plugins:
- *correlation-plugin
- *rate-limiting-plugin
routes:
- name: bar
strip_path: false
paths:
- /bar
]]))
local config, err = DeclarativeConfig:flatten(config)
assert.equal(nil, err)
local count = 0
for _, _ in pairs(config.plugins) do
count = count + 1
end
assert.equal(4, count)
end)
end)
end)