local helpers = require "spec.helpers" local cjson = require "cjson" local uuid = require "kong.tools.uuid" local tablex = require "pl.tablex" local function it_content_types(title, fn) local test_form_encoded = fn("application/x-www-form-urlencoded") local test_multipart = fn("multipart/form-data") local test_json = fn("application/json") it(title .. " with application/www-form-urlencoded", test_form_encoded) it(title .. " with multipart/form-data", test_multipart) it(title .. " with application/json", test_json) end local function client_send(req) local client = helpers.admin_client() local res = assert(client:send(req)) local status, body = res.status, res:read_body() client:close() return status, body end for _, strategy in helpers.each_strategy() do describe("Admin API #" .. strategy, function() local bp local client local weight_default, weight_min, weight_max = 100, 0, 65535 local default_port = 8000 lazy_setup(function() local fixtures = { dns_mock = helpers.dns_mock.new({ mocks_only = true, -- don't fallback to "real" DNS }) } fixtures.dns_mock:A { name = "custom_localhost", address = "127.0.0.1", } bp = helpers.get_db_utils(strategy, { "upstreams", "targets", }) assert(helpers.start_kong({ database = strategy, nginx_conf = "spec/fixtures/custom_nginx.template", }, nil, nil, fixtures)) end) lazy_teardown(function() assert(helpers.stop_kong()) end) before_each(function() client = assert(helpers.admin_client()) end) after_each(function() if client then client:close() end end) describe("/targets", function() it("returns a 404", function() local res = assert(client:send { method = "GET", path = "/targets" }) assert.response(res).has.status(404) local json = assert.response(res).has.jsonbody() assert.equal("Not found", json.message) end) end) describe("/upstreams/{upstream}/targets/", function() describe("POST", function() it_content_types("creates a target with defaults", function(content_type) return function() local upstream = bp.upstreams:insert { slots = 10 } local res = assert(client:post("/upstreams/" .. upstream.name .. "/targets/", { body = { target = "konghq.test", }, headers = {["Content-Type"] = content_type} })) assert.response(res).has.status(201) local json = assert.response(res).has.jsonbody() assert.equal("konghq.test:" .. default_port, json.target) assert.is_number(json.created_at) assert.is_string(json.id) assert.are.equal(weight_default, json.weight) end end) it_content_types("creates a target without defaults", function(content_type) return function() local upstream = bp.upstreams:insert { slots = 10 } local res = assert(client:post("/upstreams/" .. upstream.name .. "/targets/", { body = { target = "konghq.test:123", weight = 99, }, headers = {["Content-Type"] = content_type} })) assert.response(res).has.status(201) local json = assert.response(res).has.jsonbody() assert.equal("konghq.test:123", json.target) assert.is_number(json.created_at) assert.is_string(json.id) assert.are.equal(99, json.weight) end end) it_content_types("creates a target with weight = 0", function(content_type) return function() local upstream = bp.upstreams:insert { slots = 10 } local res = assert(client:post("/upstreams/" .. upstream.name .. "/targets/", { body = { target = "zero.weight.test:8080", weight = 0, }, headers = {["Content-Type"] = content_type} })) assert.response(res).has.status(201) local json = assert.response(res).has.jsonbody() assert.equal("zero.weight.test:8080", json.target) assert.is_number(json.created_at) assert.is_string(json.id) assert.are.equal(0, json.weight) -- added for testing #7699 local res2 = assert(client:get("/upstreams/" .. upstream.name .. "/targets/zero.weight.test:8080")) assert.response(res2).has.status(200) local json2 = assert.response(res2).has.jsonbody() assert.same(json, json2) end end) describe("errors", function() it("handles malformed JSON body", function() local upstream = bp.upstreams:insert { slots = 10 } local res = assert(client:post("/upstreams/" .. upstream.name .. "/targets/", { body = '{"hello": "world"', headers = {["Content-Type"] = "application/json"} })) local body = assert.response(res).has.status(400) local json = cjson.decode(body) assert.same({ message = "Cannot parse JSON body" }, json) end) it_content_types("handles invalid input", function(content_type) return function() local upstream = bp.upstreams:insert { slots = 10 } -- Missing parameter local res = assert(client:post("/upstreams/" .. upstream.name .. "/targets/", { body = { weight = weight_min, }, headers = {["Content-Type"] = content_type} })) local body = assert.response(res).has.status(400) local json = cjson.decode(body) assert.equal("schema violation", json.name) assert.same({ target = "required field missing" }, json.fields) -- Invalid target parameter res = assert(client:post("/upstreams/" .. upstream.name .. "/targets/", { body = { target = "some invalid host name", }, headers = {["Content-Type"] = content_type} })) body = assert.response(res).has.status(400) local json = cjson.decode(body) assert.equal("schema violation", json.name) assert.same({ target = "Invalid target; not a valid hostname or ip address" }, json.fields) -- Invalid weight parameter res = assert(client:send { method = "POST", path = "/upstreams/" .. upstream.name .. "/targets/", body = { target = "konghq.test", weight = weight_max + 1, }, headers = {["Content-Type"] = content_type} }) body = assert.response(res).has.status(400) local json = cjson.decode(body) assert.equal("schema violation", json.name) assert.same({ weight = "value should be between 0 and " .. weight_max }, json.fields) end end) for _, method in ipairs({"PUT", "PATCH", "DELETE"}) do it_content_types("returns 405 on " .. method, function(content_type) return function() local upstream = bp.upstreams:insert { slots = 10 } local res = assert(client:send { method = method, path = "/upstreams/" .. upstream.name .. "/targets/", body = { target = "konghq.test", }, headers = {["Content-Type"] = content_type} }) assert.response(res).has.status(405) end end) end it_content_types("fails to create duplicated targets", function(content_type) return function() local upstream = bp.upstreams:insert { slots = 10 } local res = assert(client:post("/upstreams/" .. upstream.name .. "/targets/", { body = { target = "single-target.test:8080", weight = 1, }, headers = {["Content-Type"] = content_type} })) assert.response(res).has.status(201) local json = assert.response(res).has.jsonbody() assert.equal("single-target.test:8080", json.target) assert.is_number(json.created_at) assert.is_string(json.id) assert.are.equal(1, json.weight) local res = assert(client:post("/upstreams/" .. upstream.name .. "/targets/", { body = { target = "single-target.test:8080", weight = 100, }, headers = {["Content-Type"] = content_type} })) assert.response(res).has.status(409) end end) end) end) describe("GET", function() local apis = {} local api_map local upstream local function target_list_to_map(list) local map = {} for _, t in ipairs(list) do map[t.target] = t if t.tags == ngx.null then t.tags = nil end end return map end before_each(function() upstream = bp.upstreams:insert {} apis[1] = bp.targets:insert { target = "api-1:80", weight = 10, upstream = { id = upstream.id }, } apis[2] = bp.targets:insert { target = "api-2:80", weight = 0, upstream = { id = upstream.id }, } apis[3] = bp.targets:insert { target = "api-3:80", weight = 50, upstream = { id = upstream.id }, } apis[4] = bp.targets:insert { target = "api-4:80", weight = 10, upstream = { id = upstream.id }, } api_map = target_list_to_map(apis) end) for _, append in ipairs({ "", "/" }) do it("shows all targets with " .. (append == "" and "no" or "") .. " ending slash", function() local res = assert(client:send { method = "GET", path = "/upstreams/" .. upstream.name .. "/targets" .. append, }) assert.response(res).has.status(200) local json = assert.response(res).has.jsonbody() -- we got four active targets for this upstream assert.equal(4, #json.data) assert.same(api_map, target_list_to_map(json.data)) end) end describe("empty results", function() it("data property is an empty array", function() local empty_upstream = bp.upstreams:insert {} local res = assert(client:send { method = "GET", path = "/upstreams/" .. empty_upstream.name .. "/targets", }) local body = assert.response(res).has.status(200) assert.match('"data":%[%]', body) end) end) end) end) describe("/upstreams/{upstream}/health/", function() describe("GET", function() local upstream local node_id local function add_targets(target_fmt) local targets = {} local weights = { 10, 10, 10, 10 } for i = 1, #weights do local status, body = client_send({ method = "POST", path = "/upstreams/" .. upstream.name .. "/targets", headers = { ["Content-Type"] = "application/json", }, body = { target = string.format(target_fmt, i), weight = weights[i], } }) assert.same(201, status) targets[i] = assert(cjson.decode(body)) end return targets end -- Performs tests similar to /upstreams/:upstream_id/targets, -- and checks for the "health" field of each target. -- @param targets the array of target data produced by add_targets -- @param n the expected number of targets in the response -- It is different from #targets because add_targets adds -- zero-weight targets as well. -- @param health the expected "health" response for all targets local function check_health_endpoint(targets, n, health) for _, append in ipairs({ "", "/" }) do local status, body = client_send({ method = "GET", path = "/upstreams/" .. upstream.name .. "/health" .. append, }) assert.same(200, status) local res = assert(cjson.decode(body)) assert.same(node_id, res.node_id) assert.equal(n, #res.data) -- when multiple active targets are present, we only see the last one assert.equal(targets[4].id, res.data[1].id) -- validate the remaining returned targets -- note the backwards order, because we walked the targets backwards assert.equal(targets[3].target, res.data[2].target) assert.equal(targets[2].target, res.data[3].target) for i = 1, n do if res.data[i].data ~= nil and res.data[i].data.addresses ~= nil then for j = 1, #res.data[i].data.addresses do assert.equal(health, res.data[i].data.addresses[j].health) end assert.equal(health, res.data[i].health) end end end end lazy_setup(function() local status, body = client_send({ method = "GET", path = "/", }) assert.same(200, status) local res = assert(cjson.decode(body)) assert.string(res.node_id) node_id = res.node_id end) before_each(function() local any_upstream = bp.upstreams:insert {} local status, body = client_send({ method = "POST", path = "/upstreams", headers = { ["Content-Type"] = "application/json", }, body = { name = any_upstream.name .. "-health", } }) assert.same(201, status) upstream = assert(cjson.decode(body)) end) describe("with healthchecks off", function() it("returns HEALTHCHECKS_OFF for targets that resolve", function() add_targets("127.0.0.1:8%d") local targets = add_targets("custom_localhost:8%d") check_health_endpoint(targets, 8, "HEALTHCHECKS_OFF") end) it("returns DNS_ERROR if DNS cannot be resolved", function() local targets = add_targets("bad-health-target-%d:80") check_health_endpoint(targets, 4, "DNS_ERROR") end) end) describe("with healthchecks on", function() local checked before_each(function() if checked == nil then ngx.sleep(1) end local status, body = client_send({ method = "PATCH", path = "/upstreams/" .. upstream.name, headers = { ["Content-Type"] = "application/json", }, body = { healthchecks = { passive = { healthy = { successes = 1, }, unhealthy = { tcp_failures = 1, http_failures = 1, timeouts = 1, }, } } } }) assert.same(200, status) if checked == nil then local json = assert(cjson.decode(body)) assert.truthy(upstream.updated_at < json.updated_at) checked = true end end) it("returns DNS_ERROR if DNS cannot be resolved", function() local targets = add_targets("bad-target-%d:80") check_health_endpoint(targets, 4, "DNS_ERROR") end) it("returns HEALTHY if failure not detected", function() local targets = add_targets("custom_localhost:222%d") check_health_endpoint(targets, 4, "HEALTHY") end) it("returns UNHEALTHY if failure detected", function() local targets = add_targets("custom_localhost:222%d") local status = client_send({ method = "PUT", path = "/upstreams/" .. upstream.name, headers = { ["Content-Type"] = "application/json", }, body = { healthchecks = { active = { healthy = { interval = 1, }, unhealthy = { interval = 1, tcp_failures = 1, }, } } } }) assert.same(200, status) helpers.pwait_until(function() check_health_endpoint(targets, 4, "UNHEALTHY") end, 15) end) it("returns HEALTHCHECKS_OFF for target with weight 0", function () local status, _ = client_send({ method = "POST", path = "/upstreams/" .. upstream.name .. "/targets", headers = { ["Content-Type"] = "application/json", }, body = { target = "custom_localhost:2221", weight = 0, } }) assert.same(201, status) helpers.pwait_until(function () local status, body = client_send({ method = "GET", path = "/upstreams/" .. upstream.name .. "/health", }) assert.same(200, status) local res = assert(cjson.decode(body)) local function check_health_addresses(addresses, health) for i=1, #addresses do assert.same(health, addresses[i].health) end end assert.equal(1, #res.data) check_health_addresses(res.data[1].data.addresses, "HEALTHCHECKS_OFF") end, 15) end) local function check_health_addresses(addresses, health) for i=1, #addresses do assert.same(health, addresses[i].health) end end it("returns HEALTHCHECKS_OFF for target with weight 0", function () local status, _ = client_send({ method = "POST", path = "/upstreams/" .. upstream.name .. "/targets", headers = { ["Content-Type"] = "application/json", }, body = { target = "custom_localhost:2221", weight = 0, } }) assert.same(201, status) -- Give time for weight modification to kick in ngx.sleep(0.3) local status, body = client_send({ method = "GET", path = "/upstreams/" .. upstream.name .. "/health", }) assert.same(200, status) local res = assert(cjson.decode(body)) assert.equal(1, #res.data) check_health_addresses(res.data[1].data.addresses, "HEALTHCHECKS_OFF") end) it("return HEALTHY if weight of target turns from zero to non-zero", function () local status, _ = client_send({ method = "POST", path = "/upstreams/" .. upstream.name .. "/targets", headers = { ["Content-Type"] = "application/json", }, body = { target = "custom_localhost:2221", weight = 0, } }) assert.same(201, status) -- Give time for weight modification to kick in ngx.sleep(0.3) local status, body = client_send({ method = "GET", path = "/upstreams/" .. upstream.name .. "/health", }) assert.same(200, status) local res = assert(cjson.decode(body)) assert.equal(1, #res.data) check_health_addresses(res.data[1].data.addresses, "HEALTHCHECKS_OFF") local status = client_send({ method = "PATCH", path = "/upstreams/" .. upstream.name .. "/targets/custom_localhost:2221", headers = { ["Content-Type"] = "application/json", }, body = { -- target = "custom_localhost:2221", weight = 10, }, }) assert.same(200, status) -- Give time for weight modification to kick in ngx.sleep(0.3) local status, body = client_send({ method = "GET", path = "/upstreams/" .. upstream.name .. "/health", }) assert.same(200, status) local res = assert(cjson.decode(body)) assert.equal(1, #res.data) check_health_addresses(res.data[1].data.addresses, "HEALTHY") end) end) end) end) describe("/upstreams/{upstream}/targets/all/", function() describe("GET", function() local upstream before_each(function() upstream = bp.upstreams:insert {} for i = 1, 10 do bp.targets:insert { target = "api-" .. i .. ":80", weight = 100, upstream = { id = upstream.id }, } end end) it("retrieves the first page", function() local res = assert(client:send { method = "GET", path = "/upstreams/" .. upstream.name .. "/targets/all", }) assert.response(res).has.status(200) local json = assert.response(res).has.jsonbody() assert.equal(10, #json.data) end) it("offset is a string", function() local res = assert(client:send { method = "GET", path = "/upstreams/" .. upstream.name .. "/targets/all", query = {size = 3}, }) assert.response(res).has.status(200) local json = assert.response(res).has.jsonbody() assert.is_string(json.offset) end) it("next url ends with /targets/all", function() local res = assert(client:send { method = "GET", path = "/upstreams/" .. upstream.name .. "/targets/all", query = {size = 3}, }) assert.response(res).has.status(200) local json = assert.response(res).has.jsonbody() assert.equals("/upstreams/" .. upstream.name .. "/targets/all?offset=" .. ngx.escape_uri(json.offset), json.next) end) it("paginates a set", function() local pages = {} local offset for i = 1, 4 do local res = assert(client:send { method = "GET", path = "/upstreams/" .. upstream.name .. "/targets/all", query = {size = 3, offset = offset} }) assert.response(res).has.status(200) local json = assert.response(res).has.jsonbody() if i < 4 then assert.equal(3, #json.data) else assert.equal(1, #json.data) end if i > 1 then -- check all pages are different assert.not_same(pages[i-1], json) end offset = json.offset pages[i] = json end end) it("ignores filters", function() local res = assert(client:send { method = "GET", path = "/upstreams/" .. upstream.name .. "/targets/all", query = {foo = "bar"}, }) assert.response(res).has.status(200) end) it("ignores an invalid body", function() local res = assert(client:send { method = "GET", path = "/upstreams/" .. upstream.name .. "/targets/all", body = "this fails if decoded as json", headers = { ["Content-Type"] = "application/json", } }) assert.response(res).has.status(200) end) describe("empty results", function() it("data property is an empty array", function() local empty_upstream = bp.upstreams:insert {} local res = assert(client:send { method = "GET", path = "/upstreams/" .. empty_upstream.name .. "/targets/all", }) local body = assert.response(res).has.status(200) local json = cjson.decode(body) assert.same({ data = {}, next = ngx.null, }, json) -- ensure JSON representation is correct assert.match('"data":%[%]', body) end) end) end) end) describe("/upstreams/{upstream}/targets/{target}/(un)healthy", function() local localhosts = { ipv4 = "127.0.0.1", ipv6 = "[0000:0000:0000:0000:0000:0000:0000:0001]", hostname = "localhost", } for mode, localhost in pairs(localhosts) do describe("POST #" .. mode, function() local upstream local target_path local target local wrong_target lazy_setup(function() local my_target_name = localhost .. ":8192" wrong_target = bp.targets:insert { target = my_target_name, weight = 10 } upstream = bp.upstreams:insert {} local status, body = assert(client_send({ method = "PATCH", path = "/upstreams/" .. upstream.id, headers = {["Content-Type"] = "application/json"}, body = { healthchecks = { passive = { healthy = { successes = 1, }, unhealthy = { tcp_failures = 1, http_failures = 1, timeouts = 1, }, } } } })) assert.same(200, status, body) local json = assert(cjson.decode(body)) status, body = assert(client_send({ method = "POST", path = "/upstreams/" .. upstream.id .. "/targets", headers = {["Content-Type"] = "application/json"}, body = { target = my_target_name, weight = 10, upstream = { id = json.id }, } })) assert.same(201, status) target = assert(cjson.decode(body)) assert.same(my_target_name, target.target) target_path = "/upstreams/" .. upstream.id .. "/targets/" .. target.target end) it("checks every combination of valid and invalid upstream and target", function() for i, u in ipairs({ uuid.uuid(), "invalid", upstream.name, upstream.id }) do for j, t in ipairs({ uuid.uuid(), "invalid:1234", wrong_target.id, target.target, target.id }) do for _, e in ipairs({ "healthy", "unhealthy" }) do local expected = (i >= 3 and j >= 4) and 204 or 404 local path = "/upstreams/" .. u .. "/targets/" .. t .. "/" .. e local status = assert(client_send { method = "PUT", path = "/upstreams/" .. u .. "/targets/" .. t .. "/" .. e }) assert.same(expected, status, "bad status for path " .. path) end end end end) it("flips the target status from UNHEALTHY to HEALTHY", function() local status, body, json status, body = assert(client_send { method = "PUT", path = target_path .. "/unhealthy" }) assert.same(204, status, body) helpers.pwait_until(function() status, body = assert(client_send { method = "GET", path = "/upstreams/" .. upstream.id .. "/health" }) assert.same(200, status) json = assert(cjson.decode(body)) assert.same(target.target, json.data[1].target) assert.same("UNHEALTHY", json.data[1].health) end, 15) status = assert(client_send { method = "PUT", path = target_path .. "/healthy" }) assert.same(204, status) helpers.pwait_until(function() status, body = assert(client_send { method = "GET", path = "/upstreams/" .. upstream.id .. "/health" }) assert.same(200, status) json = assert(cjson.decode(body)) assert.same(target.target, json.data[1].target) assert.same("HEALTHY", json.data[1].health) end, 15) end) it("flips the target status from HEALTHY to UNHEALTHY", function() local status, body, json status = assert(client_send { method = "PUT", path = target_path .. "/healthy" }) assert.same(204, status) helpers.pwait_until(function () status, body = assert(client_send { method = "GET", path = "/upstreams/" .. upstream.id .. "/health" }) assert.same(200, status) json = assert(cjson.decode(body)) assert.same(target.target, json.data[1].target) assert.same("HEALTHY", json.data[1].health) end, 15) status = assert(client_send { method = "PUT", path = target_path .. "/unhealthy" }) assert.same(204, status) helpers.pwait_until(function () status, body = assert(client_send { method = "GET", path = "/upstreams/" .. upstream.id .. "/health" }) assert.same(200, status) json = assert(cjson.decode(body)) assert.same(target.target, json.data[1].target) assert.same("UNHEALTHY", json.data[1].health) end, 15) end) end) end end) describe("/upstreams/{upstream}/targets/{target}", function() describe("GET", function() local target local upstream before_each(function() upstream = bp.upstreams:insert {} bp.targets:insert { target = "api-1:80", weight = 10, upstream = { id = upstream.id }, } target = bp.targets:insert { target = "api-2:80", weight = 10, upstream = { id = upstream.id }, } end) it("returns target entity", function() local res = client:get("/upstreams/" .. upstream.name .. "/targets/" .. target.target) assert.response(res).has.status(200) local json = assert.response(res).has.jsonbody() json.tags = nil json.updated_at = nil target.updated_at = nil assert.same(target, json) end) end) describe("PATCH", function() local target local upstream before_each(function() upstream = bp.upstreams:insert {} bp.targets:insert { target = "api-1:80", weight = 10, upstream = { id = upstream.id }, } -- predefine the target to mock delete target = bp.targets:insert { target = "api-2:80", weight = 10, upstream = { id = upstream.id }, } end) it("is allowed and works", function() local admin_client = assert(helpers.admin_client()) local res assert .with_timeout(10) .ignore_exceptions(true) .eventually(function() res = admin_client:patch("/upstreams/" .. upstream.name .. "/targets/" .. target.target, { body = { weight = 659, }, headers = { ["Content-Type"] = "application/json" } }) assert.response(res).has.status(200) end) .has_no_error() local json = assert.response(res).has.jsonbody() assert.is_string(json.id) assert.are.equal(target.target, json.target) assert.are.equal(659, json.weight) assert.truthy(target.updated_at < json.updated_at) admin_client:close() local res = assert(client:send { method = "GET", path = "/upstreams/" .. upstream.name .. "/targets/" .. target.target, }) assert.response(res).has.status(200) json = assert.response(res).has.jsonbody() assert.is_string(json.id) assert.are.equal(659, json.weight) end) end) describe("PUT", function() local target local upstream before_each(function() upstream = bp.upstreams:insert {} bp.targets:insert { target = "api-1:80", weight = 10, upstream = { id = upstream.id }, } target = bp.targets:insert { target = "api-2:80", weight = 10, upstream = { id = upstream.id }, } end) it("updates target (by id)", function() -- update the target port target.target = target.target .. "1" local res = client:put("/upstreams/" .. upstream.name .. "/targets/" .. target.id, { body = { target = target.target }, headers = { ["Content-Type"] = "application/json" } }) assert.response(res).has.status(200) res = client:get("/upstreams/" .. upstream.name .. "/targets/" .. target.id) assert.response(res).has.status(200) local json = assert.response(res).has.jsonbody() json.tags = nil json.updated_at = nil target.updated_at = nil assert.same(target, json) end) it("updates target (by target)", function() local res = client:put("/upstreams/" .. upstream.name .. "/targets/" .. target.target, { body = { -- update the target port target = target.target .. "1" }, headers = { ["Content-Type"] = "application/json" } }) assert.response(res).has.status(200) local tgt = assert.response(res).has.jsonbody() -- the previous one should not exist now res = client:get("/upstreams/" .. upstream.name .. "/targets/" .. target.target) assert.response(res).has.status(404) -- now check the updated one target.target = target.target .. "1" res = client:get("/upstreams/" .. upstream.name .. "/targets/" .. target.target) assert.response(res).has.status(200) local json = assert.response(res).has.jsonbody() assert.same(tgt, json) end) end) describe("DELETE", function() local target local upstream before_each(function() upstream = bp.upstreams:insert {} bp.targets:insert { target = "api-1:80", weight = 10, upstream = { id = upstream.id }, } -- predefine the target to mock delete target = bp.targets:insert { target = "api-2:80", weight = 10, upstream = { id = upstream.id }, } end) it("method DELETE actually deletes targets (by target)", function() local targets = assert(client:send { method = "GET", path = "/upstreams/" .. upstream.name .. "/targets/all", }) assert.response(targets).has.status(200) local json = assert.response(targets).has.jsonbody() assert.equal(2, #json.data) local res = assert(client:send { method = "DELETE", path = "/upstreams/" .. upstream.name .. "/targets/" .. target.target }) assert.response(res).has.status(204) local targets = assert(client:send { method = "GET", path = "/upstreams/" .. upstream.name .. "/targets/all", }) assert.response(targets).has.status(200) local json = assert.response(targets).has.jsonbody() assert.equal(1, #json.data) local active = assert(client:send { method = "GET", path = "/upstreams/" .. upstream.name .. "/targets", }) assert.response(active).has.status(200) json = assert.response(active).has.jsonbody() assert.equal(1, #json.data) assert.equal("api-1:80", json.data[1].target) end) it("method DELETE actually deletes targets (by id)", function() local targets = assert(client:send { method = "GET", path = "/upstreams/" .. upstream.name .. "/targets/all", }) assert.response(targets).has.status(200) local json = assert.response(targets).has.jsonbody() assert.equal(2, #json.data) local res = assert(client:send { method = "DELETE", path = "/upstreams/" .. upstream.name .. "/targets/" .. target.id }) assert.response(res).has.status(204) local targets = assert(client:send { method = "GET", path = "/upstreams/" .. upstream.name .. "/targets/all", }) assert.response(targets).has.status(200) local json = assert.response(targets).has.jsonbody() assert.equal(1, #json.data) local active = assert(client:send { method = "GET", path = "/upstreams/" .. upstream.name .. "/targets", }) assert.response(active).has.status(200) json = assert.response(active).has.jsonbody() assert.equal(1, #json.data) assert.equal("api-1:80", json.data[1].target) end) end) end) end) describe("/upstreams/{upstream}/targets/{target}/(un)healthy not available in hybrid mode", function() lazy_setup(function() assert(helpers.start_kong({ role = "control_plane", cluster_cert = "spec/fixtures/kong_clustering.crt", cluster_cert_key = "spec/fixtures/kong_clustering.key", database = strategy, })) end) lazy_teardown(function() assert(helpers.stop_kong()) end) it("healthcheck endpoints not included in /endpoints", function() local admin_client = assert(helpers.admin_client()) local res = admin_client:get("/endpoints") local body = assert.res_status(200, res) local json = cjson.decode(body) assert.is_nil(tablex.find(json.data, '/upstreams/{upstreams}/targets/{targets}/healthy')) assert.is_nil(tablex.find(json.data, '/upstreams/{upstreams}/targets/{targets}/unhealthy')) assert.is_nil(tablex.find(json.data, '/upstreams/{upstreams}/targets/{targets}/{address}/healthy')) assert.is_nil(tablex.find(json.data, '/upstreams/{upstreams}/targets/{targets}/{address}/unhealthy')) end) end) end