Add code coverage and more unit tests
diff --git a/.gitignore b/.gitignore
index ddfc902..5b58e6a 100644
--- a/.gitignore
+++ b/.gitignore
@@ -2,6 +2,7 @@
api-gateway-config/target/*
api-gateway-config/conf.d/includes/resolvers.conf
*.iml
+.DS_STORE
# Webstorm files
.idea
diff --git a/Dockerfile-test b/Dockerfile-test
index 44d3548..f56bb35 100644
--- a/Dockerfile-test
+++ b/Dockerfile-test
@@ -18,13 +18,14 @@
&& cd /etc/api-gateway/tests \
&& curl -R -O https://raw.githubusercontent.com/bsm/fakengx/master/fakengx.lua
-RUN echo "... Installing lua rocks ..." \
+RUN echo "... Installing lua modules ..." \
&& luarocks install busted \
&& luarocks install luabitop \
&& luarocks install luasocket \
&& luarocks install sha1 \
&& luarocks install md5 \
- && luarocks install fakeredis
+ && luarocks install fakeredis \
+ && luarocks install luacov
COPY init-test.sh /etc/init-container-test.sh
ONBUILD COPY init-test.sh /etc/init-container-test.sh
diff --git a/api-gateway-config/scripts/lua/lib/logger.lua b/api-gateway-config/scripts/lua/lib/logger.lua
index cf089cf..c1e5680 100644
--- a/api-gateway-config/scripts/lua/lib/logger.lua
+++ b/api-gateway-config/scripts/lua/lib/logger.lua
@@ -30,15 +30,9 @@
ngx.log(ngx.ERR, s)
end
---- Handle debug stream
+--- Handle debug stream to stdout
-- @param s String to write to debug stream
function _M.debug(s)
- print(s)
-end
-
---- Output to stdout
--- @param s String to write to stdout
-function _M.info(s)
os.execute("echo \"" .. s .. "\"")
end
diff --git a/api-gateway-config/scripts/lua/lib/redis.lua b/api-gateway-config/scripts/lua/lib/redis.lua
index 40a0093..f8144fc 100644
--- a/api-gateway-config/scripts/lua/lib/redis.lua
+++ b/api-gateway-config/scripts/lua/lib/redis.lua
@@ -50,7 +50,7 @@
if retryCount == 1 then
msg = utils.concatStrings({msg:sub(1, -3), "."})
end
- logger.info(msg)
+ logger.debug(msg)
retryCount = retryCount - 1
os.execute("sleep 1")
connect, err = red:connect(host, port)
@@ -164,6 +164,8 @@
local ok, err = red:del(key)
if not ok then
request.err(500, utils.concatStrings({"Failed deleting resource: ", err}))
+ else
+ return ok
end
end
@@ -172,7 +174,7 @@
-- @param key redis subscription key to create
function _M.createSubscription(red, key)
-- Add/update a subscription key to redis
- local ok, err = red:set(key, true)
+ local ok, err = red:set(key, '')
if not ok then
request.err(500, utils.concatStrings({"Failed adding subscription to redis", err}))
end
@@ -219,7 +221,7 @@
--- Sync with redis on startup and create conf files for resources that are already in redis
-- @param red redis client instance
function syncWithRedis(red)
- logger.info("\nCreating nginx conf files for existing resources...")
+ logger.debug("\nCreating nginx conf files for existing resources...")
local redisKeys, err = red:keys("*")
if not redisKeys then
request.err(500, util.concatStrings({"Failed to sync with Redis: ", err}))
@@ -246,14 +248,14 @@
-- Create new conf file
local resourceObj = _M.getResource(red, redisKey, REDIS_FIELD)
local fileLocation = filemgmt.createResourceConf(BASE_CONF_DIR, tenant, ngx.escape_uri(gatewayPath), resourceObj)
- logger.info(utils.concatStrings({"Updated file: ", fileLocation}))
+ logger.debug(utils.concatStrings({"Updated file: ", fileLocation}))
end
index = index + 1
end
end
end
if resourcesExist == false then
- logger.info("No existing resources.")
+ logger.debug("No existing resources.")
end
end
@@ -289,12 +291,12 @@
local resourceObj = _M.getResource(redisGetClient, redisKey, REDIS_FIELD)
if resourceObj == nil then
local fileLocation = filemgmt.deleteResourceConf(BASE_CONF_DIR, tenant, ngx.escape_uri(gatewayPath))
- logger.info(utils.concatStrings({"Redis key deleted: ", redisKey}))
- logger.info(utils.concatStrings({"Deleted file: ", fileLocation}))
+ logger.debug(utils.concatStrings({"Redis key deleted: ", redisKey}))
+ logger.debug(utils.concatStrings({"Deleted file: ", fileLocation}))
else
local fileLocation = filemgmt.createResourceConf(BASE_CONF_DIR, tenant, ngx.escape_uri(gatewayPath), resourceObj)
- logger.info(utils.concatStrings({"Redis key updated: ", redisKey}))
- logger.info(utils.concatStrings({"Updated file: ", fileLocation}))
+ logger.debug(utils.concatStrings({"Redis key updated: ", redisKey}))
+ logger.debug(utils.concatStrings({"Updated file: ", fileLocation}))
end
end
end
diff --git a/api-gateway-config/scripts/lua/management.lua b/api-gateway-config/scripts/lua/management.lua
index ada9f4a..d582b2b 100644
--- a/api-gateway-config/scripts/lua/management.lua
+++ b/api-gateway-config/scripts/lua/management.lua
@@ -155,7 +155,7 @@
-- Initialize and connect to redis
local redisGetClient = redis.init(REDIS_HOST, REDIS_PORT, REDIS_PASS, 1000)
local redisSubClient = redis.init(REDIS_HOST, REDIS_PORT, REDIS_PASS, 60000) -- read_reply will timeout every minute
- logger.info(utils.concatStrings({"\nConnected to redis at ", REDIS_HOST, ":", REDIS_PORT}))
+ logger.debug(utils.concatStrings({"\nConnected to redis at ", REDIS_HOST, ":", REDIS_PORT}))
redis.subscribe(redisSubClient, redisGetClient)
ngx.exit(200)
end
diff --git a/api-gateway-config/tests/fakengx.lua b/api-gateway-config/tests/fakengx.lua
new file mode 100644
index 0000000..add7f71
--- /dev/null
+++ b/api-gateway-config/tests/fakengx.lua
@@ -0,0 +1,526 @@
+-- Copyright (c) 2012 Dimitrij Denissenko
+--
+-- Permission is hereby granted, free of charge, to any person obtaining a copy of
+-- this software and associated documentation files (the "Software"), to deal in
+-- the Software without restriction, including without limitation the rights to
+-- use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies
+-- of the Software, and to permit persons to whom the Software is furnished to do
+-- so, subject to the following conditions:
+--
+-- The above copyright notice and this permission notice shall be included in all
+-- copies or substantial portions of the Software.
+--
+-- THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+-- IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+-- FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+-- AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+-- LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+-- OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+-- SOFTWARE.
+
+local bit = require 'bit'
+local socket = require 'socket'
+local sha1 = require 'sha1'
+local md5 = require 'md5'
+local mime = require 'mime'
+local CRC32 = { 0x00000000, 0x77073096, 0xEE0E612C, 0x990951BA, 0x076DC419, 0x706AF48F, 0xE963A535, 0x9E6495A3, 0x0EDB8832, 0x79DCB8A4, 0xE0D5E91E, 0x97D2D988, 0x09B64C2B, 0x7EB17CBD, 0xE7B82D07, 0x90BF1D91, 0x1DB71064, 0x6AB020F2, 0xF3B97148, 0x84BE41DE, 0x1ADAD47D, 0x6DDDE4EB, 0xF4D4B551, 0x83D385C7, 0x136C9856, 0x646BA8C0, 0xFD62F97A, 0x8A65C9EC, 0x14015C4F, 0x63066CD9, 0xFA0F3D63, 0x8D080DF5, 0x3B6E20C8, 0x4C69105E, 0xD56041E4, 0xA2677172, 0x3C03E4D1, 0x4B04D447, 0xD20D85FD, 0xA50AB56B, 0x35B5A8FA, 0x42B2986C, 0xDBBBC9D6, 0xACBCF940, 0x32D86CE3, 0x45DF5C75, 0xDCD60DCF, 0xABD13D59, 0x26D930AC, 0x51DE003A, 0xC8D75180, 0xBFD06116, 0x21B4F4B5, 0x56B3C423, 0xCFBA9599, 0xB8BDA50F, 0x2802B89E, 0x5F058808, 0xC60CD9B2, 0xB10BE924, 0x2F6F7C87, 0x58684C11, 0xC1611DAB, 0xB6662D3D, 0x76DC4190, 0x01DB7106, 0x98D220BC, 0xEFD5102A, 0x71B18589, 0x06B6B51F, 0x9FBFE4A5, 0xE8B8D433, 0x7807C9A2, 0x0F00F934, 0x9609A88E, 0xE10E9818, 0x7F6A0DBB, 0x086D3D2D, 0x91646C97, 0xE6635C01, 0x6B6B51F4, 0x1C6C6162, 0x856530D8, 0xF262004E, 0x6C0695ED, 0x1B01A57B, 0x8208F4C1, 0xF50FC457, 0x65B0D9C6, 0x12B7E950, 0x8BBEB8EA, 0xFCB9887C, 0x62DD1DDF, 0x15DA2D49, 0x8CD37CF3, 0xFBD44C65, 0x4DB26158, 0x3AB551CE, 0xA3BC0074, 0xD4BB30E2, 0x4ADFA541, 0x3DD895D7, 0xA4D1C46D, 0xD3D6F4FB, 0x4369E96A, 0x346ED9FC, 0xAD678846, 0xDA60B8D0, 0x44042D73, 0x33031DE5, 0xAA0A4C5F, 0xDD0D7CC9, 0x5005713C, 0x270241AA, 0xBE0B1010, 0xC90C2086, 0x5768B525, 0x206F85B3, 0xB966D409, 0xCE61E49F, 0x5EDEF90E, 0x29D9C998, 0xB0D09822, 0xC7D7A8B4, 0x59B33D17, 0x2EB40D81, 0xB7BD5C3B, 0xC0BA6CAD, 0xEDB88320, 0x9ABFB3B6, 0x03B6E20C, 0x74B1D29A, 0xEAD54739, 0x9DD277AF, 0x04DB2615, 0x73DC1683, 0xE3630B12, 0x94643B84, 0x0D6D6A3E, 0x7A6A5AA8, 0xE40ECF0B, 0x9309FF9D, 0x0A00AE27, 0x7D079EB1, 0xF00F9344, 0x8708A3D2, 0x1E01F268, 0x6906C2FE, 0xF762575D, 0x806567CB, 0x196C3671, 0x6E6B06E7, 0xFED41B76, 0x89D32BE0, 0x10DA7A5A, 0x67DD4ACC, 0xF9B9DF6F, 0x8EBEEFF9, 0x17B7BE43, 0x60B08ED5, 0xD6D6A3E8, 0xA1D1937E, 0x38D8C2C4, 0x4FDFF252, 0xD1BB67F1, 0xA6BC5767, 0x3FB506DD, 0x48B2364B, 0xD80D2BDA, 0xAF0A1B4C, 0x36034AF6, 0x41047A60, 0xDF60EFC3, 0xA867DF55, 0x316E8EEF, 0x4669BE79, 0xCB61B38C, 0xBC66831A, 0x256FD2A0, 0x5268E236, 0xCC0C7795, 0xBB0B4703, 0x220216B9, 0x5505262F, 0xC5BA3BBE, 0xB2BD0B28, 0x2BB45A92, 0x5CB36A04, 0xC2D7FFA7, 0xB5D0CF31, 0x2CD99E8B, 0x5BDEAE1D, 0x9B64C2B0, 0xEC63F226, 0x756AA39C, 0x026D930A, 0x9C0906A9, 0xEB0E363F, 0x72076785, 0x05005713, 0x95BF4A82, 0xE2B87A14, 0x7BB12BAE, 0x0CB61B38, 0x92D28E9B, 0xE5D5BE0D, 0x7CDCEFB7, 0x0BDBDF21, 0x86D3D2D4, 0xF1D4E242, 0x68DDB3F8, 0x1FDA836E, 0x81BE16CD, 0xF6B9265B, 0x6FB077E1, 0x18B74777, 0x88085AE6, 0xFF0F6A70, 0x66063BCA, 0x11010B5C, 0x8F659EFF, 0xF862AE69, 0x616BFFD3, 0x166CCF45, 0xA00AE278, 0xD70DD2EE, 0x4E048354, 0x3903B3C2, 0xA7672661, 0xD06016F7, 0x4969474D, 0x3E6E77DB, 0xAED16A4A, 0xD9D65ADC, 0x40DF0B66, 0x37D83BF0, 0xA9BCAE53, 0xDEBB9EC5, 0x47B2CF7F, 0x30B5FFE9, 0xBDBDF21C, 0xCABAC28A, 0x53B39330, 0x24B4A3A6, 0xBAD03605, 0xCDD70693, 0x54DE5729, 0x23D967BF, 0xB3667A2E, 0xC4614AB8, 0x5D681B02, 0x2A6F2B94, 0xB40BBE37, 0xC30C8EA1, 0x5A05DF1B, 0x2D02EF8D }
+
+-- Helpers
+local encode_param = function(str)
+ return tostring(str):gsub("\n", "\r\n"):gsub("([^%w_])", function (c)
+ return string.format("%%%02X", string.byte(c))
+ end)
+end
+
+local encode_params = function(tab)
+ local list = {}
+ for k, v in pairs(tab) do
+ table.insert(list, encode_param(k) .. "=" .. encode_param(v))
+ end
+ return table.concat(list, "&")
+end
+
+local function reverse_merge(src, defs)
+ local opts = {}
+ for k,v in pairs(src) do opts[k] = v end
+ for k,v in pairs(defs) do
+ if src[k] then v = src[k] end
+ opts[k] = v
+ end
+ return opts
+end
+
+local function stub_options(opts, method)
+ opts = opts or {}
+ if method and opts["method"] == nil then opts["method"] = method end
+ if type(opts.args) == "table" then opts["args"] = encode_params(opts.args) end
+ return opts
+end
+
+local function stub_response(res)
+ return reverse_merge(res or {}, { status = 200, headers = {}, body = "" })
+end
+
+local control_chars = {
+ ["\a"] = "\\a", ["\b"] = "\\b", ["\f"] = "\\f", ["\n"] = "\\n",
+ ["\r"] = "\\r", ["\t"] = "\\t", ["\v"] = "\\v", ["\\"] = "\\\\"
+}
+
+local function replace_control_char(c)
+ return control_chars[c]
+end
+
+local function stub_format(uri, opts)
+ local pad = 0
+ for k,_ in pairs(opts) do
+ if k ~= "method" and #k > pad then pad = #k end
+ end
+
+ local msg = " " .. (opts.method or "GET") .. " " .. uri .. "\n"
+ for k,v in pairs(opts) do
+ if k ~= "method" then
+ k = k .. ":" .. string.rep(" ", pad + 1 - #k)
+ msg = msg .. " " .. k .. v:gsub("(%c)", replace_control_char) .. "\n"
+ end
+ end
+ return msg
+end
+
+-- Capture Registry
+local Captures = {}
+
+function Captures:new()
+ local this = { stubs = {} }
+ setmetatable(this, { __index = self })
+ return this
+end
+
+function Captures:length()
+ return #self.stubs
+end
+
+function Captures:each(fun)
+ for i=self:length(),1,-1 do
+ local stub = self.stubs[i]
+ fun(stub)
+ end
+end
+
+function Captures:find(uri, opts)
+ opts = stub_options(opts, "GET")
+
+ for i=self:length(),1,-1 do
+ local stub = self.stubs[i]
+ if uri == stub.uri then
+ local is_match = true
+
+ for k,v in pairs(stub.opts) do
+ if type(v) == 'function' then
+ is_match = v(opts[k])
+ elseif type(v) == 'string' and v:sub(1, 2) == "~>" then
+ is_match = tostring(opts[k]):match(v:sub(3)) and true
+ elseif opts[k] ~= v then
+ is_match = false
+ end
+ if not is_match then break end
+ end
+ if is_match then return stub end
+ end
+ end
+
+ return nil
+end
+
+function Captures:stub(uri, opts, res)
+ local stub = { uri = uri, opts = stub_options(opts), res = stub_response(res), calls = {} }
+ table.insert(self.stubs, stub)
+ return stub
+end
+
+-- TCP Proxy
+local TCP = {}
+
+function TCP:new()
+ return setmetatable({ host = nil, port = 0, timeout = 0, keepalive = {-1, 0}, data = {} }, { __index = self })
+end
+
+function TCP:connect(host, port)
+ self.host = host
+ self.port = port
+ return true, nil
+end
+
+function TCP:settimeout(value)
+ self.timeout = value
+end
+
+function TCP:setkeepalive(...)
+ self.keepalive = {...}
+end
+
+function TCP:send(msg)
+ table.insert(self.data, msg)
+end
+
+-- UDP Proxy
+local UDP = {}
+
+function UDP:new()
+ return setmetatable({ host = nil, port = 0, timeout = 0, data = {}, closed = false }, { __index = self })
+end
+
+function UDP:setpeername(host, port)
+ self.host = host
+ self.port = port
+ return true, nil
+end
+
+function UDP:settimeout(value)
+ self.timeout = value
+end
+
+function UDP:send(msg)
+ table.insert(self.data, msg)
+ return true, nil
+end
+
+function UDP:close()
+ self.closed = true
+ return true, nil
+end
+
+-- DICT Proxy
+local SharedDict = {}
+
+function SharedDict:new()
+ return setmetatable({ data = {} }, { __index = self })
+end
+
+function SharedDict:get(key)
+ return self.data[key], 0
+end
+
+function SharedDict:set(key, value)
+ self.data[key] = value
+ return true, nil, false
+end
+
+function SharedDict:add(key, value)
+ if self.data[key] ~= nil then
+ return false, "exists", false
+ end
+
+ self.data[key] = value
+ return true, nil, false
+end
+
+function SharedDict:replace(key, value)
+ if self.data[key] == nil then
+ return false, "not found", false
+ end
+
+ self.data[key] = value
+ return true, nil, false
+end
+
+function SharedDict:delete(key)
+ self.data[key] = nil
+end
+
+function SharedDict:incr(key, value)
+ if not self.data[key] then
+ return nil, "not found"
+ elseif type(self.data[key]) ~= "number" then
+ return nil, "not a number"
+ end
+
+ self.data[key] = self.data[key] + value
+ return self.data[key], nil
+end
+
+-- NGX Prototype
+local protoype = {
+
+ -- Log constants
+ STDERR = 0,
+ EMERG = 1,
+ ALERT = 2,
+ CRIT = 3,
+ ERR = 4,
+ WARN = 5,
+ NOTICE = 6,
+ INFO = 7,
+ DEBUG = 8,
+
+ -- HTTP Method Constants
+ HTTP_GET = "GET",
+ HTTP_HEAD = "HEAD",
+ HTTP_POST = "POST",
+ HTTP_PUT = "PUT",
+ HTTP_DELETE = "DELETE",
+
+ -- HTTP Status Constants
+ HTTP_OK = 200,
+ HTTP_CREATED = 201,
+ HTTP_ACCEPTED = 202,
+ HTTP_NO_CONTENT = 204,
+ HTTP_PARTIAL_CONTENT = 206,
+ HTTP_SPECIAL_RESPONSE = 300,
+ HTTP_MOVED_PERMANENTLY = 301,
+ HTTP_MOVED_TEMPORARILY = 302,
+ HTTP_SEE_OTHER = 303,
+ HTTP_NOT_MODIFIED = 304,
+ HTTP_BAD_REQUEST = 400,
+ HTTP_UNAUTHORIZED = 401,
+ HTTP_FORBIDDEN = 403,
+ HTTP_NOT_FOUND = 404,
+ HTTP_NOT_ALLOWED = 405,
+ HTTP_REQUEST_TIME_OUT = 408,
+ HTTP_CONFLICT = 409,
+ HTTP_LENGTH_REQUIRED = 411,
+ HTTP_PRECONDITION_FAILED = 412,
+ HTTP_REQUEST_ENTITY_TOO_LARGE = 413,
+ HTTP_REQUEST_URI_TOO_LARGE = 414,
+ HTTP_UNSUPPORTED_MEDIA_TYPE = 415,
+ HTTP_RANGE_NOT_SATISFIABLE = 416,
+ HTTP_CLOSE = 444,
+ HTTP_NGINX_CODES = 494,
+ HTTP_REQUEST_HEADER_TOO_LARGE = 494,
+ HTTP_INTERNAL_SERVER_ERROR = 500,
+ HTTP_NOT_IMPLEMENTED = 501,
+ HTTP_BAD_GATEWAY = 502,
+ HTTP_SERVICE_UNAVAILABLE = 503,
+ HTTP_GATEWAY_TIME_OUT = 504,
+ HTTP_INSUFFICIENT_STORAGE = 507,
+
+}
+
+-- NGX Builder
+local fakengx = {}
+
+-- Constructor
+function fakengx.new()
+ local ngx = {}
+ for k, v in pairs(protoype) do
+ ngx[k] = v
+ end
+ setmetatable(ngx, getmetatable(protoype))
+
+ -- Create namespaces
+ ngx.req = {}
+ ngx.re = {}
+ ngx.socket = {}
+ ngx.thread = {}
+ ngx.location = {}
+ ngx.shared = {}
+
+ -- Create shared dict API
+ setmetatable(ngx.shared, {
+ __index = function(t, k)
+ t[k] = SharedDict:new()
+ return t[k]
+ end
+ })
+
+ function ngx._reset()
+ ngx.status = 200
+ ngx.var = {}
+ ngx.ctx = {}
+ ngx.header = {}
+ ngx.arg = {}
+
+ -- Internal Registries
+ ngx._captures = Captures:new()
+ ngx._sockets = {}
+ ngx._body = ""
+ ngx._log = ""
+ ngx._exit = nil
+
+ for k,_ in pairs(ngx.shared) do
+ ngx.shared[k] = nil
+ end
+ end
+
+ -- Reset once
+ ngx._reset()
+
+ -- http://wiki.nginx.org/HttpLuaModule#ngx.print
+ function ngx.print(s)
+ ngx._body = ngx._body .. s
+ end
+
+ -- http://wiki.nginx.org/HttpLuaModule#ngx.say
+ function ngx.say(s)
+ ngx.print(s .. "\n")
+ end
+
+ -- http://wiki.nginx.org/HttpLuaModule#ngx.log
+ function ngx.log(level, ...)
+ local args = {...}
+ for i=1,#args do args[i] = tostring(args[i]) or "nil" end
+ ngx._log = ngx._log .. "LOG(" .. tostring(level) .. "): " .. table.concat(args) .. "\n"
+ end
+
+ -- http://wiki.nginx.org/HttpLuaModule#ngx.time
+ function ngx.time()
+ if not ngx._time then
+ ngx._time = os.time()
+ end
+ return ngx._time
+ end
+
+ -- http://wiki.nginx.org/HttpLuaModule#ngx.update_time
+ function ngx.update_time()
+ ngx._time = nil
+ ngx._now = nil
+ end
+
+ -- http://wiki.nginx.org/HttpLuaModule#ngx.now
+ function ngx.now()
+ if not ngx._now then
+ ngx._now = socket.gettime()
+ end
+ return ngx._now
+ end
+
+ -- http://wiki.nginx.org/HttpLuaModule#ngx.cookie_time
+ function ngx.cookie_time(t)
+ return os.date('!%a, %d-%b-%Y %H:%M:%S GMT', t)
+ end
+
+ -- http://wiki.nginx.org/HttpLuaModule#ngx.exit
+ function ngx.exit(status)
+ if status > ngx.status then ngx.status = status end
+ ngx._exit = status
+ end
+
+ -- http://wiki.nginx.org/HttpLuaModule#ngx.crc32_short
+ function ngx.crc32_short(s)
+ local crc, l, i = 0xFFFFFFFF, string.len(s)
+ for i = 1, l, 1 do
+ crc = bit.bxor(bit.rshift(crc, 8), CRC32[bit.band(bit.bxor(crc, string.byte(s, i)), 0xFF) + 1])
+ end
+ return bit.bxor(crc, -1) % 2^32
+ end
+
+ -- http://wiki.nginx.org/HttpLuaModule#ngx.hmac_sha1
+ function ngx.hmac_sha1(secret_key, str)
+ return sha1.hmac_sha1_binary(secret_key, str)
+ end
+
+ -- http://wiki.nginx.org/HttpLuaModule#ngx.sha1_bin
+ function ngx.sha1_bin(str)
+ return sha1.sha1_binary(str)
+ end
+
+ -- http://wiki.nginx.org/HttpLuaModule#ngx.md5
+ function ngx.md5(str)
+ return md5.sumhexa(str)
+ end
+
+ -- http://wiki.nginx.org/HttpLuaModule#ngx.md5_bin
+ function ngx.md5_bin(str)
+ return md5.sum(str)
+ end
+
+ -- http://wiki.nginx.org/HttpLuaModule#ngx.escape_uri
+ function ngx.escape_uri(str)
+ return tostring(str):gsub("\n", "\r\n"):gsub("([^%w_ ])", function (c)
+ return string.format("%%%02X", string.byte(c))
+ end):gsub(" ", "+")
+ end
+
+ -- http://wiki.nginx.org/HttpLuaModule#ngx.unescape_uri
+ function ngx.unescape_uri(str)
+ return tostring(str):gsub("+", " "):gsub("\r\n", "\n"):gsub("%%(%x%x)", function(h)
+ return string.char(tonumber(h,16))
+ end)
+ end
+
+ -- http://wiki.nginx.org/HttpLuaModule#ngx.encode_args
+ function ngx.encode_args(tab)
+ return encode_params(tab)
+ end
+
+ -- http://wiki.nginx.org/HttpLuaModule#ngx.location.capture
+ function ngx.location.capture(uri, opts)
+ local stub = ngx._captures:find(uri, opts)
+ if not stub then
+ local msg = "\n\nUnstubbed request:\n\n" .. stub_format(uri, opts or {}) .. "\nStubbed were:\n"
+ ngx._captures:each(function(stub)
+ msg = msg .. "\n" .. stub_format(stub.uri, stub.opts or {})
+ end)
+ error(msg)
+ end
+
+ table.insert(stub.calls, { uri = uri, opts = opts })
+ return stub.res
+ end
+
+ -- http://wiki.nginx.org/HttpLuaModule#ngx.location.capture_multi
+ function ngx.location.capture_multi(...)
+ local requests = ...
+ local responses = {}
+ for i, request in ipairs(requests) do
+ table.insert(responses, ngx.location.capture(request[1], request[2]))
+ end
+ return unpack(responses)
+ end
+
+ -- Stub a capture
+ function ngx.location.stub(...)
+ return ngx._captures:stub(...)
+ end
+
+ -- http://wiki.nginx.org/HttpLuaModule#ngx.req.read_body
+ function ngx.req.read_body()
+ end
+
+ -- http://wiki.nginx.org/HttpLuaModule#ngx.socket.tcp
+ function ngx.socket.tcp()
+ local sock = TCP:new()
+ table.insert(ngx._sockets, sock)
+ return sock
+ end
+
+ -- http://wiki.nginx.org/HttpLuaModule#ngx.socket.udp
+ function ngx.socket.udp()
+ local sock = UDP:new()
+ table.insert(ngx._sockets, sock)
+ return sock
+ end
+
+ -- http://wiki.nginx.org/HttpLuaModule#ngx.encode_base64
+ function ngx.encode_base64(s)
+ return mime.b64(s)
+ end
+
+ -- http://wiki.nginx.org/HttpLuaModule#ngx.decode_base64
+ function ngx.decode_base64(s)
+ return mime.unb64(s)
+ end
+
+ -- http://wiki.nginx.org/HttpLuaModule#ngx.thread.spawn
+ function ngx.thread.spawn(fun, ...)
+ return { fun = fun, args = {...} }
+ end
+
+ -- http://wiki.nginx.org/HttpLuaModule#ngx.thread.wait
+ function ngx.thread.wait(thread)
+ return true, thread.fun(unpack(thread.args))
+ end
+
+ -- http://wiki.nginx.org/HttpLuaModule#ngx.re.gmatch
+ function ngx.re.gmatch(s, pattern)
+ return string.gmatch(s, pattern)
+ end
+
+ -- http://wiki.nginx.org/HttpLuaModule#ngx.re.match
+ function ngx.re.match(s, pattern)
+ return string.match(s, pattern)
+ end
+
+ return ngx
+end
+
+return fakengx
diff --git a/api-gateway-config/tests/test.lua b/api-gateway-config/tests/test.lua
index af2bf6a..3c9e789 100644
--- a/api-gateway-config/tests/test.lua
+++ b/api-gateway-config/tests/test.lua
@@ -21,49 +21,28 @@
-- Unit tests for the apigateway using the busted framework.
-- @author Alex Song (songs)
-package.path = package.path .. ';/usr/local/share/lua/5.2/?.lua' ..
- ';/usr/local/api-gateway/lualib/?.lua;/etc/api-gateway/scripts/lua/?.lua'
-package.cpath = package.cpath .. ';/usr/local/lib/lua/5.2/?.so;/usr/local/api-gateway/lualib/?.so'
-
+local handle = io.popen('pwd')
+local pwd = handle:read('*a'):sub(1, -2) -- get current working directory
+handle:close()
+package.path = package.path .. ';' .. pwd .. '/../scripts/lua/?.lua'
local fakengx = require 'fakengx'
local fakeredis = require 'fakeredis'
local cjson = require 'cjson'
-
-local redis = require 'lib/redis'
local request = require 'lib/request'
+local utils = require 'lib/utils'
+local logger = require 'lib/logger'
+local redis = require 'lib/redis'
-describe('Testing Redis module', function()
- before_each(function()
- _G.ngx = fakengx.new()
- red = fakeredis.new()
- end)
- it('should generate a resource obj to store in redis', function()
- local key = 'resources:guest:hello'
- local gatewayMethod = 'GET'
- local backendUrl = 'https://httpbin.org:8000/get'
- local backendMethod = gatewayMethod
- local apiId = 12345
- local policies
- local security
- local expected = {
- operations = {
- [gatewayMethod] = {
- backendUrl = backendUrl,
- backendMethod = backendMethod
- }
- },
- apiId = apiId
- }
- expected = cjson.encode(expected)
- local generated = redis.generateResourceObj(red, key, gatewayMethod, backendUrl, backendMethod, apiId, policies, security)
- assert.are.same(expected, generated)
- end)
-end)
+
+------------------------------------
+---- Unit tests for lib modules ----
+------------------------------------
describe('Testing Request module', function()
before_each(function()
_G.ngx = fakengx.new()
end)
+
it('should return correct error response', function()
local code = 500
local msg = 'Internal server error\n'
@@ -71,6 +50,7 @@
assert.are.equal(ngx._body, 'Error: ' .. msg)
assert.are.equal(ngx._exit, code)
end)
+
it('should return correct success response', function()
local code = 200
local msg ='Success!\n'
@@ -79,3 +59,222 @@
assert.are.equal(ngx._exit, code)
end)
end)
+
+
+describe('Testing utils module', function()
+ before_each(function()
+ _G.ngx = fakengx.new()
+ end)
+
+ it('should concatenate strings properly', function()
+ local expected = 'hello' .. 'gateway' .. 'world'
+ local generated = utils.concatStrings({'hello', 'gateway', 'world'})
+ assert.are.equal(expected, generated)
+ end)
+
+ it('should serialize lua table', function()
+ -- Empty table
+ local expected = {}
+ local serialized = utils.serializeTable(expected)
+ loadstring('generated = ' .. serialized)() -- convert serialzed string to lua table
+ assert.are.same(expected, generated)
+
+ -- Simple table
+ expected = {
+ test = true
+ }
+ serialized = utils.serializeTable(expected)
+ loadstring('generated = ' .. serialized)() -- convert serialzed string to lua table
+ assert.are.same(expected, generated)
+
+ -- Complex nested table
+ expected = {
+ test1 = {
+ nested = 'value'
+ },
+ test2 = true
+ }
+ serialized = utils.serializeTable(expected)
+ loadstring('generated = ' .. serialized)() -- convert serialzed string to lua table
+ assert.are.same(expected, generated)
+ end)
+
+ it('should convert templated path parameter', function()
+ -- TODO: Add test cases for convertTemplatedPathParam(m)
+ end)
+
+ it('should convert json body to lua table', function()
+ -- General case
+ local jsonString = '{ "apiId" : 12345, "policies" : [{"test":true}], "security" : {}}'
+ local expected = cjson.decode(jsonString)
+ local args = {}
+ args[jsonString] = true
+ local generated = utils.convertJSONBody(args)
+ assert.are.same(expected, generated)
+
+ -- "=" sign in original body, resulting in separated out key
+ -- original json string is '{ "apiId" : 12345, "policies" : [{"test":true}], "security" : {"apiKey":"a53kw2kfq302="}}'
+ local firstHalf = '{ "apiId" : 12345, "policies" : [{"test":true}], "security" : {"apiKey":"a53kw2kfq302'
+ local secondHalf = '"}}'
+ expected = {
+ apiId = 12345,
+ policies = {{test=true}},
+ security = {apiKey = "a53kw2kfq302="}
+ }
+ args = {}
+ args[firstHalf] = secondHalf
+ generated = utils.convertJSONBody(args)
+ assert.are.same(expected, generated)
+ end)
+end)
+
+
+describe('Testing logger module', function()
+ it('Should handle error stream', function()
+ local msg = 'Error!'
+ logger.err(msg)
+ local expected = 'LOG(4): ' .. msg .. '\n'
+ local generated = ngx._log
+ assert.are.equal(expected, generated)
+ end)
+end)
+
+
+describe('Testing Redis module', function()
+ before_each(function()
+ _G.ngx = fakengx.new()
+ red = fakeredis.new()
+ end)
+
+ it('should generate resource object to store in redis', function()
+ -- Resource object with no policies or security
+ local key = 'resources:guest:hello'
+ local gatewayMethod = 'GET'
+ local backendUrl = 'https://httpbin.org/get'
+ local backendMethod = gatewayMethod
+ local apiId = 12345
+ local policies
+ local security
+ local resourceObj = {
+ operations = {
+ [gatewayMethod] = {
+ backendUrl = backendUrl,
+ backendMethod = backendMethod
+ }
+ },
+ apiId = apiId
+ }
+ local expected = resourceObj
+ local generated = cjson.decode(redis.generateResourceObj(red, key, gatewayMethod, backendUrl, backendMethod, apiId, policies, security))
+ assert.are.same(expected, generated)
+
+ -- Resource object with policy added
+ local policyList = [[
+ [{
+ "type":"rateLimit",
+ "value":[{
+ "interval":60,
+ "rate":100,
+ "scope":"api",
+ "subscription": "true"
+ }]
+ }]
+ ]]
+ policies = cjson.decode(policyList)
+ resourceObj.operations[gatewayMethod].policies = policies
+ expected = resourceObj
+ generated = cjson.decode(redis.generateResourceObj(red, key, gatewayMethod, backendUrl, backendMethod, apiId, policies, security))
+ assert.are.same(expected, generated)
+
+ -- Resource object with security added
+ local securityObj = [[
+ {
+ "type":"apiKey",
+ "scope":"api",
+ "header":"myheader"
+ }
+ ]]
+ security = cjson.decode(securityObj)
+ resourceObj.operations[gatewayMethod].security = security
+ expected = resourceObj
+ generated = cjson.decode(redis.generateResourceObj(red, key, gatewayMethod, backendUrl, backendMethod, apiId, policies, security))
+ assert.are.same(expected, generated)
+
+ -- Update already existing resource object
+ local field = 'resources'
+ redis.createResource(red, key, field, cjson.encode(generated))
+ local newGatewayMethod = 'POST'
+ resourceObj.operations[newGatewayMethod] = {
+ backendUrl = backendUrl,
+ backendMethod = backendMethod
+ }
+ policies = nil
+ security = nil
+ expected = resourceObj
+ generated = cjson.decode(redis.generateResourceObj(red, key, newGatewayMethod, backendUrl, backendMethod, apiId, policies, security))
+ assert.are.same(expected, generated)
+ end)
+
+ it('should get a resource from redis', function()
+ local key = 'resources:guest:hello'
+ local field = 'resources'
+ -- resource doesn't exist in redis
+ local generated = redis.getResource(red, key, field)
+ assert.are.same(nil, generated)
+
+ -- resource exists in redis
+ local expected = redis.generateResourceObj(red, key, 'GET', 'https://httpbin.org/get', 'GET', '12345', nil, nil)
+ red:hset(key, field, expected)
+ generated = redis.getResource(red, key, field)
+ assert.are.same(expected, generated)
+ end)
+
+ it('should create a resource in redis', function()
+ local key = 'resources:guest:hello'
+ local field = 'resources'
+ local expected = redis.generateResourceObj(red, key, 'GET', 'https://httpbin.org/get', 'GET', '12345', nil, nil)
+ redis.createResource(red, key, field, expected)
+ local generated = redis.getResource(red, key, field)
+ assert.are.same(expected, generated)
+ end)
+
+ it('should delete a resource in redis', function()
+ -- Key doesn't exist - throw 404
+ local key = 'resources:guest:hello'
+ local field = 'resources'
+ redis.deleteResource(red, key, field)
+ assert.are.equal(ngx._exit, 404)
+ -- Key exists - deleted properly
+ local resourceObj = redis.generateResourceObj(red, key, 'GET', 'https://httpbin.org/get', 'GET', '12345', nil, nil)
+ redis.createResource(red, key, field, resourceObj)
+ local expected = 1
+ local generated = redis.deleteResource(red, key, field)
+ assert.are.same(expected, generated)
+ end)
+
+ it('shoud create an API Key subscription', function()
+ local key = 'subscriptions:test:apikey'
+ redis.createSubscription(red, key)
+ assert.are.same(true, red:exists(key))
+ end)
+
+ it('should delete an API Key subscription', function()
+ -- API key doesn't exist in redis - throw 404
+ local key = 'subscriptions:test:apikey'
+ redis.deleteSubscription(red, key)
+ assert.are.equal(404, ngx._exit)
+
+ -- API key to delete exists in redis
+ red:set(key, '')
+ redis.deleteSubscription(red, key)
+ assert.are.equal(false, red:exists(key))
+ end)
+end)
+
+--TODO: filemgmt
+
+---------------------------------------
+---- Unit tests for policy modules ----
+---------------------------------------
+
+--TODO: mapping, rateLimit, security
\ No newline at end of file
diff --git a/init-test.sh b/init-test.sh
deleted file mode 100644
index e83d454..0000000
--- a/init-test.sh
+++ /dev/null
@@ -1,3 +0,0 @@
-#!/bin/bash
-cd /etc/api-gateway/tests
-busted --output=TAP test.lua
\ No newline at end of file
diff --git a/run-tests.sh b/run-tests.sh
new file mode 100755
index 0000000..189ef3a
--- /dev/null
+++ b/run-tests.sh
@@ -0,0 +1,10 @@
+#!/bin/bash
+
+# Run unit tests
+cd api-gateway-config/tests
+busted -c --output=TAP test.lua
+
+# Generate code coverage report
+luacov ../scripts/lua/
+cat luacov.report.out
+rm luacov.report.out && rm luacov.stats.out
\ No newline at end of file