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