From e9b7cfae3c90a1887060bbd898ecde09cf85661c Mon Sep 17 00:00:00 2001 From: Thibault Charbonnier Date: Wed, 20 May 2015 00:09:34 +0200 Subject: [PATCH 1/4] [wip] refactor(api) --- kong/api/app.lua | 51 +++----- kong/api/routes/apis.lua | 77 +++++++++++- kong/api/routes/base_controller.lua | 189 +++++++++++----------------- kong/api/routes/consumers.lua | 2 - kong/dao/cassandra/base_dao.lua | 11 +- kong/dao/schemas_validation.lua | 6 + kong/plugins/keyauth/api.lua | 21 +++- 7 files changed, 185 insertions(+), 172 deletions(-) diff --git a/kong/api/app.lua b/kong/api/app.lua index 4183eb766400..9a408007310e 100644 --- a/kong/api/app.lua +++ b/kong/api/app.lua @@ -1,40 +1,14 @@ local lapis = require "lapis" -local utils = require "kong.tools.utils" local responses = require "kong.tools.responses" local constants = require "kong.constants" local Apis = require "kong.api.routes.apis" -local Consumers = require "kong.api.routes.consumers" -local PluginsConfigurations = require "kong.api.routes.plugins_configurations" - -app = lapis.Application() - --- Huge hack to support PATCH methods. --- This is a copy/pasted and adapted method from Lapis application.lua --- It registers a method on `app.patch` listening for PATCH requests. -function app:patch(route_name, path, handler) - local lapis_application = require "lapis.application" - if handler == nil then - handler = path - path = route_name - route_name = nil - end - self.responders = self.responders or {} - local existing = self.responders[route_name or path] - local tbl = { ["PATCH"] = handler } - if existing then - setmetatable(tbl, { - __index = function(self, key) - if key:match("%u") then - return existing - end - end - }) - end - local responder = lapis_application.respond_to(tbl) - self.responders[route_name or path] = responder - return self:match(route_name, path, responder) -end +--local Consumers = require "kong.api.routes.consumers" +--local PluginsConfigurations = require "kong.api.routes.plugins_configurations" + +local app = lapis.Application() + +local BaseController = require "kong.api.routes.base_controller" local function get_hostname() local f = io.popen ("/bin/hostname") @@ -97,12 +71,15 @@ app.handle_error = function(self, err, trace) end -- Load controllers -Apis() -Consumers() -PluginsConfigurations() +--Apis(app, dao) +--Consumers() +--PluginsConfigurations() + +local kong_app = BaseController(app, dao) +kong_app:attach(Apis) -- Loading plugins routes -if configuration and configuration.plugins_available then +--[[if configuration and configuration.plugins_available then for _, v in ipairs(configuration.plugins_available) do local loaded, mod = utils.load_module_if_exists("kong.plugins."..v..".api") if loaded then @@ -112,6 +89,6 @@ if configuration and configuration.plugins_available then ngx.log(ngx.DEBUG, "No API endpoints loaded for plugin: "..v) end end -end +end]] return app diff --git a/kong/api/routes/apis.lua b/kong/api/routes/apis.lua index d7d76c0aa3ef..c897a67150f9 100644 --- a/kong/api/routes/apis.lua +++ b/kong/api/routes/apis.lua @@ -1,9 +1,74 @@ -local BaseController = require "kong.api.routes.base_controller" +local validations = require("kong.dao.schemas") -local Apis = BaseController:extend() +return { + ["/apis/"] = { + GET = function(self, dao_factory, helpers) + helpers.return_paginated_set(self, dao_factory.apis) + end, -function Apis:new() - Apis.super.new(self, dao.apis, "apis") -end + PUT = function(self, dao_factory, helpers) + local new_api, err + if self.params.id then + new_api, err = dao_factory.apis:update(self.params) + if not err then + return helpers.responses.send_HTTP_OK(new_api) + end + else + new_api, err = dao_factory.apis:insert(self.params) + if not err then + return helpers.responses.send_HTTP_CREATED(new_api) + end + end + + if err then + return helpers.yield_error(err) + end + end, + + POST = function(self, dao_factory, helpers) + local data, err = dao_factory.apis:insert(self.params) + if err then + return helpers.yield_error(err) + else + return helpers.responses.send_HTTP_CREATED(data) + end + end + }, + + ["/apis/:name_or_id"] = { + before = function(self, dao_factory, helpers) + local fetch_keys = { + [validations.is_valid_uuid(self.params.name_or_id) and "id" or "name"] = self.params.name_or_id + } + self.params.name_or_id = nil + + -- TODO: make the base_dao more flexible so we can query find_one with key/values + -- https://github.com/Mashape/kong/issues/103 + local data, err = dao_factory.apis:find_by_keys(fetch_keys) + if err then + return helpers.yield_error(err) + end + + self.api = data[1] + if not self.api then + return helpers.responses.send_HTTP_NOT_FOUND() + end + end, + + GET = function(self, dao_factory, helpers) + return helpers.responses.send_HTTP_OK(self.api) + end, + + PATCH = function(self, dao_factory, helpers) + self.params.id = self.api.id + + local new_api, err = dao_factory.apis:update(self.params) + if err then + return helpers.yield_error(err) + else + return helpers.responses.send_HTTP_OK(new_api) + end + end + } +} -return Apis diff --git a/kong/api/routes/base_controller.lua b/kong/api/routes/base_controller.lua index 98db819c07d6..a09c8e02684e 100644 --- a/kong/api/routes/base_controller.lua +++ b/kong/api/routes/base_controller.lua @@ -1,23 +1,51 @@ +local Object = require "kong.vendor.classic" local utils = require "kong.tools.utils" -local Object = require "classic" local stringy = require "stringy" local responses = require "kong.tools.responses" -local json_params = require("lapis.application").json_params - -local BaseController = Object:extend() - -local function send_dao_error_response(err) - if err.database then - return responses.send_HTTP_INTERNAL_SERVER_ERROR(err.message) - elseif err.unique then - return responses.send_HTTP_CONFLICT(err.message) - elseif err.foreign then - return responses.send_HTTP_NOT_FOUND(err.message) - elseif err.invalid_type and err.message.id then - return responses.send_HTTP_BAD_REQUEST(err.message) - else - return responses.send_HTTP_BAD_REQUEST(err.message) +local app_helpers = require "lapis.application" + +local function return_paginated_set(self, dao_collection) + local size = self.params.size and tonumber(self.params.size) or 100 + local offset = self.params.offset and ngx.decode_base64(self.params.offset) or nil + + self.params.size = nil + self.params.offset = nil + + local data, err = dao_collection:find_by_keys(self.params, size, offset) + if err then + return app_helpers.yield_error(err) end + + local next_url + if data.next_page then + next_url = self:build_url(self.req.parsed_url.path, { + port = self.req.parsed_url.port, + query = ngx.encode_args({ + offset = ngx.encode_base64(data.next_page), + size = size + }) + }) + data.next_page = nil + end + + -- This check is required otherwise the response is going to be a + -- JSON Object and not a JSON array. The reason is because an empty Lua array `{}` + -- will not be translated as an empty array by cjson, but as an empty object. + local result = #data == 0 and "{\"data\":[]}" or {data=data, ["next"]=next_url} + + return responses.send_HTTP_OK(result, type(result) ~= "table") +end + +local _M = Object:extend() + +function _M:new(app, dao_factory) + self.app = app + self.dao_factory = dao_factory + self.helpers = { + return_paginated_set = return_paginated_set, + responses = responses, + yield_error = app_helpers.yield_error + } end -- Parses a form value, handling multipart/data values @@ -70,8 +98,25 @@ local function normalize_nested_params(obj) return normalized_obj end -local function parse_params(fn) - return json_params(function(self, ...) +local function default_on_error(self) + local err = self.errors[1] + if type(err) == "table" then + if err.database then + return responses.send_HTTP_INTERNAL_SERVER_ERROR(err.message) + elseif err.unique then + return responses.send_HTTP_CONFLICT(err.message) + elseif err.foreign then + return responses.send_HTTP_NOT_FOUND(err.message) + elseif err.invalid_type and err.message.id then + return responses.send_HTTP_BAD_REQUEST(err.message) + else + return responses.send_HTTP_BAD_REQUEST(err.message) + end + end +end + +function _M.parse_params(fn) + return app_helpers.json_params(function(self, ...) local content_type = self.req.headers["content-type"] if content_type and string.find(content_type:lower(), "application/json", nil, true) then if not self.json then @@ -83,105 +128,23 @@ local function parse_params(fn) end) end --- Expose for children classes and unit testing -BaseController.parse_params = parse_params - -function BaseController:new(dao_collection, collection) - app:post("/"..collection, parse_params(function(self) - local data, err = dao_collection:insert(self.params) - if err then - return send_dao_error_response(err) - else - return responses.send_HTTP_CREATED(data) - end - end)) - - app:put("/"..collection, parse_params(function(self) - local data, err - if self.params.id then - data, err = dao_collection:update(self.params) - if not err then - return responses.send_HTTP_OK(data) - end - else - data, err = dao_collection:insert(self.params) - if not err then - return responses.send_HTTP_CREATED(data) - end +function _M:attach(routes) + for route_path, methods in pairs(routes) do + if not methods.on_error then + methods.on_error = default_on_error end - if err then - return send_dao_error_response(err) - end - end)) - - app:get("/"..collection, function(self) - local size = self.params.size and tonumber(self.params.size) or 100 - local offset = self.params.offset and ngx.decode_base64(self.params.offset) or nil - - self.params.size = nil - self.params.offset = nil - - local data, err = dao_collection:find_by_keys(self.params, size, offset) - if err then - return send_dao_error_response(err) - end - - local next_url - if data.next_page then - next_url = self:build_url(self.req.parsed_url.path, { - port = self.req.parsed_url.port, - query = ngx.encode_args({ - offset = ngx.encode_base64(data.next_page), - size = size - }) - }) - data.next_page = nil - end - - -- This check is required otherwise the response is going to be a - -- JSON Object and not a JSON array. The reason is because an empty Lua array `{}` - -- will not be translated as an empty array by cjson, but as an empty object. - local result = #data == 0 and "{\"data\":[]}" or {data=data, ["next"]=next_url} - - return responses.send_HTTP_OK(result, type(result) ~= "table") - end) - - app:get("/"..collection.."/:id", function(self) - local data, err = dao_collection:find_one(self.params.id) - if err then - return send_dao_error_response(err) - end - if data then - return responses.send_HTTP_OK(data) - else - return responses.send_HTTP_NOT_FOUND() - end - end) - - app:delete("/"..collection.."/:id", function(self) - local ok, err = dao_collection:delete(self.params.id) - if not ok then - if err then - return send_dao_error_response(err) - else - return responses.send_HTTP_NOT_FOUND() + for k, v in pairs(methods) do + local dao_factory = self.dao_factory + local helpers = self.helpers + local method = function(self) + return v(self, dao_factory, helpers) end - else - return responses.send_HTTP_NO_CONTENT() + methods[k] = _M.parse_params(method) end - end) - app:patch("/"..collection.."/:id", parse_params(function(self) - local data, err = dao_collection:update(self.params) - if err then - return send_dao_error_response(err) - elseif not data then - return responses.send_HTTP_NOT_FOUND() - else - return responses.send_HTTP_OK(data) - end - end)) + self.app:match(route_path, route_path, app_helpers.respond_to(methods)) + end end -return BaseController \ No newline at end of file +return _M diff --git a/kong/api/routes/consumers.lua b/kong/api/routes/consumers.lua index e5b8ba1e2075..aed9c423520a 100644 --- a/kong/api/routes/consumers.lua +++ b/kong/api/routes/consumers.lua @@ -1,5 +1,3 @@ --- Copyright (C) Mashape, Inc. - local BaseController = require "kong.api.routes.base_controller" local Consumers = BaseController:extend() diff --git a/kong/dao/cassandra/base_dao.lua b/kong/dao/cassandra/base_dao.lua index aca5dca2cdfa..b9a0ddec7c7b 100644 --- a/kong/dao/cassandra/base_dao.lua +++ b/kong/dao/cassandra/base_dao.lua @@ -5,10 +5,11 @@ -- this object to benefit from methods such as `insert`, `update`, schema validations -- (including UNIQUE and FOREIGN check), marshalling of some properties, etc... +local validations = require("kong.dao.schemas_validation") +local validate = validations.validate local constants = require "kong.constants" local cassandra = require "cassandra" local timestamp = require "kong.tools.timestamp" -local validate = require("kong.dao.schemas_validation").validate local DaoError = require "kong.dao.error" local stringy = require "stringy" local Object = require "classic" @@ -182,12 +183,6 @@ function BaseDao:_close_session(session) end end -local digit = "[0-9a-f]" -local uuid_pattern = "^"..table.concat({ digit:rep(8), digit:rep(4), digit:rep(4), digit:rep(4), digit:rep(12) }, '%-').."$" -local function is_valid_uuid(uuid) - return uuid and uuid:match(uuid_pattern) ~= nil -end - -- Build the array of arguments to pass to lua-resty-cassandra :execute method. -- Note: -- Since this method only accepts an ordered list, we build this list from @@ -206,7 +201,7 @@ local function encode_cassandra_args(schema, t, args_keys) local value = t[column] if schema_field.type == constants.DATABASE_TYPES.ID and value then - if is_valid_uuid(value) then + if validations.is_valid_uuid(value) then value = cassandra.uuid(value) else errors = utils.add_error(errors, column, value.." is an invalid uuid") diff --git a/kong/dao/schemas_validation.lua b/kong/dao/schemas_validation.lua index 0355884fc1a9..082791bc68d9 100644 --- a/kong/dao/schemas_validation.lua +++ b/kong/dao/schemas_validation.lua @@ -153,4 +153,10 @@ function _M.validate(t, schema, is_update) return errors == nil, errors end +local digit = "[0-9a-f]" +local uuid_pattern = "^"..table.concat({ digit:rep(8), digit:rep(4), digit:rep(4), digit:rep(4), digit:rep(12) }, '%-').."$" +function _M.is_valid_uuid(uuid) + return uuid and uuid:match(uuid_pattern) ~= nil +end + return _M diff --git a/kong/plugins/keyauth/api.lua b/kong/plugins/keyauth/api.lua index 73f3e95bbbf3..a4c3eca1f94d 100644 --- a/kong/plugins/keyauth/api.lua +++ b/kong/plugins/keyauth/api.lua @@ -1,9 +1,18 @@ -local BaseController = require "kong.api.routes.base_controller" +local base_controller = require "kong.api.routes.base_controller" -local KeyAuthCredentials = BaseController:extend() +return function(lapis_app, dao_factory) + local inspect = require "inspect" + print(inspect(lapis_app)) + lapis_app:get("api/keyauth", "/apis/:name_or_id", function(self) + if is_valid_uuid(self.params.name_or_id) then + self.params.id = self.params.name_or_id + else + self.params.name = self.params.name_or_id + end + self.params.name_or_id = nil -function KeyAuthCredentials:new() - KeyAuthCredentials.super.new(self, dao.keyauth_credentials, "keyauth_credentials") -end + base_controller.find_by_keys_paginated(self, dao_factory.apis) + end) -return KeyAuthCredentials + base_controller(lapis_app, dao_factory.apis, "apis") +end From d7f06464e4540e4c5faf7c4b295a15c65ec4913a Mon Sep 17 00:00:00 2001 From: Thibault Charbonnier Date: Wed, 20 May 2015 19:42:34 +0200 Subject: [PATCH 2/4] refactor(admin api) support CRUD on all core entities --- kong/api/app.lua | 131 +++++++++++++----- kong/api/crud_helpers.lua | 88 ++++++++++++ kong/api/routes/apis.lua | 105 +++++++------- kong/api/routes/consumers.lua | 53 ++++++- kong/api/routes/kong.lua | 31 +++++ kong/api/routes/plugins_configurations.lua | 45 +++++- spec/integration/admin_api/admin_api_spec.lua | 20 +-- 7 files changed, 354 insertions(+), 119 deletions(-) create mode 100644 kong/api/crud_helpers.lua create mode 100644 kong/api/routes/kong.lua diff --git a/kong/api/app.lua b/kong/api/app.lua index 9a408007310e..9b5f29950fc9 100644 --- a/kong/api/app.lua +++ b/kong/api/app.lua @@ -1,46 +1,90 @@ local lapis = require "lapis" +local utils = require "kong.tools.utils" +local stringy = require "stringy" local responses = require "kong.tools.responses" -local constants = require "kong.constants" - -local Apis = require "kong.api.routes.apis" ---local Consumers = require "kong.api.routes.consumers" ---local PluginsConfigurations = require "kong.api.routes.plugins_configurations" - +local app_helpers = require "lapis.application" local app = lapis.Application() -local BaseController = require "kong.api.routes.base_controller" +-- Put nested keys in objects: +-- Normalize dotted keys in objects. +-- Example: {["key.value.sub"]=1234} becomes {key = {value = {sub=1234}} +-- @param `obj` Object to normalize +-- @return `normalized_object` +local function normalize_nested_params(obj) + local normalized_obj = {} -- create a copy to not modify obj while it is in a loop. + + for k, v in pairs(obj) do + if type(v) == "table" then + -- normalize arrays since Lapis parses ?key[1]=foo as {["1"]="foo"} instead of {"foo"} + if utils.is_array(v) then + local arr = {} + for _, arr_v in pairs(v) do table.insert(arr, arr_v) end + v = arr + else + v = normalize_nested_params(v) -- recursive call on other table values + end + end -local function get_hostname() - local f = io.popen ("/bin/hostname") - local hostname = f:read("*a") or "" - f:close() - hostname = string.gsub(hostname, "\n$", "") - return hostname + -- normalize sub-keys with dot notation + local keys = stringy.split(k, ".") + if #keys > 1 then -- we have a key containing a dot + local current_level = keys[1] -- let's create an empty object for the first level + if normalized_obj[current_level] == nil then + normalized_obj[current_level] = {} + end + table.remove(keys, 1) -- remove the first level + normalized_obj[k] = nil -- remove it from the object + if #keys > 0 then -- if we still have some keys, then there are more levels of nestinf + normalized_obj[current_level][table.concat(keys, ".")] = v + normalized_obj[current_level] = normalize_nested_params(normalized_obj[current_level]) + else + normalized_obj[current_level] = v -- latest level of nesting, attaching the value + end + else + normalized_obj[k] = v -- nothing special with that key, simply attaching the value + end + end + + return normalized_obj end -app:get("/", function(self) - local db_plugins, err = dao.plugins_configurations:find_distinct() - if err then - return responses.send_HTTP_INTERNAL_SERVER_ERROR(err) +local function default_on_error(self) + local err = self.errors[1] + if type(err) == "table" then + if err.database then + return responses.send_HTTP_INTERNAL_SERVER_ERROR(err.message) + elseif err.unique then + return responses.send_HTTP_CONFLICT(err.message) + elseif err.foreign then + return responses.send_HTTP_NOT_FOUND(err.message) + elseif err.invalid_type and err.message.id then + return responses.send_HTTP_BAD_REQUEST(err.message) + else + return responses.send_HTTP_BAD_REQUEST(err.message) + end end +end - return responses.send_HTTP_OK({ - tagline = "Welcome to Kong", - version = constants.VERSION, - hostname = get_hostname(), - plugins = { - available_on_server = configuration.plugins_available, - enabled_in_cluster = db_plugins - }, - lua_version = jit and jit.version or _VERSION - }) -end) +local function parse_params(fn) + return app_helpers.json_params(function(self, ...) + local content_type = self.req.headers["content-type"] + if content_type and string.find(content_type:lower(), "application/json", nil, true) then + if not self.json then + return responses.send_HTTP_BAD_REQUEST("Cannot parse JSON body") + end + end + self.params = normalize_nested_params(self.params) + return fn(self, ...) + end) +end app.default_route = function(self) local path = self.req.parsed_url.path:match("^(.*)/$") if path and self.app.router:resolve(path, self) then return + elseif self.app.router:resolve(self.req.parsed_url.path.."/", self) then + return end return self.app.handle_404(self) @@ -70,13 +114,32 @@ app.handle_error = function(self, err, trace) end end --- Load controllers ---Apis(app, dao) ---Consumers() ---PluginsConfigurations() +local handler_helpers = { + responses = responses, + yield_error = app_helpers.yield_error +} -local kong_app = BaseController(app, dao) -kong_app:attach(Apis) +local function attach_routes(routes) + for route_path, methods in pairs(routes) do + if not methods.on_error then + methods.on_error = default_on_error + end + + for k, v in pairs(methods) do + local method = function(self) + return v(self, dao, handler_helpers) + end + methods[k] = parse_params(method) + end + + app:match(route_path, route_path, app_helpers.respond_to(methods)) + end +end + +for _, v in ipairs({"kong", "apis", "consumers", "plugins_configurations"}) do + local routes = require("kong.api.routes."..v) + attach_routes(routes) +end -- Loading plugins routes --[[if configuration and configuration.plugins_available then diff --git a/kong/api/crud_helpers.lua b/kong/api/crud_helpers.lua new file mode 100644 index 000000000000..469fe92123c3 --- /dev/null +++ b/kong/api/crud_helpers.lua @@ -0,0 +1,88 @@ +local responses = require "kong.tools.responses" +local app_helpers = require "lapis.application" + +local _M = {} + +function _M.paginated_set(self, dao_collection) + local size = self.params.size and tonumber(self.params.size) or 100 + local offset = self.params.offset and ngx.decode_base64(self.params.offset) or nil + + self.params.size = nil + self.params.offset = nil + + local data, err = dao_collection:find_by_keys(self.params, size, offset) + if err then + return app_helpers.yield_error(err) + end + + local next_url + if data.next_page then + next_url = self:build_url(self.req.parsed_url.path, { + port = self.req.parsed_url.port, + query = ngx.encode_args({ + offset = ngx.encode_base64(data.next_page), + size = size + }) + }) + data.next_page = nil + end + + -- This check is required otherwise the response is going to be a + -- JSON Object and not a JSON array. The reason is because an empty Lua array `{}` + -- will not be translated as an empty array by cjson, but as an empty object. + local result = #data == 0 and "{\"data\":[]}" or {data=data, ["next"]=next_url} + + return responses.send_HTTP_OK(result, type(result) ~= "table") +end + +function _M.put(self, dao_collection) + local new_entity, err + if self.params.id then + new_entity, err = dao_collection:update(self.params) + if not err then + return responses.send_HTTP_OK(new_entity) + end + else + new_entity, err = dao_collection:insert(self.params) + if not err then + return responses.send_HTTP_CREATED(new_entity) + end + end + + if err then + return app_helpers.yield_error(err) + end +end + +function _M.post(self, dao_collection) + local data, err = dao_collection:insert(self.params) + if err then + return app_helpers.yield_error(err) + else + return responses.send_HTTP_CREATED(data) + end +end + +function _M.patch(params, dao_collection) + local new_entity, err = dao_collection:update(params) + if err then + return app_helpers.yield_error(err) + else + return responses.send_HTTP_OK(new_entity) + end +end + +function _M.delete(entity_id, dao_collection) + local ok, err = dao_collection:delete(entity_id) + if not ok then + if err then + return app_helpers.yield_error(err) + else + return responses.send_HTTP_NOT_FOUND() + end + else + return responses.send_HTTP_NO_CONTENT() + end +end + +return _M diff --git a/kong/api/routes/apis.lua b/kong/api/routes/apis.lua index c897a67150f9..f07afd730fc4 100644 --- a/kong/api/routes/apis.lua +++ b/kong/api/routes/apis.lua @@ -1,74 +1,73 @@ -local validations = require("kong.dao.schemas") +local validations = require "kong.dao.schemas" +local crud = require "kong.api.crud_helpers" + +local function find_by_name_or_id(self, dao_factory, helpers) + local fetch_keys = { + [validations.is_valid_uuid(self.params.name_or_id) and "id" or "name"] = self.params.name_or_id + } + self.params.name_or_id = nil + + -- TODO: make the base_dao more flexible so we can query find_one with key/values + -- https://github.com/Mashape/kong/issues/103 + local data, err = dao_factory.apis:find_by_keys(fetch_keys) + if err then + return helpers.yield_error(err) + end + + self.api = data[1] + if not self.api then + return helpers.responses.send_HTTP_NOT_FOUND() + end +end return { ["/apis/"] = { - GET = function(self, dao_factory, helpers) - helpers.return_paginated_set(self, dao_factory.apis) + GET = function(self, dao_factory) + crud.paginated_set(self, dao_factory.apis) end, - PUT = function(self, dao_factory, helpers) - local new_api, err - if self.params.id then - new_api, err = dao_factory.apis:update(self.params) - if not err then - return helpers.responses.send_HTTP_OK(new_api) - end - else - new_api, err = dao_factory.apis:insert(self.params) - if not err then - return helpers.responses.send_HTTP_CREATED(new_api) - end - end - - if err then - return helpers.yield_error(err) - end + PUT = function(self, dao_factory) + crud.put(self, dao_factory.apis) end, - POST = function(self, dao_factory, helpers) - local data, err = dao_factory.apis:insert(self.params) - if err then - return helpers.yield_error(err) - else - return helpers.responses.send_HTTP_CREATED(data) - end + POST = function(self, dao_factory) + crud.post(self, dao_factory.apis) end }, ["/apis/:name_or_id"] = { - before = function(self, dao_factory, helpers) - local fetch_keys = { - [validations.is_valid_uuid(self.params.name_or_id) and "id" or "name"] = self.params.name_or_id - } - self.params.name_or_id = nil - - -- TODO: make the base_dao more flexible so we can query find_one with key/values - -- https://github.com/Mashape/kong/issues/103 - local data, err = dao_factory.apis:find_by_keys(fetch_keys) - if err then - return helpers.yield_error(err) - end - - self.api = data[1] - if not self.api then - return helpers.responses.send_HTTP_NOT_FOUND() - end - end, + before = find_by_name_or_id, GET = function(self, dao_factory, helpers) return helpers.responses.send_HTTP_OK(self.api) end, - PATCH = function(self, dao_factory, helpers) + PATCH = function(self, dao_factory) self.params.id = self.api.id + crud.patch(self.params, dao_factory.apis) + end, - local new_api, err = dao_factory.apis:update(self.params) - if err then - return helpers.yield_error(err) - else - return helpers.responses.send_HTTP_OK(new_api) - end + DELETE = function(self, dao_factory) + crud.delete(self.api.id, dao_factory.apis) + end + }, + + ["/apis/:name_or_id/plugins/"] = { + before = function(self, dao_factory, helpers) + find_by_name_or_id(self, dao_factory, helpers) + self.params.api_id = self.api.id + end, + + GET = function(self, dao_factory) + crud.paginated_set(self, dao_factory.plugins_configurations) + end, + + POST = function(self, dao_factory, helpers) + crud.post(self, dao_factory.plugins_configurations) + end, + + PUT = function(self, dao_factory, helpers) + crud.put(self, dao_factory.plugins_configurations) end } } - diff --git a/kong/api/routes/consumers.lua b/kong/api/routes/consumers.lua index aed9c423520a..9b6edad3996b 100644 --- a/kong/api/routes/consumers.lua +++ b/kong/api/routes/consumers.lua @@ -1,9 +1,50 @@ -local BaseController = require "kong.api.routes.base_controller" +local validations = require("kong.dao.schemas") +local crud = require "kong.api.crud_helpers" -local Consumers = BaseController:extend() +return { + ["/consumers/"] = { + GET = function(self, dao_factory) + crud.paginated_set(self, dao_factory.consumers) + end, -function Consumers:new() - Consumers.super.new(self, dao.consumers, "consumers") -end + PUT = function(self, dao_factory) + crud.put(self, dao_factory.consumers) + end, -return Consumers + POST = function(self, dao_factory) + crud.post(self, dao_factory.consumers) + end + }, + + ["/consumers/:username_or_id"] = { + before = function(self, dao_factory, helpers) + local fetch_keys = { + [validations.is_valid_uuid(self.params.username_or_id) and "id" or "username"] = self.params.username_or_id + } + self.params.username_or_id = nil + + local data, err = dao_factory.consumers:find_by_keys(fetch_keys) + if err then + return helpers.yield_error(err) + end + + self.consumer = data[1] + if not self.consumer then + return helpers.responses.send_HTTP_NOT_FOUND() + end + end, + + GET = function(self, dao_factory, helpers) + return helpers.responses.send_HTTP_OK(self.consumer) + end, + + PATCH = function(self, dao_factory, helpers) + self.params.id = self.consumer.id + crud.patch(self.params, dao_factory.consumers) + end, + + DELETE = function(self, dao_factory, helpers) + crud.delete(self.consumer.id, dao_factory.consumers) + end + } +} diff --git a/kong/api/routes/kong.lua b/kong/api/routes/kong.lua new file mode 100644 index 000000000000..77981d06f92f --- /dev/null +++ b/kong/api/routes/kong.lua @@ -0,0 +1,31 @@ +local constants = require "kong.constants" + +local function get_hostname() + local f = io.popen ("/bin/hostname") + local hostname = f:read("*a") or "" + f:close() + hostname = string.gsub(hostname, "\n$", "") + return hostname +end + +return { + ["/"] = { + GET = function(self, dao, helpers) + local db_plugins, err = dao.plugins_configurations:find_distinct() + if err then + return helpers.responses.send_HTTP_INTERNAL_SERVER_ERROR(err) + end + + return helpers.responses.send_HTTP_OK({ + tagline = "Welcome to Kong", + version = constants.VERSION, + hostname = get_hostname(), + plugins = { + available_on_server = configuration.plugins_available, + enabled_in_cluster = db_plugins + }, + lua_version = jit and jit.version or _VERSION + }) + end + } +} diff --git a/kong/api/routes/plugins_configurations.lua b/kong/api/routes/plugins_configurations.lua index 911cf9e09595..6e46aeb93b66 100644 --- a/kong/api/routes/plugins_configurations.lua +++ b/kong/api/routes/plugins_configurations.lua @@ -1,11 +1,42 @@ --- Copyright (C) Mashape, Inc. +local crud = require "kong.api.crud_helpers" -local BaseController = require "kong.api.routes.base_controller" +return { + ["/plugins_configurations"] = { + GET = function(self, dao_factory) + crud.paginated_set(self, dao_factory.plugins_configurations) + end, -local PluginsConfigurations = BaseController:extend() + PUT = function(self, dao_factory) + crud.put(self, dao_factory.plugins_configurations) + end, -function PluginsConfigurations:new() - PluginsConfigurations.super.new(self, dao.plugins_configurations, "plugins_configurations") -end + POST = function(self, dao_factory) + crud.post(self, dao_factory.plugins_configurations) + end + }, -return PluginsConfigurations + ["/plugins_configurations/:id"] = { + before = function(self, dao_factory, helpers) + local err + self.plugin_conf, err = dao_factory.plugins_configurations:find_one(self.params.id) + if err then + return helpers.yield_error(err) + elseif not self.plugin_conf then + return helpers.responses.send_HTTP_NOT_FOUND() + end + end, + + GET = function(self, dao_factory, helpers) + return helpers.responses.send_HTTP_OK(self.plugin_conf) + end, + + PATCH = function(self, dao_factory) + self.params.id = self.plugin_conf.id + crud.patch(self.params, dao_factory.plugins_configurations) + end, + + DELETE = function(self, dao_factory) + crud.delete(self.plugin_conf.id, dao_factory.plugins_configurations) + end + } +} diff --git a/spec/integration/admin_api/admin_api_spec.lua b/spec/integration/admin_api/admin_api_spec.lua index 26f5e2ed8a33..d56fa4e530f1 100644 --- a/spec/integration/admin_api/admin_api_spec.lua +++ b/spec/integration/admin_api/admin_api_spec.lua @@ -209,12 +209,6 @@ describe("Admin API", function() assert.are.equal('{"message":"Not found"}\n', response) end) - it("should respond 400 to malformed requests", function() - local response, status = http_client.get(base_url.."/"..CREATED_IDS[endpoint.collection].."blah") - assert.are.equal(400, status) - assert.are.equal('{"id":"'..CREATED_IDS[endpoint.collection]..'blah is an invalid uuid"}\n', response) - end) - it("should retrieve one entity", function() local response, status = http_client.get(base_url.."/"..CREATED_IDS[endpoint.collection]) local body = json.decode(response) @@ -234,12 +228,6 @@ describe("Admin API", function() assert.are.equal('{"message":"Not found"}\n', response) end) - it("should respond 400 to malformed requests", function() - local response, status = http_client.patch(base_url.."/"..CREATED_IDS[endpoint.collection].."blah") - assert.are.equal(400, status) - assert.are.equal('{"id":"'..CREATED_IDS[endpoint.collection]..'blah is an invalid uuid"}\n', response) - end) - describe("application/x-www-form-urlencoded", function() it("should update an entity with an application/x-www-form-urlencoded body", function() @@ -274,8 +262,8 @@ describe("Admin API", function() local response, status = http_client.patch(base_url.."/"..CREATED_IDS[endpoint.collection], body, { ["content-type"] = "application/json" } ) - local response_body = json.decode(response) assert.are.equal(200, status) + local response_body = json.decode(response) assert.are.equal(CREATED_IDS[endpoint.collection], response_body.id) assert.are.same(body, response_body) end) @@ -297,12 +285,6 @@ describe("Admin API", function() assert.are.same('{"message":"Not found"}\n', response) end) - it("should respond 400 to malformed requests", function() - local response, status = http_client.delete(base_url.."/"..CREATED_IDS[endpoint.collection].."blah") - assert.are.equal(400, status) - assert.are.equal('{"id":"'..CREATED_IDS[endpoint.collection]..'blah is an invalid uuid"}\n', response) - end) - end) it("should delete a plugin_configuration", function() From d11687f7aa50f54f8d9c4b25783b4dee59c6020a Mon Sep 17 00:00:00 2001 From: Thibault Charbonnier Date: Thu, 21 May 2015 18:35:52 +0200 Subject: [PATCH 3/4] new Admin API tests suite --- kong/api/crud_helpers.lua | 4 +- kong/api/routes/apis.lua | 46 ++- kong/dao/cassandra/base_dao.lua | 2 +- kong/dao/schemas/consumers.lua | 7 +- .../admin_api/apis_routes_spec.lua | 375 ++++++++++++++++++ .../admin_api/consumers_routes_spec.lua | 182 +++++++++ spec/integration/admin_api/helpers.lua | 33 ++ .../admin_api/kong_routes_spec.lua | 38 ++ spec/spec_helpers.lua | 4 +- spec/unit/dao/cassandra_spec.lua | 6 +- 10 files changed, 685 insertions(+), 12 deletions(-) create mode 100644 spec/integration/admin_api/apis_routes_spec.lua create mode 100644 spec/integration/admin_api/consumers_routes_spec.lua create mode 100644 spec/integration/admin_api/helpers.lua create mode 100644 spec/integration/admin_api/kong_routes_spec.lua diff --git a/kong/api/crud_helpers.lua b/kong/api/crud_helpers.lua index 469fe92123c3..6681a347b707 100644 --- a/kong/api/crud_helpers.lua +++ b/kong/api/crud_helpers.lua @@ -39,8 +39,10 @@ function _M.put(self, dao_collection) local new_entity, err if self.params.id then new_entity, err = dao_collection:update(self.params) - if not err then + if not err and new_entity then return responses.send_HTTP_OK(new_entity) + elseif not new_entity then + return responses.send_HTTP_NOT_FOUND() end else new_entity, err = dao_collection:insert(self.params) diff --git a/kong/api/routes/apis.lua b/kong/api/routes/apis.lua index f07afd730fc4..f097630bc97b 100644 --- a/kong/api/routes/apis.lua +++ b/kong/api/routes/apis.lua @@ -1,7 +1,7 @@ local validations = require "kong.dao.schemas" local crud = require "kong.api.crud_helpers" -local function find_by_name_or_id(self, dao_factory, helpers) +local function find_api_by_name_or_id(self, dao_factory, helpers) local fetch_keys = { [validations.is_valid_uuid(self.params.name_or_id) and "id" or "name"] = self.params.name_or_id } @@ -20,6 +20,24 @@ local function find_by_name_or_id(self, dao_factory, helpers) end end +local function find_plugin_by_name_or_id(self, dao_factory, helpers) + local fetch_keys = { + api_id = self.api.id, + [validations.is_valid_uuid(self.params.plugin_name_or_id) and "id" or "name"] = self.params.plugin_name_or_id + } + self.params.plugin_name_or_id = nil + + local data, err = dao_factory.plugins_configurations:find_by_keys(fetch_keys) + if err then + return helpers.yield_error(err) + end + + self.plugin = data[1] + if not self.plugin then + return helpers.responses.send_HTTP_NOT_FOUND() + end +end + return { ["/apis/"] = { GET = function(self, dao_factory) @@ -36,7 +54,7 @@ return { }, ["/apis/:name_or_id"] = { - before = find_by_name_or_id, + before = find_api_by_name_or_id, GET = function(self, dao_factory, helpers) return helpers.responses.send_HTTP_OK(self.api) @@ -54,7 +72,7 @@ return { ["/apis/:name_or_id/plugins/"] = { before = function(self, dao_factory, helpers) - find_by_name_or_id(self, dao_factory, helpers) + find_api_by_name_or_id(self, dao_factory, helpers) self.params.api_id = self.api.id end, @@ -69,5 +87,27 @@ return { PUT = function(self, dao_factory, helpers) crud.put(self, dao_factory.plugins_configurations) end + }, + + ["/apis/:name_or_id/plugins/:plugin_name_or_id"] = { + before = function(self, dao_factory, helpers) + find_api_by_name_or_id(self, dao_factory, helpers) + self.params.api_id = self.api.id + + find_plugin_by_name_or_id(self, dao_factory, helpers) + end, + + GET = function(self, dao_factory, helpers) + return helpers.responses.send_HTTP_OK(self.plugin) + end, + + PATCH = function(self, dao_factory, helpers) + self.params.id = self.plugin.id + crud.patch(self.params, dao_factory.plugins_configurations) + end, + + DELETE = function(self, dao_factory) + crud.delete(self.plugin.id, dao_factory.plugins_configurations) + end } } diff --git a/kong/dao/cassandra/base_dao.lua b/kong/dao/cassandra/base_dao.lua index b9a0ddec7c7b..fb01c8f37b6e 100644 --- a/kong/dao/cassandra/base_dao.lua +++ b/kong/dao/cassandra/base_dao.lua @@ -129,7 +129,7 @@ function BaseDao:_check_all_unique(t, is_updating) elseif not unique and k == "self" then return false, nil, self._entity.." already exists" elseif not unique then - errors = utils.add_error(errors, k, k.." already exists with value "..t[k]) + errors = utils.add_error(errors, k, k.." already exists with value '"..t[k].."'") end end end diff --git a/kong/dao/schemas/consumers.lua b/kong/dao/schemas/consumers.lua index 32dd97bc35b9..6ba7d25d5ec4 100644 --- a/kong/dao/schemas/consumers.lua +++ b/kong/dao/schemas/consumers.lua @@ -2,8 +2,11 @@ local stringy = require "stringy" local constants = require "kong.constants" local function check_custom_id_and_username(value, consumer_t) - if (consumer_t.custom_id == nil or stringy.strip(consumer_t.custom_id) == "") - and (consumer_t.username == nil or stringy.strip(consumer_t.username) == "") then + local custom_id = consumer_t.custom_id + local username = consumer_t.username + + if (custom_id == nil or type(custom_id) == "string" and stringy.strip(custom_id) == "") + and (username == nil or type(username) == "string" and stringy.strip(username) == "") then return false, "At least a 'custom_id' or a 'username' must be specified" end return true diff --git a/spec/integration/admin_api/apis_routes_spec.lua b/spec/integration/admin_api/apis_routes_spec.lua new file mode 100644 index 000000000000..852c398ecd20 --- /dev/null +++ b/spec/integration/admin_api/apis_routes_spec.lua @@ -0,0 +1,375 @@ +local json = require "cjson" +local http_client = require "kong.tools.http_client" +local spec_helper = require "spec.spec_helpers" +local send_content_types = require "spec.integration.admin_api.helpers" + +describe("Admin API", function() + + setup(function() + spec_helper.prepare_db() + spec_helper.start_kong() + end) + + teardown(function() + spec_helper.stop_kong() + end) + + describe("/apis/", function() + + local BASE_URL = spec_helper.API_URL.."/apis/" + + describe("POST", function() + + it("[SUCCESS] should create an API", function() + send_content_types(BASE_URL, "POST", { + name="api POST tests", + public_dns="api.mockbin.com", + target_url="http://mockbin.com" + }, 201, nil, {drop_db=true}) + end) + + it("[FAILURE] should notify of malformed body", function() + local response, status = http_client.post(BASE_URL, '{"hello":"world"', {["content-type"] = "application/json"}) + assert.are.equal(400, status) + assert.are.equal('{"message":"Cannot parse JSON body"}\n', response) + end) + + it("[FAILURE] should return proper errors", function() + send_content_types(BASE_URL, "POST", {}, + 400, + '{"public_dns":"public_dns is required","target_url":"target_url is required"}') + + send_content_types(BASE_URL, "POST", {public_dns="api.mockbin.com"}, + 400, '{"target_url":"target_url is required"}') + + send_content_types(BASE_URL, "POST", { + public_dns="api.mockbin.com", + target_url="http://mockbin.com" + }, 409, '{"public_dns":"public_dns already exists with value \'api.mockbin.com\'"}') + end) + + end) + + describe("PUT", function() + setup(function() + spec_helper.drop_db() + end) + + it("[SUCCESS] should create and update", function() + local api = send_content_types(BASE_URL, "PUT", { + name="api PUT tests", + public_dns="api.mockbin.com", + target_url="http://mockbin.com" + }, 201, nil, {drop_db=true}) + + api = send_content_types(BASE_URL, "PUT", { + id=api.id, + name="api PUT tests updated", + public_dns="updated-api.mockbin.com", + target_url="http://mockbin.com" + }, 200) + assert.equal("api PUT tests updated", api.name) + end) + + it("[FAILURE] should return proper errors", function() + send_content_types(BASE_URL, "PUT", {}, + 400, + '{"public_dns":"public_dns is required","target_url":"target_url is required"}') + + send_content_types(BASE_URL, "PUT", {public_dns="api.mockbin.com"}, + 400, '{"target_url":"target_url is required"}') + + send_content_types(BASE_URL, "PUT", { + public_dns="updated-api.mockbin.com", + target_url="http://mockbin.com" + }, 409, '{"public_dns":"public_dns already exists with value \'updated-api.mockbin.com\'"}') + end) + + end) + + describe("GET", function() + + setup(function() + spec_helper.drop_db() + spec_helper.seed_db(10) + end) + + it("should retrieve all", function() + local response, status = http_client.get(BASE_URL) + assert.equal(200, status) + local body = json.decode(response) + assert.truthy(body.data) + assert.equal(10, table.getn(body.data)) + end) + + it("should retrieve a paginated set", function() + local response, status = http_client.get(BASE_URL, {size=3}) + assert.equal(200, status) + local body_page_1 = json.decode(response) + assert.truthy(body_page_1.data) + assert.equal(3, table.getn(body_page_1.data)) + assert.truthy(body_page_1.next) + + response, status = http_client.get(BASE_URL, {size=3,offset=body_page_1.next}) + assert.equal(200, status) + local body_page_2 = json.decode(response) + assert.truthy(body_page_2.data) + assert.equal(3, table.getn(body_page_2.data)) + assert.truthy(body_page_2.next) + assert.not_same(body_page_1, body_page_2) + + response, status = http_client.get(BASE_URL, {size=4,offset=body_page_2.next}) + assert.equal(200, status) + local body_page_3 = json.decode(response) + assert.truthy(body_page_3.data) + assert.equal(4, table.getn(body_page_3.data)) + -- TODO: fixme + --assert.falsy(body_page_3.next) + assert.not_same(body_page_2, body_page_3) + end) + + end) + end) + + describe("/apis/:api", function() + local BASE_URL = spec_helper.API_URL.."/apis/" + local api + + setup(function() + spec_helper.drop_db() + local fixtures = spec_helper.insert_fixtures { + api = {{ public_dns="mockbin.com", target_url="http://mockbin.com" }} + } + api = fixtures.api[1] + end) + + describe("GET", function() + + it("should retrieve by id", function() + local response, status = http_client.get(BASE_URL..api.id) + assert.equal(200, status) + local body = json.decode(response) + assert.same(api, body) + end) + + it("should retrieve by name", function() + local response, status = http_client.get(BASE_URL..api.name) + assert.equal(200, status) + local body = json.decode(response) + assert.same(api, body) + end) + + end) + + describe("PATCH", function() + + it("[SUCCESS] should update an API", function() + local response, status = http_client.patch(BASE_URL..api.id, {name="patch-updated"}) + assert.equal(200, status) + local body = json.decode(response) + assert.same("patch-updated", body.name) + + api = body + + response, status = http_client.patch(BASE_URL..api.name, {name="patch-updated-json"}, {["content-type"]="application/json"}) + assert.equal(200, status) + body = json.decode(response) + assert.same("patch-updated-json", body.name) + + api = body + end) + + it("[FAILURE] should return proper errors", function() + local _, status = http_client.patch(BASE_URL.."hello", {name="patch-updated"}) + assert.equal(404, status) + + response, status = http_client.patch(BASE_URL..api.id, {target_url=""}) + assert.equal(400, status) + assert.equal('{"target_url":"target_url is not a string"}\n', response) + end) + + end) + + describe("DELETE", function() + + it("[FAILURE] should return proper errors", function() + local _, status = http_client.delete(BASE_URL.."hello") + assert.equal(404, status) + end) + + it("[SUCCESS] should delete an API", function() + local response, status = http_client.delete(BASE_URL..api.id) + assert.equal(204, status) + assert.falsy(response) + end) + + end) + + describe("/apis/:api/plugins/", function() + local dao_plugins = spec_helper.get_env().dao_factory.plugins_configurations + + setup(function() + spec_helper.drop_db() + local fixtures = spec_helper.insert_fixtures { + api = {{ public_dns="mockbin.com", target_url="http://mockbin.com" }} + } + api = fixtures.api[1] + BASE_URL = BASE_URL..api.id.."/plugins/" + end) + + describe("POST", function() + + it("[FAILURE] should return proper errors", function() + send_content_types(BASE_URL, "POST", {}, + 400, '{"name":"name is required","value":"value is required"}') + end) + + it("[SUCCESS] should create a plugin configuration", function() + local response, status = http_client.post(BASE_URL, { + name = "keyauth", + ["value.key_names"] = {"apikey"} + }) + assert.equal(201, status) + local body = json.decode(response) + + local _, err = dao_plugins:delete(body.id) + assert.falsy(err) + + response, status = http_client.post(BASE_URL, { + name = "keyauth", + value = {key_names={"apikey"}} + }, {["content-type"]="application/json"}) + assert.equal(201, status) + body = json.decode(response) + + _, err = dao_plugins:delete(body.id) + assert.falsy(err) + end) + + end) + + describe("PUT", function() + + it("[FAILURE] should return proper errors", function() + send_content_types(BASE_URL, "PUT", {}, + 400, '{"name":"name is required","value":"value is required"}') + end) + + it("[SUCCESS] should create and update", function() + local response, status = http_client.put(BASE_URL, { + name = "keyauth", + ["value.key_names"] = {"apikey"} + }) + assert.equal(201, status) + local body = json.decode(response) + + local _, err = dao_plugins:delete(body.id) + assert.falsy(err) + + response, status = http_client.put(BASE_URL, { + name = "keyauth", + value = {key_names={"apikey"}} + }, {["content-type"]="application/json"}) + assert.equal(201, status) + body = json.decode(response) + + response, status = http_client.put(BASE_URL, { + id=body.id, + name = "keyauth", + value = {key_names={"updated_apikey"}} + }, {["content-type"]="application/json"}) + assert.equal(200, status) + body = json.decode(response) + assert.equal("updated_apikey", body.value.key_names[1]) + end) + + end) + + describe("GET", function() + + it("should retrieve all", function() + local response, status = http_client.get(BASE_URL) + assert.equal(200, status) + local body = json.decode(response) + assert.truthy(body.data) + assert.equal(1, table.getn(body.data)) + end) + + end) + + describe("/apis/:api/plugins/:plugin", function() + local BASE_URL = spec_helper.API_URL.."/apis/" + local api, plugin + + setup(function() + spec_helper.drop_db() + local fixtures = spec_helper.insert_fixtures { + api = {{ public_dns="mockbin.com", target_url="http://mockbin.com" }}, + plugin_configuration = {{ name = "keyauth", value = { key_names = { "apikey" }}, __api = 1 }} + } + api = fixtures.api[1] + plugin = fixtures.plugin_configuration[1] + BASE_URL = BASE_URL..api.id.."/plugins/" + end) + + describe("GET", function() + + it("should retrieve by id", function() + local response, status = http_client.get(BASE_URL..plugin.id) + assert.equal(200, status) + local body = json.decode(response) + assert.same(plugin, body) + end) + + it("should retrieve by name", function() + local response, status = http_client.get(BASE_URL..plugin.name) + assert.equal(200, status) + local body = json.decode(response) + assert.same(plugin, body) + end) + + end) + + describe("PATCH", function() + + it("[SUCCESS] should update a plugin", function() + local response, status = http_client.patch(BASE_URL..plugin.id, {["value.key_names"]={"key_updated"}}) + assert.equal(200, status) + local body = json.decode(response) + assert.same("key_updated", body.value.key_names[1]) + + response, status = http_client.patch(BASE_URL..plugin.name, {["value.key_names"]={"key_updated-json"}}, {["content-type"]="application/json"}) + assert.equal(200, status) + body = json.decode(response) + assert.same("key_updated-json", body.value.key_names[1]) + end) + + it("[FAILURE] should return proper errors", function() + local _, status = http_client.patch(BASE_URL.."hello", {}) + assert.equal(404, status) + + response, status = http_client.patch(BASE_URL..plugin.id, {["value.key_names"]="key_updated-json"}) + assert.equal(400, status) + assert.equal('{"value.key_names":"key_names is not a table"}\n', response) + end) + + end) + + describe("DELETE", function() + + it("[FAILURE] should return proper errors", function() + local _, status = http_client.delete(BASE_URL.."hello") + assert.equal(404, status) + end) + + it("[SUCCESS] should delete an API", function() + local response, status = http_client.delete(BASE_URL..plugin.id) + assert.equal(204, status) + assert.falsy(response) + end) + + end) + end) + end) + end) +end) diff --git a/spec/integration/admin_api/consumers_routes_spec.lua b/spec/integration/admin_api/consumers_routes_spec.lua new file mode 100644 index 000000000000..6302d3ac8e95 --- /dev/null +++ b/spec/integration/admin_api/consumers_routes_spec.lua @@ -0,0 +1,182 @@ +local json = require "cjson" +local http_client = require "kong.tools.http_client" +local spec_helper = require "spec.spec_helpers" +local send_content_types = require "spec.integration.admin_api.helpers" + +describe("Admin API", function() + + setup(function() + spec_helper.prepare_db() + spec_helper.start_kong() + end) + + teardown(function() + spec_helper.stop_kong() + end) + + describe("/consumers/", function() + local BASE_URL = spec_helper.API_URL.."/consumers/" + + describe("POST", function() + + it("[SUCCESS] should create a Consumer", function() + send_content_types(BASE_URL, "POST", { + username="consumer POST tests" + }, 201, nil, {drop_db=true}) + end) + + it("[FAILURE] should return proper errors", function() + send_content_types(BASE_URL, "POST", {}, + 400, + '{"custom_id":"At least a \'custom_id\' or a \'username\' must be specified","username":"At least a \'custom_id\' or a \'username\' must be specified"}') + + send_content_types(BASE_URL, "POST", { + username="consumer POST tests" + }, 409, '{"username":"username already exists with value \'consumer POST tests\'"}') + end) + + end) + + describe("PUT", function() + + it("[SUCCESS] should create and update", function() + local consumer = send_content_types(BASE_URL, "PUT", { + username="consumer PUT tests" + }, 201, nil, {drop_db=true}) + + consumer = send_content_types(BASE_URL, "PUT", { + id=consumer.id, + username="consumer PUT tests updated", + }, 200) + assert.equal("consumer PUT tests updated", consumer.username) + end) + + it("[FAILURE] should return proper errors", function() + send_content_types(BASE_URL, "PUT", {}, + 400, + '{"custom_id":"At least a \'custom_id\' or a \'username\' must be specified","username":"At least a \'custom_id\' or a \'username\' must be specified"}') + + send_content_types(BASE_URL, "PUT", { + username="consumer PUT tests updated", + }, 409, '{"username":"username already exists with value \'consumer PUT tests updated\'"}') + end) + + end) + + describe("GET", function() + + setup(function() + spec_helper.drop_db() + spec_helper.seed_db(10) + end) + + it("should retrieve all", function() + local response, status = http_client.get(BASE_URL) + assert.equal(200, status) + local body = json.decode(response) + assert.truthy(body.data) + assert.equal(10, table.getn(body.data)) + end) + + it("should retrieve a paginated set", function() + local response, status = http_client.get(BASE_URL, {size=3}) + assert.equal(200, status) + local body_page_1 = json.decode(response) + assert.truthy(body_page_1.data) + assert.equal(3, table.getn(body_page_1.data)) + assert.truthy(body_page_1.next) + + response, status = http_client.get(BASE_URL, {size=3,offset=body_page_1.next}) + assert.equal(200, status) + local body_page_2 = json.decode(response) + assert.truthy(body_page_2.data) + assert.equal(3, table.getn(body_page_2.data)) + assert.truthy(body_page_2.next) + assert.not_same(body_page_1, body_page_2) + + response, status = http_client.get(BASE_URL, {size=4,offset=body_page_2.next}) + assert.equal(200, status) + local body_page_3 = json.decode(response) + assert.truthy(body_page_3.data) + assert.equal(4, table.getn(body_page_3.data)) + -- TODO: fixme + --assert.falsy(body_page_3.next) + assert.not_same(body_page_2, body_page_3) + end) + + end) + + describe("/consumers/:consumer", function() + local consumer + + setup(function() + spec_helper.drop_db() + local fixtures = spec_helper.insert_fixtures { + consumer = {{ username="get_consumer_tests" }} + } + consumer = fixtures.consumer[1] + end) + + describe("GET", function() + + it("should retrieve by id", function() + local response, status = http_client.get(BASE_URL..consumer.id) + assert.equal(200, status) + local body = json.decode(response) + assert.same(consumer, body) + end) + + it("should retrieve by username", function() + local response, status = http_client.get(BASE_URL..consumer.username) + assert.equal(200, status) + local body = json.decode(response) + assert.same(consumer, body) + end) + + end) + + describe("PATCH", function() + + it("[SUCCESS] should update a Consumer", function() + local response, status = http_client.patch(BASE_URL..consumer.id, {username="patch-updated"}) + assert.equal(200, status) + local body = json.decode(response) + assert.same("patch-updated", body.username) + + consumer = body + + response, status = http_client.patch(BASE_URL..consumer.username, {username="patch-updated-json"}, {["content-type"]="application/json"}) + assert.equal(200, status) + body = json.decode(response) + assert.same("patch-updated-json", body.username) + + consumer = body + end) + + it("[FAILURE] should return proper errors", function() + local _, status = http_client.patch(BASE_URL.."hello", {username="patch-updated"}) + assert.equal(404, status) + + response, status = http_client.patch(BASE_URL..consumer.id, {username=""}) + assert.equal(400, status) + assert.equal('{"username":"username is not a string"}\n', response) + end) + end) + + describe("DELETE", function() + + it("[FAILURE] should return proper errors", function() + local _, status = http_client.delete(BASE_URL.."hello") + assert.equal(404, status) + end) + + it("[SUCCESS] should delete a Consumer", function() + local response, status = http_client.delete(BASE_URL..consumer.id) + assert.equal(204, status) + assert.falsy(response) + end) + + end) + end) + end) +end) diff --git a/spec/integration/admin_api/helpers.lua b/spec/integration/admin_api/helpers.lua new file mode 100644 index 000000000000..9e54fd0c1389 --- /dev/null +++ b/spec/integration/admin_api/helpers.lua @@ -0,0 +1,33 @@ +local json = require "cjson" +local http_client = require "kong.tools.http_client" +local spec_helper = require "spec.spec_helpers" +local assert = require "luassert" + +local function send_content_types(url, method, body, res_status, res_body, options) + if not options then options = {} end + + local form_response, form_status = http_client[method:lower()](url, body) + assert.equal(res_status, form_status) + + if options.drop_db then + spec_helper.drop_db() + end + + local json_response, json_status = http_client[method:lower()](url, body, {["content-type"]="application/json"}) + assert.equal(res_status, json_status) + + if res_body then + assert.same(res_body.."\n", form_response) + assert.same(res_body.."\n", json_response) + end + + local res_obj + local status, res = pcall(function() res_obj = json.decode(json_response) end) + if not status then + error(res, 2) + end + + return res_obj +end + +return send_content_types diff --git a/spec/integration/admin_api/kong_routes_spec.lua b/spec/integration/admin_api/kong_routes_spec.lua new file mode 100644 index 000000000000..202e8e701c04 --- /dev/null +++ b/spec/integration/admin_api/kong_routes_spec.lua @@ -0,0 +1,38 @@ +local json = require "cjson" +local http_client = require "kong.tools.http_client" +local spec_helper = require "spec.spec_helpers" + +describe("Admin API", function() + + setup(function() + spec_helper.prepare_db() + spec_helper.start_kong() + end) + + teardown(function() + spec_helper.stop_kong() + end) + + describe("Kong routes", function() + describe("/", function() + local constants = require "kong.constants" + + it("should return Kong's version and a welcome message", function() + local response, status = http_client.get(spec_helper.API_URL) + assert.are.equal(200, status) + local body = json.decode(response) + assert.truthy(body.version) + assert.truthy(body.tagline) + assert.are.same(constants.VERSION, body.version) + end) + + it("should have a Server header", function() + local _, status, headers = http_client.get(spec_helper.API_URL) + assert.are.same(200, status) + assert.are.same(string.format("%s/%s", constants.NAME, constants.VERSION), headers.server) + assert.falsy(headers.via) -- Via is only set for proxied requests + end) + + end) + end) +end) diff --git a/spec/spec_helpers.lua b/spec/spec_helpers.lua index d6354ffac252..899ab31c7a74 100644 --- a/spec/spec_helpers.lua +++ b/spec/spec_helpers.lua @@ -174,9 +174,9 @@ function _M.drop_db(conf_file) end end -function _M.seed_db(random_amount, conf_file) +function _M.seed_db(amount, conf_file) local env = _M.get_env(conf_file) - return env.faker:seed(random_amount) + return env.faker:seed(amount) end function _M.insert_fixtures(fixtures, conf_file) diff --git a/spec/unit/dao/cassandra_spec.lua b/spec/unit/dao/cassandra_spec.lua index d709624ce10a..82c9efac6936 100644 --- a/spec/unit/dao/cassandra_spec.lua +++ b/spec/unit/dao/cassandra_spec.lua @@ -176,7 +176,7 @@ describe("Cassandra DAO", function() assert.truthy(err) assert.is_daoError(err) assert.True(err.unique) - assert.are.same("name already exists with value "..api_t.name, err.message.name) + assert.are.same("name already exists with value '"..api_t.name.."'", err.message.name) assert.falsy(api) -- Duplicated name @@ -191,7 +191,7 @@ describe("Cassandra DAO", function() assert.truthy(err) assert.is_daoError(err) assert.True(err.unique) - assert.are.same("name already exists with value "..api_t.name, err.message.name) + assert.are.same("name already exists with value '"..api_t.name.."'", err.message.name) end) end) @@ -416,7 +416,7 @@ describe("Cassandra DAO", function() assert.truthy(err) assert.is_daoError(err) assert.True(err.unique) - assert.are.same("public_dns already exists with value "..api_t.public_dns, err.message.public_dns) + assert.are.same("public_dns already exists with value '"..api_t.public_dns.."'", err.message.public_dns) end) end) From 4605e516e1ac3c339989c081543fb27bcb1c972b Mon Sep 17 00:00:00 2001 From: Thibault Charbonnier Date: Fri, 22 May 2015 02:09:55 +0200 Subject: [PATCH 4/4] refactor: keyauth/basicauth APIs + their tests - keyauth and basicauth plugins now have their APIs updated and their route feels more RESTful too, they are accessible via /consumers/:consumer/keyauth - A migration was needed to be able to retrieve them per consumer. - Slightly better file structure Related to #98 --- .../2015-05-22-235608_plugins_fix.lua | 19 +++ kong-0.3.0-1.rockspec | 3 +- kong/api/app.lua | 8 +- kong/api/crud_helpers.lua | 49 ++++++- kong/api/routes/apis.lua | 69 ++++------ kong/api/routes/consumers.lua | 20 +-- kong/api/routes/plugins_configurations.lua | 4 +- kong/plugins/basicauth/api.lua | 54 +++++++- kong/plugins/keyauth/api.lua | 63 ++++++--- .../admin_api/apis_routes_spec.lua | 6 +- spec/plugins/basicauth/access_spec.lua | 86 +++++++++++++ spec/plugins/basicauth/api_spec.lua | 121 ++++++++++++++++++ .../access_spec.lua} | 106 +++------------ spec/plugins/keyauth/api_spec.lua | 121 ++++++++++++++++++ spec/plugins/request_transformer_spec.lua | 2 +- .../app_spec.lua} | 12 +- spec/unit/tools/migrations_spec.lua | 2 +- 17 files changed, 550 insertions(+), 195 deletions(-) create mode 100644 database/migrations/cassandra/2015-05-22-235608_plugins_fix.lua create mode 100644 spec/plugins/basicauth/access_spec.lua create mode 100644 spec/plugins/basicauth/api_spec.lua rename spec/plugins/{authentication_spec.lua => keyauth/access_spec.lua} (58%) create mode 100644 spec/plugins/keyauth/api_spec.lua rename spec/unit/{base_controller_spec.lua => api/app_spec.lua} (85%) diff --git a/database/migrations/cassandra/2015-05-22-235608_plugins_fix.lua b/database/migrations/cassandra/2015-05-22-235608_plugins_fix.lua new file mode 100644 index 000000000000..993ea6a67fd5 --- /dev/null +++ b/database/migrations/cassandra/2015-05-22-235608_plugins_fix.lua @@ -0,0 +1,19 @@ +local Migration = { + name = "2015-05-22-235608_plugins_fix", + + up = function(options) + return [[ + CREATE INDEX IF NOT EXISTS ON keyauth_credentials(consumer_id); + CREATE INDEX IF NOT EXISTS ON basicauth_credentials(consumer_id); + ]] + end, + + down = function(options) + return [[ + DROP INDEX keyauth_credentials_consumer_id_idx; + DROP INDEX basicauth_credentials_consumer_id_idx; + ]] + end +} + +return Migration diff --git a/kong-0.3.0-1.rockspec b/kong-0.3.0-1.rockspec index 9b32e9badbc9..c5048ab0b4be 100644 --- a/kong-0.3.0-1.rockspec +++ b/kong-0.3.0-1.rockspec @@ -134,10 +134,11 @@ build = { ["kong.plugins.ssl.schema"] = "kong/plugins/ssl/schema.lua", ["kong.api.app"] = "kong/api/app.lua", + ["kong.api.crud_helpers"] = "kong/api/crud_helpers.lua", + ["kong.api.routes.kong"] = "kong/api/routes/kong.lua", ["kong.api.routes.apis"] = "kong/api/routes/apis.lua", ["kong.api.routes.consumers"] = "kong/api/routes/consumers.lua", ["kong.api.routes.plugins_configurations"] = "kong/api/routes/plugins_configurations.lua", - ["kong.api.routes.base_controller"] = "kong/api/routes/base_controller.lua" }, install = { conf = { "kong.yml" }, diff --git a/kong/api/app.lua b/kong/api/app.lua index 9b5f29950fc9..4b874463a032 100644 --- a/kong/api/app.lua +++ b/kong/api/app.lua @@ -78,6 +78,8 @@ local function parse_params(fn) end) end +app.parse_params = parse_params + app.default_route = function(self) local path = self.req.parsed_url.path:match("^(.*)/$") @@ -142,16 +144,16 @@ for _, v in ipairs({"kong", "apis", "consumers", "plugins_configurations"}) do end -- Loading plugins routes ---[[if configuration and configuration.plugins_available then +if configuration and configuration.plugins_available then for _, v in ipairs(configuration.plugins_available) do local loaded, mod = utils.load_module_if_exists("kong.plugins."..v..".api") if loaded then ngx.log(ngx.DEBUG, "Loading API endpoints for plugin: "..v) - mod() + attach_routes(mod) else ngx.log(ngx.DEBUG, "No API endpoints loaded for plugin: "..v) end end -end]] +end return app diff --git a/kong/api/crud_helpers.lua b/kong/api/crud_helpers.lua index 6681a347b707..40577b4dbed9 100644 --- a/kong/api/crud_helpers.lua +++ b/kong/api/crud_helpers.lua @@ -1,8 +1,45 @@ local responses = require "kong.tools.responses" +local validations = require "kong.dao.schemas_validation" local app_helpers = require "lapis.application" local _M = {} +function _M.find_api_by_name_or_id(self, dao_factory, helpers) + local fetch_keys = { + [validations.is_valid_uuid(self.params.name_or_id) and "id" or "name"] = self.params.name_or_id + } + self.params.name_or_id = nil + + -- TODO: make the base_dao more flexible so we can query find_one with key/values + -- https://github.com/Mashape/kong/issues/103 + local data, err = dao_factory.apis:find_by_keys(fetch_keys) + if err then + return helpers.yield_error(err) + end + + self.api = data[1] + if not self.api then + return helpers.responses.send_HTTP_NOT_FOUND() + end +end + +function _M.find_consumer_by_username_or_id(self, dao_factory, helpers) + local fetch_keys = { + [validations.is_valid_uuid(self.params.username_or_id) and "id" or "username"] = self.params.username_or_id + } + self.params.username_or_id = nil + + local data, err = dao_factory.consumers:find_by_keys(fetch_keys) + if err then + return helpers.yield_error(err) + end + + self.consumer = data[1] + if not self.consumer then + return helpers.responses.send_HTTP_NOT_FOUND() + end +end + function _M.paginated_set(self, dao_collection) local size = self.params.size and tonumber(self.params.size) or 100 local offset = self.params.offset and ngx.decode_base64(self.params.offset) or nil @@ -35,17 +72,17 @@ function _M.paginated_set(self, dao_collection) return responses.send_HTTP_OK(result, type(result) ~= "table") end -function _M.put(self, dao_collection) +function _M.put(params, dao_collection) local new_entity, err - if self.params.id then - new_entity, err = dao_collection:update(self.params) + if params.id then + new_entity, err = dao_collection:update(params) if not err and new_entity then return responses.send_HTTP_OK(new_entity) elseif not new_entity then return responses.send_HTTP_NOT_FOUND() end else - new_entity, err = dao_collection:insert(self.params) + new_entity, err = dao_collection:insert(params) if not err then return responses.send_HTTP_CREATED(new_entity) end @@ -56,8 +93,8 @@ function _M.put(self, dao_collection) end end -function _M.post(self, dao_collection) - local data, err = dao_collection:insert(self.params) +function _M.post(params, dao_collection) + local data, err = dao_collection:insert(params) if err then return app_helpers.yield_error(err) else diff --git a/kong/api/routes/apis.lua b/kong/api/routes/apis.lua index f097630bc97b..af93f0b7e296 100644 --- a/kong/api/routes/apis.lua +++ b/kong/api/routes/apis.lua @@ -1,43 +1,6 @@ -local validations = require "kong.dao.schemas" +local validations = require "kong.dao.schemas_validation" local crud = require "kong.api.crud_helpers" -local function find_api_by_name_or_id(self, dao_factory, helpers) - local fetch_keys = { - [validations.is_valid_uuid(self.params.name_or_id) and "id" or "name"] = self.params.name_or_id - } - self.params.name_or_id = nil - - -- TODO: make the base_dao more flexible so we can query find_one with key/values - -- https://github.com/Mashape/kong/issues/103 - local data, err = dao_factory.apis:find_by_keys(fetch_keys) - if err then - return helpers.yield_error(err) - end - - self.api = data[1] - if not self.api then - return helpers.responses.send_HTTP_NOT_FOUND() - end -end - -local function find_plugin_by_name_or_id(self, dao_factory, helpers) - local fetch_keys = { - api_id = self.api.id, - [validations.is_valid_uuid(self.params.plugin_name_or_id) and "id" or "name"] = self.params.plugin_name_or_id - } - self.params.plugin_name_or_id = nil - - local data, err = dao_factory.plugins_configurations:find_by_keys(fetch_keys) - if err then - return helpers.yield_error(err) - end - - self.plugin = data[1] - if not self.plugin then - return helpers.responses.send_HTTP_NOT_FOUND() - end -end - return { ["/apis/"] = { GET = function(self, dao_factory) @@ -45,16 +8,16 @@ return { end, PUT = function(self, dao_factory) - crud.put(self, dao_factory.apis) + crud.put(self.params, dao_factory.apis) end, POST = function(self, dao_factory) - crud.post(self, dao_factory.apis) + crud.post(self.params, dao_factory.apis) end }, ["/apis/:name_or_id"] = { - before = find_api_by_name_or_id, + before = crud.find_api_by_name_or_id, GET = function(self, dao_factory, helpers) return helpers.responses.send_HTTP_OK(self.api) @@ -72,7 +35,7 @@ return { ["/apis/:name_or_id/plugins/"] = { before = function(self, dao_factory, helpers) - find_api_by_name_or_id(self, dao_factory, helpers) + crud.find_api_by_name_or_id(self, dao_factory, helpers) self.params.api_id = self.api.id end, @@ -81,20 +44,34 @@ return { end, POST = function(self, dao_factory, helpers) - crud.post(self, dao_factory.plugins_configurations) + crud.post(self.params, dao_factory.plugins_configurations) end, PUT = function(self, dao_factory, helpers) - crud.put(self, dao_factory.plugins_configurations) + crud.put(self.params, dao_factory.plugins_configurations) end }, ["/apis/:name_or_id/plugins/:plugin_name_or_id"] = { before = function(self, dao_factory, helpers) - find_api_by_name_or_id(self, dao_factory, helpers) + crud.find_api_by_name_or_id(self, dao_factory, helpers) self.params.api_id = self.api.id - find_plugin_by_name_or_id(self, dao_factory, helpers) + local fetch_keys = { + api_id = self.api.id, + [validations.is_valid_uuid(self.params.plugin_name_or_id) and "id" or "name"] = self.params.plugin_name_or_id + } + self.params.plugin_name_or_id = nil + + local data, err = dao_factory.plugins_configurations:find_by_keys(fetch_keys) + if err then + return helpers.yield_error(err) + end + + self.plugin = data[1] + if not self.plugin then + return helpers.responses.send_HTTP_NOT_FOUND() + end end, GET = function(self, dao_factory, helpers) diff --git a/kong/api/routes/consumers.lua b/kong/api/routes/consumers.lua index 9b6edad3996b..610e0ffd4d3a 100644 --- a/kong/api/routes/consumers.lua +++ b/kong/api/routes/consumers.lua @@ -1,4 +1,3 @@ -local validations = require("kong.dao.schemas") local crud = require "kong.api.crud_helpers" return { @@ -8,30 +7,17 @@ return { end, PUT = function(self, dao_factory) - crud.put(self, dao_factory.consumers) + crud.put(self.params, dao_factory.consumers) end, POST = function(self, dao_factory) - crud.post(self, dao_factory.consumers) + crud.post(self.params, dao_factory.consumers) end }, ["/consumers/:username_or_id"] = { before = function(self, dao_factory, helpers) - local fetch_keys = { - [validations.is_valid_uuid(self.params.username_or_id) and "id" or "username"] = self.params.username_or_id - } - self.params.username_or_id = nil - - local data, err = dao_factory.consumers:find_by_keys(fetch_keys) - if err then - return helpers.yield_error(err) - end - - self.consumer = data[1] - if not self.consumer then - return helpers.responses.send_HTTP_NOT_FOUND() - end + crud.find_consumer_by_username_or_id(self, dao_factory, helpers) end, GET = function(self, dao_factory, helpers) diff --git a/kong/api/routes/plugins_configurations.lua b/kong/api/routes/plugins_configurations.lua index 6e46aeb93b66..418c5c898c93 100644 --- a/kong/api/routes/plugins_configurations.lua +++ b/kong/api/routes/plugins_configurations.lua @@ -7,11 +7,11 @@ return { end, PUT = function(self, dao_factory) - crud.put(self, dao_factory.plugins_configurations) + crud.put(self.params, dao_factory.plugins_configurations) end, POST = function(self, dao_factory) - crud.post(self, dao_factory.plugins_configurations) + crud.post(self.params, dao_factory.plugins_configurations) end }, diff --git a/kong/plugins/basicauth/api.lua b/kong/plugins/basicauth/api.lua index 67be66d7b1b7..d72a37cc7cd3 100644 --- a/kong/plugins/basicauth/api.lua +++ b/kong/plugins/basicauth/api.lua @@ -1,11 +1,51 @@ --- Copyright (C) Mashape, Inc. +local crud = require "kong.api.crud_helpers" -local BaseController = require "kong.api.routes.base_controller" +return { + ["/consumers/:username_or_id/basicauth/"] = { + before = function(self, dao_factory, helpers) + crud.find_consumer_by_username_or_id(self, dao_factory, helpers) + self.params.consumer_id = self.consumer.id + end, -local BasicAuthCredentials = BaseController:extend() + GET = function(self, dao_factory, helpers) + crud.paginated_set(self, dao_factory.basicauth_credentials) + end, -function BasicAuthCredentials:new() - BasicAuthCredentials.super.new(self, dao.basicauth_credentials, "basicauth_credentials") -end + PUT = function(self, dao_factory) + crud.put(self.params, dao_factory.basicauth_credentials) + end, -return BasicAuthCredentials + POST = function(self, dao_factory) + crud.post(self.params, dao_factory.basicauth_credentials) + end + }, + + ["/consumers/:username_or_id/basicauth/:id"] = { + before = function(self, dao_factory, helpers) + crud.find_consumer_by_username_or_id(self, dao_factory, helpers) + self.params.consumer_id = self.consumer.id + + local data, err = dao_factory.basicauth_credentials:find_by_keys({ id = self.params.id }) + if err then + return helpers.yield_error(err) + end + + self.credential = data[1] + if not self.credential then + return helpers.responses.send_HTTP_NOT_FOUND() + end + end, + + GET = function(self, dao_factory, helpers) + return helpers.responses.send_HTTP_OK(self.credential) + end, + + PATCH = function(self, dao_factory) + crud.patch(self.params, dao_factory.basicauth_credentials) + end, + + DELETE = function(self, dao_factory) + crud.delete(self.credential.id, dao_factory.basicauth_credentials) + end + } +} diff --git a/kong/plugins/keyauth/api.lua b/kong/plugins/keyauth/api.lua index a4c3eca1f94d..84aa16503c24 100644 --- a/kong/plugins/keyauth/api.lua +++ b/kong/plugins/keyauth/api.lua @@ -1,18 +1,51 @@ -local base_controller = require "kong.api.routes.base_controller" - -return function(lapis_app, dao_factory) - local inspect = require "inspect" - print(inspect(lapis_app)) - lapis_app:get("api/keyauth", "/apis/:name_or_id", function(self) - if is_valid_uuid(self.params.name_or_id) then - self.params.id = self.params.name_or_id - else - self.params.name = self.params.name_or_id +local crud = require "kong.api.crud_helpers" + +return { + ["/consumers/:username_or_id/keyauth/"] = { + before = function(self, dao_factory, helpers) + crud.find_consumer_by_username_or_id(self, dao_factory, helpers) + self.params.consumer_id = self.consumer.id + end, + + GET = function(self, dao_factory, helpers) + crud.paginated_set(self, dao_factory.keyauth_credentials) + end, + + PUT = function(self, dao_factory) + crud.put(self.params, dao_factory.keyauth_credentials) + end, + + POST = function(self, dao_factory) + crud.post(self.params, dao_factory.keyauth_credentials) end - self.params.name_or_id = nil + }, + + ["/consumers/:username_or_id/keyauth/:id"] = { + before = function(self, dao_factory, helpers) + crud.find_consumer_by_username_or_id(self, dao_factory, helpers) + self.params.consumer_id = self.consumer.id - base_controller.find_by_keys_paginated(self, dao_factory.apis) - end) + local data, err = dao_factory.keyauth_credentials:find_by_keys({ id = self.params.id }) + if err then + return helpers.yield_error(err) + end - base_controller(lapis_app, dao_factory.apis, "apis") -end + self.plugin = data[1] + if not self.plugin then + return helpers.responses.send_HTTP_NOT_FOUND() + end + end, + + GET = function(self, dao_factory, helpers) + return helpers.responses.send_HTTP_OK(self.plugin) + end, + + PATCH = function(self, dao_factory) + crud.patch(self.params, dao_factory.keyauth_credentials) + end, + + DELETE = function(self, dao_factory) + crud.delete(self.plugin.id, dao_factory.keyauth_credentials) + end + } +} diff --git a/spec/integration/admin_api/apis_routes_spec.lua b/spec/integration/admin_api/apis_routes_spec.lua index 852c398ecd20..49558a49c1b8 100644 --- a/spec/integration/admin_api/apis_routes_spec.lua +++ b/spec/integration/admin_api/apis_routes_spec.lua @@ -15,7 +15,6 @@ describe("Admin API", function() end) describe("/apis/", function() - local BASE_URL = spec_helper.API_URL.."/apis/" describe("POST", function() @@ -51,6 +50,7 @@ describe("Admin API", function() end) describe("PUT", function() + setup(function() spec_helper.drop_db() end) @@ -221,7 +221,7 @@ describe("Admin API", function() it("[FAILURE] should return proper errors", function() send_content_types(BASE_URL, "POST", {}, - 400, '{"name":"name is required","value":"value is required"}') + 400, '{"name":"name is required"}') end) it("[SUCCESS] should create a plugin configuration", function() @@ -252,7 +252,7 @@ describe("Admin API", function() it("[FAILURE] should return proper errors", function() send_content_types(BASE_URL, "PUT", {}, - 400, '{"name":"name is required","value":"value is required"}') + 400, '{"name":"name is required"}') end) it("[SUCCESS] should create and update", function() diff --git a/spec/plugins/basicauth/access_spec.lua b/spec/plugins/basicauth/access_spec.lua new file mode 100644 index 000000000000..1e074b8a0358 --- /dev/null +++ b/spec/plugins/basicauth/access_spec.lua @@ -0,0 +1,86 @@ +local spec_helper = require "spec.spec_helpers" +local http_client = require "kong.tools.http_client" +local cjson = require "cjson" + +local STUB_GET_URL = spec_helper.STUB_GET_URL +local STUB_POST_URL = spec_helper.STUB_POST_URL + +describe("Authentication Plugin", function() + + setup(function() + spec_helper.prepare_db() + spec_helper.insert_fixtures { + api = { + { name = "tests basicauth", public_dns = "basicauth.com", target_url = "http://mockbin.com" } + }, + consumer = { + { username = "basicauth_tests_consuser" } + }, + plugin_configuration = { + { name = "basicauth", value = {}, __api = 1 } + }, + basicauth_credential = { + { username = "username", password = "password", __consumer = 1 } + } + } + + spec_helper.start_kong() + end) + + teardown(function() + spec_helper.stop_kong() + end) + + describe("Basic Authentication", function() + + it("should return invalid credentials when the credential value is wrong", function() + local response, status = http_client.get(STUB_GET_URL, {}, {host = "basicauth.com", authorization = "asd"}) + local body = cjson.decode(response) + assert.are.equal(403, status) + assert.are.equal("Invalid authentication credentials", body.message) + end) + + it("should not pass when passing only the password", function() + local response, status = http_client.get(STUB_GET_URL, {}, {host = "basicauth.com", authorization = "Basic OmFwaWtleTEyMw=="}) + local body = cjson.decode(response) + assert.are.equal(403, status) + assert.are.equal("Invalid authentication credentials", body.message) + end) + + it("should not pass when passing only the username", function() + local response, status = http_client.get(STUB_GET_URL, {}, {host = "basicauth.com", authorization = "Basic dXNlcjEyMzo="}) + local body = cjson.decode(response) + assert.are.equal(403, status) + assert.are.equal("Invalid authentication credentials", body.message) + end) + + it("should return invalid credentials when the credential parameter name is wrong in GET", function() + local response, status = http_client.get(STUB_GET_URL, {}, {host = "basicauth.com", authorization123 = "Basic dXNlcm5hbWU6cGFzc3dvcmQ="}) + local body = cjson.decode(response) + assert.are.equal(403, status) + assert.are.equal("Invalid authentication credentials", body.message) + end) + + it("should return invalid credentials when the credential parameter name is wrong in POST", function() + local response, status = http_client.post(STUB_POST_URL, {}, {host = "basicauth.com", authorization123 = "Basic dXNlcm5hbWU6cGFzc3dvcmQ="}) + local body = cjson.decode(response) + assert.are.equal(403, status) + assert.are.equal("Invalid authentication credentials", body.message) + end) + + it("should pass with GET", function() + local response, status = http_client.get(STUB_GET_URL, {}, {host = "basicauth.com", authorization = "Basic dXNlcm5hbWU6cGFzc3dvcmQ="}) + assert.are.equal(200, status) + local parsed_response = cjson.decode(response) + assert.are.equal("Basic dXNlcm5hbWU6cGFzc3dvcmQ=", parsed_response.headers.authorization) + end) + + it("should pass with POST", function() + local response, status = http_client.post(STUB_POST_URL, {}, {host = "basicauth.com", authorization = "Basic dXNlcm5hbWU6cGFzc3dvcmQ="}) + assert.are.equal(200, status) + local parsed_response = cjson.decode(response) + assert.are.equal("Basic dXNlcm5hbWU6cGFzc3dvcmQ=", parsed_response.headers.authorization) + end) + + end) +end) diff --git a/spec/plugins/basicauth/api_spec.lua b/spec/plugins/basicauth/api_spec.lua new file mode 100644 index 000000000000..be23c3a468dc --- /dev/null +++ b/spec/plugins/basicauth/api_spec.lua @@ -0,0 +1,121 @@ +local json = require "cjson" +local http_client = require "kong.tools.http_client" +local spec_helper = require "spec.spec_helpers" + +describe("Basic Auth Credentials API", function() + local BASE_URL, credential, consumer + + setup(function() + spec_helper.prepare_db() + spec_helper.start_kong() + end) + + teardown(function() + spec_helper.stop_kong() + end) + + describe("/consumers/:consumer/basicauth/", function() + + setup(function() + local fixtures = spec_helper.insert_fixtures { + consumer = {{ username = "bob" }} + } + consumer = fixtures.consumer[1] + BASE_URL = spec_helper.API_URL.."/consumers/bob/basicauth/" + end) + + describe("POST", function() + + it("[SUCCESS] should create a basicauth credential", function() + local response, status = http_client.post(BASE_URL, { username = "bob", password = "1234" }) + assert.equal(201, status) + credential = json.decode(response) + assert.equal(consumer.id, credential.consumer_id) + end) + + it("[FAILURE] should return proper errors", function() + local response, status = http_client.post(BASE_URL, {}) + assert.equal(400, status) + assert.equal('{"username":"username is required"}\n', response) + end) + + end) + + describe("PUT", function() + setup(function() + spec_helper.get_env().dao_factory.basicauth_credentials:delete(credential.id) + end) + + it("[SUCCESS] should create and update", function() + local response, status = http_client.put(BASE_URL, { username = "bob", password = "1234" }) + assert.equal(201, status) + credential = json.decode(response) + assert.equal(consumer.id, credential.consumer_id) + end) + + it("[FAILURE] should return proper errors", function() + local response, status = http_client.put(BASE_URL, {}) + assert.equal(400, status) + assert.equal('{"username":"username is required"}\n', response) + end) + + end) + + describe("GET", function() + + it("should retrieve all", function() + local response, status = http_client.get(BASE_URL) + assert.equal(200, status) + local body = json.decode(response) + assert.equal(1, #(body.data)) + end) + + end) + end) + + describe("/consumers/:consumer/basicauth/:id", function() + + describe("GET", function() + + it("should retrieve by id", function() + local response, status = http_client.get(BASE_URL..credential.id) + assert.equal(200, status) + end) + + end) + + describe("PATCH", function() + + it("[SUCCESS] should update a credential", function() + local response, status = http_client.patch(BASE_URL..credential.id, { username = "alice" }) + assert.equal(200, status) + credential = json.decode(response) + assert.equal("alice", credential.username) + end) + + it("[FAILURE] should return proper errors", function() + local response, status = http_client.patch(BASE_URL..credential.id, { username = "" }) + assert.equal(400, status) + assert.equal('{"username":"username is not a string"}\n', response) + end) + + end) + + describe("DELETE", function() + + it("[FAILURE] should return proper errors", function() + local _, status = http_client.delete(BASE_URL.."blah") + assert.equal(400, status) + + _, status = http_client.delete(BASE_URL.."00000000-0000-0000-0000-000000000000") + assert.equal(404, status) + end) + + it("[SUCCESS] should delete a credential", function() + local _, status = http_client.delete(BASE_URL..credential.id) + assert.equal(204, status) + end) + + end) + end) +end) diff --git a/spec/plugins/authentication_spec.lua b/spec/plugins/keyauth/access_spec.lua similarity index 58% rename from spec/plugins/authentication_spec.lua rename to spec/plugins/keyauth/access_spec.lua index d96aaffa7084..f1cdbce84df4 100644 --- a/spec/plugins/authentication_spec.lua +++ b/spec/plugins/keyauth/access_spec.lua @@ -11,23 +11,18 @@ describe("Authentication Plugin", function() spec_helper.prepare_db() spec_helper.insert_fixtures { api = { - { name = "tests auth 1", public_dns = "test1.com", target_url = "http://mockbin.com" }, - { name = "tests auth 2", public_dns = "test2.com", target_url = "http://mockbin.com" }, - { name = "tests auth 3", public_dns = "test3.com", target_url = "http://mockbin.com" } + { name = "tests auth 1", public_dns = "keyauth1.com", target_url = "http://mockbin.com" }, + { name = "tests auth 2", public_dns = "keyauth2.com", target_url = "http://mockbin.com" } }, consumer = { { username = "auth_tests_consumer" } }, plugin_configuration = { { name = "keyauth", value = { key_names = { "apikey" }}, __api = 1 }, - { name = "basicauth", value = {}, __api = 2 }, - { name = "keyauth", value = {key_names = {"apikey"}, hide_credentials = true}, __api = 3 } + { name = "keyauth", value = {key_names = {"apikey"}, hide_credentials = true}, __api = 2 } }, keyauth_credential = { { key = "apikey123", __consumer = 1 } - }, - basicauth_credential = { - { username = "username", password = "password", __consumer = 1 } } } @@ -41,56 +36,56 @@ describe("Authentication Plugin", function() describe("Query Authentication", function() it("should return invalid credentials when the credential value is wrong", function() - local response, status = http_client.get(STUB_GET_URL, {apikey = "asd"}, {host = "test1.com"}) + local response, status = http_client.get(STUB_GET_URL, {apikey = "asd"}, {host = "keyauth1.com"}) local body = cjson.decode(response) assert.are.equal(403, status) assert.are.equal("Invalid authentication credentials", body.message) end) it("should return invalid credentials when the credential parameter name is wrong in GET", function() - local response, status = http_client.get(STUB_GET_URL, {apikey123 = "apikey123"}, {host = "test1.com"}) + local response, status = http_client.get(STUB_GET_URL, {apikey123 = "apikey123"}, {host = "keyauth1.com"}) local body = cjson.decode(response) assert.are.equal(403, status) assert.are.equal("Invalid authentication credentials", body.message) end) it("should return invalid credentials when the credential parameter name is wrong in POST", function() - local response, status = http_client.post(STUB_POST_URL, {apikey123 = "apikey123"}, {host = "test1.com"}) + local response, status = http_client.post(STUB_POST_URL, {apikey123 = "apikey123"}, {host = "keyauth1.com"}) local body = cjson.decode(response) assert.are.equal(403, status) assert.are.equal("Invalid authentication credentials", body.message) end) it("should pass with GET", function() - local response, status = http_client.get(STUB_GET_URL, {apikey = "apikey123"}, {host = "test1.com"}) + local response, status = http_client.get(STUB_GET_URL, {apikey = "apikey123"}, {host = "keyauth1.com"}) assert.are.equal(200, status) local parsed_response = cjson.decode(response) assert.are.equal("apikey123", parsed_response.queryString.apikey) end) it("should pass with POST", function() - local response, status = http_client.post(STUB_POST_URL, {apikey = "apikey123"}, {host = "test1.com"}) + local response, status = http_client.post(STUB_POST_URL, {apikey = "apikey123"}, {host = "keyauth1.com"}) assert.are.equal(200, status) local parsed_response = cjson.decode(response) assert.are.equal("apikey123", parsed_response.postData.params.apikey) end) it("should return invalid credentials when the credential parameter name is wrong in GET header", function() - local response, status = http_client.get(STUB_GET_URL, {}, {host = "test1.com", apikey123 = "apikey123"}) + local response, status = http_client.get(STUB_GET_URL, {}, {host = "keyauth1.com", apikey123 = "apikey123"}) local body = cjson.decode(response) assert.are.equal(403, status) assert.are.equal("Invalid authentication credentials", body.message) end) it("should return invalid credentials when the credential parameter name is wrong in POST header", function() - local response, status = http_client.post(STUB_POST_URL, {}, {host = "test1.com", apikey123 = "apikey123"}) + local response, status = http_client.post(STUB_POST_URL, {}, {host = "keyauth1.com", apikey123 = "apikey123"}) local body = cjson.decode(response) assert.are.equal(403, status) assert.are.equal("Invalid authentication credentials", body.message) end) it("should set right headers", function() - local response, status = http_client.post(STUB_POST_URL, {apikey = "apikey123"}, {host = "test1.com"}) + local response, status = http_client.post(STUB_POST_URL, {apikey = "apikey123"}, {host = "keyauth1.com"}) assert.are.equal(200, status) local parsed_response = cjson.decode(response) assert.truthy(parsed_response.headers["x-consumer-id"]) @@ -101,7 +96,7 @@ describe("Authentication Plugin", function() describe("Hide credentials", function() it("should pass with POST and hide credentials", function() - local response, status = http_client.post(STUB_POST_URL, {apikey = "apikey123", wot = "wat"}, {host = "test3.com"}) + local response, status = http_client.post(STUB_POST_URL, {apikey = "apikey123", wot = "wat"}, {host = "keyauth2.com"}) assert.are.equal(200, status) local parsed_response = cjson.decode(response) assert.falsy(parsed_response.postData.params.apikey) @@ -109,7 +104,7 @@ describe("Authentication Plugin", function() end) it("should pass with POST multipart and hide credentials", function() - local response, status = http_client.post_multipart(STUB_POST_URL, {apikey = "apikey123", wot = "wat"}, {host = "test3.com"}) + local response, status = http_client.post_multipart(STUB_POST_URL, {apikey = "apikey123", wot = "wat"}, {host = "keyauth2.com"}) assert.are.equal(200, status) local parsed_response = cjson.decode(response) assert.falsy(parsed_response.postData.params.apikey) @@ -117,14 +112,14 @@ describe("Authentication Plugin", function() end) it("should pass with GET and hide credentials", function() - local response, status = http_client.get(STUB_GET_URL, {}, {host = "test3.com", apikey = "apikey123"}) + local response, status = http_client.get(STUB_GET_URL, {}, {host = "keyauth2.com", apikey = "apikey123"}) assert.are.equal(200, status) local parsed_response = cjson.decode(response) assert.falsy(parsed_response.headers.apikey) end) it("should pass with GET and hide credentials and another param", function() - local response, status = http_client.get(STUB_GET_URL, {}, {host = "test3.com", apikey = "apikey123", foo = "bar"}) + local response, status = http_client.get(STUB_GET_URL, {}, {host = "keyauth2.com", apikey = "apikey123", foo = "bar"}) assert.are.equal(200, status) local parsed_response = cjson.decode(response) assert.falsy(parsed_response.headers.apikey) @@ -132,14 +127,14 @@ describe("Authentication Plugin", function() end) it("should not pass with GET and hide credentials", function() - local response, status = http_client.get(STUB_GET_URL, {}, {host = "test3.com", apikey = "apikey123123"}) + local response, status = http_client.get(STUB_GET_URL, {}, {host = "keyauth2.com", apikey = "apikey123123"}) local body = cjson.decode(response) assert.are.equal(403, status) assert.are.equal("Invalid authentication credentials", body.message) end) it("should pass with GET and hide credentials and another param", function() - local response, status = http_client.get(STUB_GET_URL, {}, {host = "test3.com", apikey = "apikey123", wot = "wat"}) + local response, status = http_client.get(STUB_GET_URL, {}, {host = "keyauth2.com", apikey = "apikey123", wot = "wat"}) assert.are.equal(200, status) local parsed_response = cjson.decode(response) assert.falsy(parsed_response.headers.apikey) @@ -147,82 +142,19 @@ describe("Authentication Plugin", function() end) it("should not pass with GET and hide credentials", function() - local response, status = http_client.get(STUB_GET_URL, {}, {host = "test3.com", apikey = "apikey123123"}) + local response, status = http_client.get(STUB_GET_URL, {}, {host = "keyauth2.com", apikey = "apikey123123"}) local body = cjson.decode(response) assert.are.equal(403, status) assert.are.equal("Invalid authentication credentials", body.message) end) it("should pass with GET and hide credentials in querystring", function() - local response, status = http_client.get(STUB_GET_URL, {apikey = "apikey123"}, {host = "test3.com"}) + local response, status = http_client.get(STUB_GET_URL, {apikey = "apikey123"}, {host = "keyauth2.com"}) assert.are.equal(200, status) local parsed_response = cjson.decode(response) assert.falsy(parsed_response.queryString.apikey) end) end) - - end) - - describe("Basic Authentication", function() - - it("should return invalid credentials when the credential value is wrong", function() - local response, status = http_client.get(STUB_GET_URL, {}, {host = "test2.com", authorization = "asd"}) - local body = cjson.decode(response) - assert.are.equal(403, status) - assert.are.equal("Invalid authentication credentials", body.message) - end) - - it("should not pass when passing only the password", function() - local response, status = http_client.get(STUB_GET_URL, {}, {host = "test2.com", authorization = "Basic OmFwaWtleTEyMw=="}) - local body = cjson.decode(response) - assert.are.equal(403, status) - assert.are.equal("Invalid authentication credentials", body.message) - end) - - it("should not pass when passing only the username", function() - local response, status = http_client.get(STUB_GET_URL, {}, {host = "test2.com", authorization = "Basic dXNlcjEyMzo="}) - local body = cjson.decode(response) - assert.are.equal(403, status) - assert.are.equal("Invalid authentication credentials", body.message) - end) - - it("should return invalid credentials when the credential parameter name is wrong in GET", function() - local response, status = http_client.get(STUB_GET_URL, {}, {host = "test2.com", authorization123 = "Basic dXNlcm5hbWU6cGFzc3dvcmQ="}) - local body = cjson.decode(response) - assert.are.equal(403, status) - assert.are.equal("Invalid authentication credentials", body.message) - end) - - it("should return invalid credentials when the credential parameter name is wrong in POST", function() - local response, status = http_client.post(STUB_POST_URL, {}, {host = "test2.com", authorization123 = "Basic dXNlcm5hbWU6cGFzc3dvcmQ="}) - local body = cjson.decode(response) - assert.are.equal(403, status) - assert.are.equal("Invalid authentication credentials", body.message) - end) - - it("should pass with GET", function() - local response, status = http_client.get(STUB_GET_URL, {}, {host = "test2.com", authorization = "Basic dXNlcm5hbWU6cGFzc3dvcmQ="}) - assert.are.equal(200, status) - local parsed_response = cjson.decode(response) - assert.are.equal("Basic dXNlcm5hbWU6cGFzc3dvcmQ=", parsed_response.headers.authorization) - end) - - it("should pass with POST", function() - local response, status = http_client.post(STUB_POST_URL, {}, {host = "test2.com", authorization = "Basic dXNlcm5hbWU6cGFzc3dvcmQ="}) - assert.are.equal(200, status) - local parsed_response = cjson.decode(response) - assert.are.equal("Basic dXNlcm5hbWU6cGFzc3dvcmQ=", parsed_response.headers.authorization) - end) - - it("should set right headers", function() - local response, status = http_client.post(STUB_POST_URL, {}, {host = "test2.com", authorization = "Basic dXNlcm5hbWU6cGFzc3dvcmQ="}) - assert.are.equal(200, status) - local parsed_response = cjson.decode(response) - assert.truthy(parsed_response.headers["x-consumer-id"]) - assert.truthy(parsed_response.headers["x-consumer-username"]) - assert.are.equal("auth_tests_consumer", parsed_response.headers["x-consumer-username"]) - end) - end) end) diff --git a/spec/plugins/keyauth/api_spec.lua b/spec/plugins/keyauth/api_spec.lua new file mode 100644 index 000000000000..c6c8a9daa5a2 --- /dev/null +++ b/spec/plugins/keyauth/api_spec.lua @@ -0,0 +1,121 @@ +local json = require "cjson" +local http_client = require "kong.tools.http_client" +local spec_helper = require "spec.spec_helpers" + +describe("Basic Auth Credentials API", function() + local BASE_URL, credential, consumer + + setup(function() + spec_helper.prepare_db() + spec_helper.start_kong() + end) + + teardown(function() + spec_helper.stop_kong() + end) + + describe("/consumers/:consumer/keyauth/", function() + + setup(function() + local fixtures = spec_helper.insert_fixtures { + consumer = {{ username = "bob" }} + } + consumer = fixtures.consumer[1] + BASE_URL = spec_helper.API_URL.."/consumers/bob/keyauth/" + end) + + describe("POST", function() + + it("[SUCCESS] should create a keyauth credential", function() + local response, status = http_client.post(BASE_URL, { key = "1234" }) + assert.equal(201, status) + credential = json.decode(response) + assert.equal(consumer.id, credential.consumer_id) + end) + + it("[FAILURE] should return proper errors", function() + local response, status = http_client.post(BASE_URL, {}) + assert.equal(400, status) + assert.equal('{"key":"key is required"}\n', response) + end) + + end) + + describe("PUT", function() + setup(function() + spec_helper.get_env().dao_factory.keyauth_credentials:delete(credential.id) + end) + + it("[SUCCESS] should create and update", function() + local response, status = http_client.put(BASE_URL, { key = "1234" }) + assert.equal(201, status) + credential = json.decode(response) + assert.equal(consumer.id, credential.consumer_id) + end) + + it("[FAILURE] should return proper errors", function() + local response, status = http_client.put(BASE_URL, {}) + assert.equal(400, status) + assert.equal('{"key":"key is required"}\n', response) + end) + + end) + + describe("GET", function() + + it("should retrieve all", function() + local response, status = http_client.get(BASE_URL) + assert.equal(200, status) + local body = json.decode(response) + assert.equal(1, #(body.data)) + end) + + end) + end) + + describe("/consumers/:consumer/keyauth/:id", function() + + describe("GET", function() + + it("should retrieve by id", function() + local response, status = http_client.get(BASE_URL..credential.id) + assert.equal(200, status) + end) + + end) + + describe("PATCH", function() + + it("[SUCCESS] should update a credential", function() + local response, status = http_client.patch(BASE_URL..credential.id, { key = "4321" }) + assert.equal(200, status) + credential = json.decode(response) + assert.equal("4321", credential.key) + end) + + it("[FAILURE] should return proper errors", function() + local response, status = http_client.patch(BASE_URL..credential.id, { key = "" }) + assert.equal(400, status) + assert.equal('{"key":"key is not a string"}\n', response) + end) + + end) + + describe("DELETE", function() + + it("[FAILURE] should return proper errors", function() + local _, status = http_client.delete(BASE_URL.."blah") + assert.equal(400, status) + + _, status = http_client.delete(BASE_URL.."00000000-0000-0000-0000-000000000000") + assert.equal(404, status) + end) + + it("[SUCCESS] should delete a credential", function() + local _, status = http_client.delete(BASE_URL..credential.id) + assert.equal(204, status) + end) + + end) + end) +end) diff --git a/spec/plugins/request_transformer_spec.lua b/spec/plugins/request_transformer_spec.lua index f2855126cf11..a51d2763ecd6 100644 --- a/spec/plugins/request_transformer_spec.lua +++ b/spec/plugins/request_transformer_spec.lua @@ -5,7 +5,7 @@ local cjson = require "cjson" local STUB_GET_URL = spec_helper.STUB_GET_URL local STUB_POST_URL = spec_helper.STUB_POST_URL -describe("Request Transformer Plugin #proxy", function() +describe("Request Transformer Plugin", function() setup(function() spec_helper.prepare_db() diff --git a/spec/unit/base_controller_spec.lua b/spec/unit/api/app_spec.lua similarity index 85% rename from spec/unit/base_controller_spec.lua rename to spec/unit/api/app_spec.lua index 2af3bc35948c..1c0def88834b 100644 --- a/spec/unit/base_controller_spec.lua +++ b/spec/unit/api/app_spec.lua @@ -1,4 +1,4 @@ -local base_controller = require "kong.api.routes.base_controller" +local app = require "kong.api.app" require "kong.tools.ngx_stub" @@ -8,13 +8,13 @@ local stub = { params = { foo = "bar", number = 10, ["value.nested"] = 1, ["value.nested_2"] = 2 } } -describe("Base Controller", function() +describe("App", function() describe("#parse_params()", function() it("should normalize nested properties for parsed form-encoded parameters", function() -- Here Lapis already parsed the form-encoded parameters but we are normalizing -- the nested ones (with "." keys) - local f = base_controller.parse_params(function(stub) + local f = app.parse_params(function(stub) assert.are.same({ foo = "bar", number = 10, @@ -32,7 +32,7 @@ describe("Base Controller", function() ngx.req.get_body_data = function() return '{"foo":"bar","number":10,"value":{"nested":1,"nested_2":2}}' end stub.req.headers["Content-Type"] = "application/json; charset=utf-8" - local f = base_controller.parse_params(function(stub) + local f = app.parse_params(function(stub) assert.are.same({ foo = "bar", number = 10, @@ -49,7 +49,7 @@ describe("Base Controller", function() stub.params = { foo = "bar", number = 10, ["value.nested_1"] = 1, ["value.nested_2"] = 2, ["value.nested.sub-nested"] = "hi" } - local f = base_controller.parse_params(function(stub) + local f = app.parse_params(function(stub) assert.are.same({ foo = 'bar', number = 10, @@ -66,7 +66,7 @@ describe("Base Controller", function() it("should normalize nested properties when they are plain arrays", function() stub.params = { foo = "bar", number = 10, ["value.nested"] = {["1"]="hello", ["2"]="world"}} - local f = base_controller.parse_params(function(stub) + local f = app.parse_params(function(stub) assert.are.same({ foo = 'bar', number = 10, diff --git a/spec/unit/tools/migrations_spec.lua b/spec/unit/tools/migrations_spec.lua index db435e179e03..541d5f10bf46 100644 --- a/spec/unit/tools/migrations_spec.lua +++ b/spec/unit/tools/migrations_spec.lua @@ -109,7 +109,7 @@ describe("Migrations", function() assert.are.same(migrations_names[i], migration.name..".lua") end) - assert.are.same(3, i) + assert.are.same(4, i) assert.spy(env.dao_factory.migrations.get_migrations).was.called(1) assert.spy(env.dao_factory.execute_queries).was.called(#migrations_names-1) assert.spy(env.dao_factory.migrations.add_migration).was.called(#migrations_names-1)