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

377 lines
10 KiB
Lua

local helpers = require "spec.helpers"
local cjson = require "cjson"
local function client_send(req)
local client = helpers.http_client("127.0.0.1", 9500, 20000)
local res = assert(client:send(req))
local status, body = res.status, res:read_body()
client:close()
return status, body
end
local strategy = "off"
describe("Status API #" .. strategy, function()
local bp
local client
local apis, healthcheck_apis = {}, {}
local upstream, empty_upstream, healthcheck_upstream
lazy_setup(function()
local fixtures = {
dns_mock = helpers.dns_mock.new()
}
fixtures.dns_mock:A {
name = "custom_localhost",
address = "127.0.0.1",
}
bp = helpers.get_db_utils(strategy, {
"upstreams",
"targets",
})
upstream = bp.upstreams:insert {}
for i=1, 4 do
apis[i] = bp.targets:insert {
target = string.format("api-%d:80", i),
weight = 10 * i,
upstream = { id = upstream.id },
}
end
healthcheck_upstream = bp.upstreams:insert {
healthchecks = {
passive = {
healthy = {
successes = 1,
},
unhealthy = {
tcp_failures = 1,
http_failures = 1,
timeouts = 1,
},
}
}
}
for i=1, 4 do
healthcheck_apis[i] = bp.targets:insert {
target = string.format("hc-api-%d:80", i),
weight = 10 * i,
upstream = { id = healthcheck_upstream.id },
}
end
empty_upstream = bp.upstreams:insert {}
assert(helpers.start_kong({
status_listen = "127.0.0.1:9500",
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.http_client("127.0.0.1", 9500, 20000))
end)
after_each(function()
if client then client:close() end
end)
describe("/upstreams/{upstream}/targets/", function()
describe("POST", function()
it("is not exposed", function()
local res = assert(client:post(
"/upstreams/" .. upstream.name .. "/targets/", {
body = {
target = "mashape.com",
},
headers = {["Content-Type"] = "application/json"}
}))
assert.response(res).has.status(405)
end)
end)
describe("GET", function()
it("shows all targets", function()
for _, append in ipairs({ "", "/" }) do
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 three active targets for this upstream
assert.equal(4, #json.data)
local apis_targets = {}
for _, t in ipairs(apis) do
apis_targets[t.id] = t.target
end
local data_targets = {}
for _, t in ipairs(json.data) do
data_targets[t.id] = t.target
end
assert.same(apis_targets, data_targets)
end
end)
describe("empty results", function()
it("data property is an empty array", function()
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()
-- 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, upstream, 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.equal(n, #res.data)
local apis_targets = {}
for _, t in ipairs(targets) do
apis_targets[t.id] = t.target
end
local data_targets = {}
for _, t in ipairs(res.data) do
data_targets[t.id] = t.target
end
assert.same(apis_targets, data_targets)
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
describe("with healthchecks off", function()
it("returns HEALTHCHECKS_OFF for targets that resolve", function()
check_health_endpoint(apis, upstream, 4, "HEALTHCHECKS_OFF")
end)
end)
describe("with healthchecks on", function()
it("returns DNS_ERROR if DNS cannot be resolved", function()
check_health_endpoint(healthcheck_apis, healthcheck_upstream, 4, "DNS_ERROR")
end)
pending("returns HEALTHY if failure not detected", function()
check_health_endpoint(healthcheck_apis, healthcheck_upstream, 4, "HEALTHY")
end)
end)
end)
end)
describe("/upstreams/{upstream}/targets/all/", function()
describe("GET", function()
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(4, #json.data)
end)
it("offset is a string", function()
local res = assert(client:send {
method = "GET",
path = "/upstreams/" .. upstream.name .. "/targets",
query = {size = 2},
})
assert.response(res).has.status(200)
local json = assert.response(res).has.jsonbody()
assert.is_string(json.offset)
end)
it("paginates a set", function()
local pages = {}
local offset
for i = 1, 3 do
local res = assert(client:send {
method = "GET",
path = "/upstreams/" .. upstream.name .. "/targets/all",
query = {size = 2, offset = offset}
})
assert.response(res).has.status(200)
local json = assert.response(res).has.jsonbody()
if i < 3 then
assert.equal(2, #json.data)
else
assert.equal(0, #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 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()
describe("POST", function()
it("is not exposed", function()
local status, body
status, body = assert(client_send {
method = "POST",
path = "/upstreams/" .. upstream.name .. "/targets/" .. apis[1].target .. "/unhealthy"
})
assert.same(404, status, body)
end)
end)
end)
describe("/upstreams/{upstream}/targets/{target}", function()
local target = apis[1]
describe("GET", function()
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
if json.upstream.id then
json.upstream = json.upstream.id
end
assert.same(target, json)
end)
end)
describe("PATCH", function()
it("is not exposed", function()
local res = client:patch("/upstreams/" .. upstream.name .. "/targets/" .. target.target, {
body = {
weight = 659,
},
headers = { ["Content-Type"] = "application/json" }
})
assert.response(res).has.status(405)
end)
end)
describe("PUT", function()
it("is not exposed", function()
local res = client:put("/upstreams/" .. upstream.name .. "/targets/" .. target.target, {
body = {
target = target.target
},
headers = { ["Content-Type"] = "application/json" }
})
assert.response(res).has.status(405)
end)
end)
describe("DELETE", function()
it("is not exposed", function()
local res = assert(client:send {
method = "DELETE",
path = "/upstreams/" .. upstream.name .. "/targets/" .. target.target
})
assert.response(res).has.status(405)
end)
end)
end)
end)