Files
2026-07-13 12:32:21 +08:00

876 lines
23 KiB
Lua

local DB = require "kong.db"
local helpers = require "spec.helpers"
local cycle_aware_deep_copy = require("kong.tools.table").cycle_aware_deep_copy
for _, strategy in helpers.each_strategy() do
local postgres_only = strategy == "postgres" and it or pending
describe("db_spec [#" .. strategy .. "]", function()
lazy_setup(function()
local _, db = helpers.get_db_utils(strategy, {})
-- db RO permissions setup
local pg_ro_user = helpers.test_conf.pg_ro_user
local pg_db = helpers.test_conf.pg_database
db:schema_reset()
db.connector:query(string.format("CREATE user %s;", pg_ro_user))
db.connector:query(string.format([[
GRANT CONNECT ON DATABASE %s TO %s;
GRANT USAGE ON SCHEMA public TO %s;
ALTER DEFAULT PRIVILEGES FOR ROLE kong IN SCHEMA public GRANT SELECT ON TABLES TO %s;
]], pg_db, pg_ro_user, pg_ro_user, pg_ro_user))
helpers.bootstrap_database(db)
end)
describe("kong.db.init", function()
describe(".new()", function()
it("errors on invalid arg", function()
assert.has_error(function()
DB.new(nil, strategy)
end, "missing kong_config")
assert.has_error(function()
DB.new(helpers.test_conf, 123)
end, "strategy must be a string")
end)
it("instantiates a DB", function()
local db, err = DB.new(helpers.test_conf, strategy)
assert.is_nil(err)
assert.is_table(db)
end)
it("initializes infos", function()
local db, err = DB.new(helpers.test_conf, strategy)
assert.is_nil(err)
assert.is_table(db)
local infos = db.infos
if strategy == "postgres" then
assert.same({
strategy = "PostgreSQL",
db_desc = "database",
db_name = helpers.test_conf.pg_database,
db_schema = helpers.test_conf.pg_schema or "",
db_ver = "unknown",
db_readonly = false,
}, infos)
else
error("unknown database")
end
end)
postgres_only("initializes infos with custom schema", function()
local conf = cycle_aware_deep_copy(helpers.test_conf)
conf.pg_schema = "demo"
local db, err = DB.new(conf, strategy)
assert.is_nil(err)
assert.is_table(db)
local infos = db.infos
assert.same({
strategy = "PostgreSQL",
db_desc = "database",
db_name = conf.pg_database,
db_schema = conf.pg_schema,
db_ver = "unknown",
db_readonly = false,
}, infos)
end)
postgres_only("initializes infos with readonly support", function()
local conf = cycle_aware_deep_copy(helpers.test_conf)
conf.pg_ro_host = "127.0.0.1"
-- hack
ngx.IS_CLI = false
local db, err = DB.new(conf, strategy)
-- hack
ngx.IS_CLI = true
assert.is_nil(err)
assert.is_table(db)
local infos = db.infos
assert.same({
strategy = "PostgreSQL",
db_desc = "database",
db_name = conf.pg_database,
db_schema = helpers.test_conf.pg_schema or "",
db_ver = "unknown",
db_readonly = true,
}, infos)
end)
end)
end)
describe(":init_connector()", function()
it("initializes infos", function()
local db, err = DB.new(helpers.test_conf, strategy)
assert.is_nil(err)
assert.is_table(db)
assert(db:init_connector())
local infos = db.infos
assert.matches("^%d+%.?%d*%.?%d*$", infos.db_ver)
assert.not_matches("%.$", infos.db_ver)
if strategy == "postgres" then
assert.same({
strategy = "PostgreSQL",
db_desc = "database",
db_name = helpers.test_conf.pg_database,
-- this depends on pg config, but for test-suite it is "public"
-- when not specified-
db_schema = helpers.test_conf.pg_schema or "public",
db_ver = infos.db_ver,
db_readonly = false,
}, infos)
else
error("unknown database")
end
end)
postgres_only("initializes infos with custom schema", function()
local conf = cycle_aware_deep_copy(helpers.test_conf)
conf.pg_schema = "demo"
local db, err = DB.new(conf, strategy)
assert.is_nil(err)
assert.is_table(db)
assert(db:init_connector())
local infos = db.infos
assert.matches("^%d+%.?%d*%.?%d*$", infos.db_ver)
assert.not_matches("%.$", infos.db_ver)
assert.same({
strategy = "PostgreSQL",
db_desc = "database",
db_name = conf.pg_database,
db_schema = conf.pg_schema,
db_ver = infos.db_ver,
db_readonly = false,
}, infos)
end)
if strategy ~= "off" then
it("calls :check_version_compat with constants", function()
local constants = require "kong.constants"
local versions = constants.DATABASE[strategy:upper()]
local db, _ = DB.new(helpers.test_conf, strategy)
local s = spy.on(db, "check_version_compat")
assert(db:init_connector())
-- called_with goes on forever when checking for self
assert.equal(versions.MIN, s.calls[1].refs[2])
assert.equal(versions.DEPRECATED, s.calls[1].refs[3])
end)
end
end)
describe(":connect()", function()
lazy_setup(function()
helpers.get_db_utils(strategy, {})
end)
postgres_only("connects to schema configured in postgres by default", function()
local db, err = DB.new(helpers.test_conf, strategy)
assert.is_nil(err)
assert.is_table(db)
assert(db:init_connector())
assert(db:connect())
local res = assert(db.connector:query("SELECT CURRENT_SCHEMA AS schema;"))
assert.is_table(res[1])
-- in test suite the CURRENT_SCHEMA is public
assert.equal("public", res[1]["schema"])
assert(db:close())
end)
postgres_only("connects to custom schema when configured", function()
local conf = cycle_aware_deep_copy(helpers.test_conf)
conf.pg_schema = "demo"
local db, err = DB.new(conf, strategy)
assert.is_nil(err)
assert.is_table(db)
assert(db:init_connector())
assert(db:connect())
assert(db:reset())
local res = assert(db.connector:query("SELECT CURRENT_SCHEMA AS schema;"))
assert.is_table(res[1])
assert.equal("demo", res[1]["schema"])
assert(db:close())
end)
postgres_only("connects with application_name = kong in postgres", function()
local db, err = DB.new(helpers.test_conf, strategy)
assert.is_nil(err)
assert.is_table(db)
assert(db:init_connector())
assert(db:connect())
local res = assert(db.connector:query("SELECT application_name from pg_stat_activity WHERE application_name = 'kong';"))
assert.is_table(res[1])
assert.equal("kong", res[1]["application_name"])
assert(db:close())
end)
it("returns opened connection when IS_CLI=false", function()
ngx.IS_CLI = false
local db, err = DB.new(helpers.test_conf, strategy)
assert.is_nil(err)
assert.is_table(db)
assert(db:init_connector())
local conn, err = db:connect()
assert.is_nil(err)
assert.is_table(conn)
if strategy == "postgres" then
assert.equal("nginx", db.connector:get_stored_connection().sock_type)
assert.is_false(db.connector:get_stored_connection().config.ssl)
end
db:close()
end)
it("returns opened connection when IS_CLI=true", function()
ngx.IS_CLI = true
local db, err = DB.new(helpers.test_conf, strategy)
assert.is_nil(err)
assert.is_table(db)
assert(db:init_connector())
local conn, err = db:connect()
assert.is_nil(err)
assert.is_table(conn)
if strategy == "postgres" then
assert.equal("nginx", db.connector:get_stored_connection().sock_type)
assert.is_false(db.connector:get_stored_connection().config.ssl)
end
db:close()
end)
postgres_only("returns opened connection with ssl (IS_CLI=false)", function()
ngx.IS_CLI = false
local conf = cycle_aware_deep_copy(helpers.test_conf)
conf.pg_ssl = true
local db, err = DB.new(conf, strategy)
assert.is_nil(err)
assert.is_table(db)
assert(db:init_connector())
local conn, err = db:connect()
assert.is_nil(err)
assert.is_table(conn)
if strategy == "postgres" then
assert.equal("nginx", db.connector:get_stored_connection().sock_type)
assert.is_true(db.connector:get_stored_connection().config.ssl)
end
db:close()
end)
postgres_only("returns opened connection with ssl (IS_CLI=true)", function()
ngx.IS_CLI = true
local conf = cycle_aware_deep_copy(helpers.test_conf)
conf.pg_ssl = true
local db, err = DB.new(conf, strategy)
assert.is_nil(err)
assert.is_table(db)
assert(db:init_connector())
local conn, err = db:connect()
assert.is_nil(err)
assert.is_table(conn)
if strategy == "postgres" then
assert.equal("nginx", db.connector:get_stored_connection().sock_type)
assert.is_true(db.connector:get_stored_connection().config.ssl)
end
db:close()
end)
postgres_only("connects to postgres with readonly account (IS_CLI=false)", function()
ngx.IS_CLI = false
local conf = cycle_aware_deep_copy(helpers.test_conf)
conf.pg_ro_host = conf.pg_host
local db, err = DB.new(conf, strategy)
assert.is_nil(err)
assert.is_table(db)
assert(db:init_connector())
local conn, err = db.connector:connect("read")
assert.is_nil(err)
assert.is_table(conn)
assert.is_nil(db.connector:get_stored_connection("write"))
assert.is_nil(db.connector:get_stored_connection()) -- empty defaults to "write"
assert.equal("nginx", db.connector:get_stored_connection("read").sock_type)
if strategy == "postgres" then
assert.is_false(db.connector:get_stored_connection("read").config.ssl)
end
db:close()
end)
postgres_only("connects to postgres with readonly account (IS_CLI=true)", function()
ngx.IS_CLI = true
local conf = cycle_aware_deep_copy(helpers.test_conf)
conf.pg_ro_host = conf.pg_host
local db, err = DB.new(conf, strategy)
assert.is_nil(err)
assert.is_table(db)
assert(db:init_connector())
local conn, err = db.connector:connect("read")
-- this gets overwritten to write instead
assert.is_nil(err)
assert.is_table(conn)
assert.is_nil(db.connector:get_stored_connection("read"))
assert.equal("nginx", db.connector:get_stored_connection("write").sock_type)
if strategy == "portgres" then
assert.is_false(db.connector:get_stored_connection("write").config.ssl)
end
assert.equal("nginx", db.connector:get_stored_connection().sock_type)
if strategy == "portgres" then
assert.is_false(db.connector:get_stored_connection("write").config.ssl)
end
db:close()
end)
end)
describe("#testme :query()", function()
lazy_setup(function()
helpers.get_db_utils(strategy, {})
end)
postgres_only("establish new connection when error occurred", function()
ngx.IS_CLI = false
local conf = cycle_aware_deep_copy(helpers.test_conf)
conf.pg_ro_host = conf.pg_host
conf.pg_ro_user = conf.pg_user
local db, err = DB.new(conf, strategy)
assert.is_nil(err)
assert.is_table(db)
assert(db:init_connector())
assert(db:connect())
local res, err = db.connector:query("SELECT now();")
assert.not_nil(res)
assert.is_nil(err)
local old_conn = db.connector:get_stored_connection("write")
assert.not_nil(old_conn)
local res, err = db.connector:query("SELECT * FROM not_exist_table;")
assert.is_nil(res)
assert.not_nil(err)
local new_conn = db.connector:get_stored_connection("write")
assert.is_nil(new_conn)
local res, err = db.connector:query("SELECT now();")
assert.not_nil(res)
assert.is_nil(err)
local res, err = db.connector:query("SELECT now();")
assert.not_nil(res)
assert.is_nil(err)
assert(db:close())
end)
end)
describe(":setkeepalive()", function()
lazy_setup(function()
helpers.get_db_utils(strategy, {})
end)
it("returns true when there is a stored connection (IS_CLI=false)", function()
ngx.IS_CLI = false
local db, err = DB.new(helpers.test_conf, strategy)
assert.is_nil(err)
assert.is_table(db)
assert(db:init_connector())
local conn, err = db:connect()
assert.is_nil(err)
assert.is_table(conn)
if strategy == "postgres" then
assert.equal("nginx", db.connector:get_stored_connection().sock_type)
assert.is_false(db.connector:get_stored_connection().config.ssl)
end
assert.is_true(db:setkeepalive())
db:close()
end)
it("returns true when there is a stored connection (IS_CLI=true)", function()
ngx.IS_CLI = true
local db, err = DB.new(helpers.test_conf, strategy)
assert.is_nil(err)
assert.is_table(db)
assert(db:init_connector())
local conn, err = db:connect()
assert.is_nil(err)
assert.is_table(conn)
if strategy == "postgres" then
assert.equal("nginx", db.connector:get_stored_connection().sock_type)
assert.is_false(db.connector:get_stored_connection().config.ssl)
end
assert.is_true(db:setkeepalive())
db:close()
end)
postgres_only("returns true when there is a stored connection with ssl (IS_CLI=false)", function()
ngx.IS_CLI = false
local conf = cycle_aware_deep_copy(helpers.test_conf)
conf.pg_ssl = true
local db, err = DB.new(conf, strategy)
assert.is_nil(err)
assert.is_table(db)
assert(db:init_connector())
local conn, err = db:connect()
assert.is_nil(err)
assert.is_table(conn)
if strategy == "postgres" then
assert.equal("nginx", db.connector:get_stored_connection().sock_type)
assert.is_true(db.connector:get_stored_connection().config.ssl)
end
assert.is_true(db:setkeepalive())
db:close()
end)
postgres_only("returns true when there is a stored connection with ssl (IS_CLI=true)", function()
ngx.IS_CLI = true
local conf = cycle_aware_deep_copy(helpers.test_conf)
conf.pg_ssl = true
local db, err = DB.new(conf, strategy)
assert.is_nil(err)
assert.is_table(db)
assert(db:init_connector())
local conn, err = db:connect()
assert.is_nil(err)
assert.is_table(conn)
if strategy == "postgres" then
assert.equal("nginx", db.connector:get_stored_connection().sock_type)
assert.is_true(db.connector:get_stored_connection().config.ssl)
end
assert.is_true(db:setkeepalive())
db:close()
end)
it("returns true when there is no stored connection (IS_CLI=false)", function()
ngx.IS_CLI = false
local db, err = DB.new(helpers.test_conf, strategy)
assert.is_nil(err)
assert.is_table(db)
assert(db:init_connector())
assert.is_nil(db.connector:get_stored_connection())
assert.is_true(db:setkeepalive())
end)
it("returns true when there is no stored connection (IS_CLI=true)", function()
ngx.IS_CLI = true
local db, err = DB.new(helpers.test_conf, strategy)
assert.is_nil(err)
assert.is_table(db)
assert(db:init_connector())
assert.is_nil(db.connector:get_stored_connection())
assert.is_true(db:setkeepalive())
end)
postgres_only("keepalives both read only and write connection (IS_CLI=false)", function()
ngx.IS_CLI = false
local conf = cycle_aware_deep_copy(helpers.test_conf)
conf.pg_ro_host = conf.pg_host
local db, err = DB.new(conf, strategy)
assert.is_nil(err)
assert.is_table(db)
assert(db:init_connector())
local conn, err = db.connector:connect("read")
assert.is_nil(err)
assert.is_table(conn)
conn, err = db.connector:connect("write")
assert.is_nil(err)
assert.is_table(conn)
assert.equal("nginx", db.connector:get_stored_connection("read").sock_type)
assert.equal("nginx", db.connector:get_stored_connection("write").sock_type)
if strategy == "postgres" then
assert.is_false(db.connector:get_stored_connection("read").config.ssl)
assert.is_false(db.connector:get_stored_connection("write").config.ssl)
end
assert.is_true(db:setkeepalive())
assert.is_nil(db.connector:get_stored_connection("read"))
assert.is_nil(db.connector:get_stored_connection("write"))
db:close()
end)
postgres_only("connects and keepalives only write connection (IS_CLI=true)", function()
ngx.IS_CLI = true
local conf = cycle_aware_deep_copy(helpers.test_conf)
conf.pg_ro_host = conf.pg_host
local db, err = DB.new(conf, strategy)
assert.is_nil(err)
assert.is_table(db)
assert(db:init_connector())
local conn, err = db.connector:connect("read")
-- this gets overwritten to "write" under the hood
assert.is_nil(err)
assert.is_table(conn)
conn, err = db.connector:connect("write")
assert.is_nil(err)
assert.is_table(conn)
assert.equal("nginx", db.connector:get_stored_connection("write").sock_type)
assert.is_nil(db.connector:get_stored_connection("read"))
if strategy == "postgres" then
assert.is_false(db.connector:get_stored_connection("write").config.ssl)
end
assert.is_true(db:setkeepalive())
assert.is_nil(db.connector:get_stored_connection("read"))
assert.is_nil(db.connector:get_stored_connection("write"))
db:close()
end)
end)
describe(":close()", function()
lazy_setup(function()
helpers.get_db_utils(strategy, {})
end)
it("returns true when there is a stored connection (IS_CLI=false)", function()
ngx.IS_CLI = false
local db, err = DB.new(helpers.test_conf, strategy)
assert.is_nil(err)
assert.is_table(db)
assert(db:init_connector())
local conn, err = db:connect()
assert.is_nil(err)
assert.is_table(conn)
if strategy == "postgres" then
assert.equal("nginx", db.connector:get_stored_connection().sock_type)
assert.is_false(db.connector:get_stored_connection().config.ssl)
end
assert.is_true(db:close())
end)
it("returns true when there is a stored connection (IS_CLI=true)", function()
ngx.IS_CLI = true
local db, err = DB.new(helpers.test_conf, strategy)
assert.is_nil(err)
assert.is_table(db)
assert(db:init_connector())
local conn, err = db:connect()
assert.is_nil(err)
assert.is_table(conn)
if strategy == "postgres" then
assert.equal("nginx", db.connector:get_stored_connection().sock_type)
assert.is_false(db.connector:get_stored_connection().config.ssl)
end
assert.is_true(db:close())
end)
postgres_only("returns true when there is a stored connection with ssl (IS_CLI=false)", function()
ngx.IS_CLI = false
local conf = cycle_aware_deep_copy(helpers.test_conf)
conf.pg_ssl = true
local db, err = DB.new(conf, strategy)
assert.is_nil(err)
assert.is_table(db)
assert(db:init_connector())
local conn, err = db:connect()
assert.is_nil(err)
assert.is_table(conn)
if strategy == "postgres" then
assert.equal("nginx", db.connector:get_stored_connection().sock_type)
assert.is_true(db.connector:get_stored_connection().config.ssl)
end
assert.is_true(db:close())
end)
postgres_only("returns true when there is a stored connection with ssl (IS_CLI=true)", function()
ngx.IS_CLI = true
local conf = cycle_aware_deep_copy(helpers.test_conf)
conf.pg_ssl = true
local db, err = DB.new(conf, strategy)
assert.is_nil(err)
assert.is_table(db)
assert(db:init_connector())
local conn, err = db:connect()
assert.is_nil(err)
assert.is_table(conn)
if strategy == "postgres" then
assert.equal("nginx", db.connector:get_stored_connection().sock_type)
assert.is_true(db.connector:get_stored_connection().config.ssl)
end
assert.is_true(db:close())
end)
it("returns true when there is no stored connection (IS_CLI=false)", function()
ngx.IS_CLI = false
local db, err = DB.new(helpers.test_conf, strategy)
assert.is_nil(err)
assert.is_table(db)
assert(db:init_connector())
assert.is_nil(db.connector:get_stored_connection())
assert.is_true(db:close())
end)
it("returns true when there is no stored connection (IS_CLI=true)", function()
ngx.IS_CLI = true
local db, err = DB.new(helpers.test_conf, strategy)
assert.is_nil(err)
assert.is_table(db)
assert(db:init_connector())
assert.is_nil(db.connector:get_stored_connection())
assert.equal(true, db:close())
end)
postgres_only("returns true when both read-only and write connection exists (IS_CLI=false)", function()
ngx.IS_CLI = false
local conf = cycle_aware_deep_copy(helpers.test_conf)
conf.pg_ro_host = conf.pg_host
local db, err = DB.new(conf, strategy)
assert.is_nil(err)
assert.is_table(db)
assert(db:init_connector())
local conn, err = db.connector:connect("read")
assert.is_nil(err)
assert.is_table(conn)
conn, err = db.connector:connect("write")
assert.is_nil(err)
assert.is_table(conn)
assert.equal("nginx", db.connector:get_stored_connection("read").sock_type)
assert.equal("nginx", db.connector:get_stored_connection("write").sock_type)
if strategy == "postgres" then
assert.is_false(db.connector:get_stored_connection("read").config.ssl)
assert.is_false(db.connector:get_stored_connection("write").config.ssl)
end
assert.is_true(db:close())
assert.is_nil(db.connector:get_stored_connection("read"))
assert.is_nil(db.connector:get_stored_connection("write"))
db:close()
end)
postgres_only("returns true when both read-only and write connection exists (IS_CLI=true)", function()
ngx.IS_CLI = true
local conf = cycle_aware_deep_copy(helpers.test_conf)
conf.pg_ro_host = conf.pg_host
local db, err = DB.new(conf, strategy)
assert.is_nil(err)
assert.is_table(db)
assert(db:init_connector())
local conn, err = db.connector:connect("read")
assert.is_nil(err)
assert.is_table(conn)
conn, err = db.connector:connect("write")
assert.is_nil(err)
assert.is_table(conn)
assert.equal("nginx", db.connector:get_stored_connection("write").sock_type)
assert.is_nil(db.connector:get_stored_connection("read"))
if strategy == "postgres" then
assert.is_false(db.connector:get_stored_connection("write").config.ssl)
end
assert.is_true(db:close())
assert.is_nil(db.connector:get_stored_connection("read"))
assert.is_nil(db.connector:get_stored_connection("write"))
db:close()
end)
end)
end)
end