Merge pull request #84 from alexsong93/proxy-fix
Redis routing fix for backend proxy
diff --git a/.gitignore b/.gitignore
index d0a944a..021d01b 100644
--- a/.gitignore
+++ b/.gitignore
@@ -15,3 +15,6 @@
# Concourse
secrets.yml
+
+# npm
+npm-debug.log
diff --git a/.travis.yml b/.travis.yml
index 5c783c9..7cc1918 100644
--- a/.travis.yml
+++ b/.travis.yml
@@ -14,4 +14,4 @@
- ./install-deps.sh
script:
- - busted --output=TAP --helper=set_paths spec/test.lua
\ No newline at end of file
+ - busted --output=TAP --helper=set_paths --pattern=.lua scripts
\ No newline at end of file
diff --git a/README.md b/README.md
index 2a59f2f..bbf8a4e 100644
--- a/README.md
+++ b/README.md
@@ -1,5 +1,7 @@
apigateway
=============
+[![Build Status](https://travis-ci.org/openwhisk/apigateway.svg?branch=master)](https://travis-ci.org/openwhisk/apigateway)
+
A performant API Gateway based on Openresty and NGINX.
Project status
@@ -54,8 +56,6 @@
### Testing
- Unit tests can be found in the `api-gateway-config/tests/spec` directory.
-
First install the necessary dependencies:
```
make test-build
@@ -64,5 +64,4 @@
```
make test-run
```
- This will output the results of the tests as well as generate a code coverage report.
diff --git a/api-gateway-config/scripts/lua/routing.lua b/api-gateway-config/scripts/lua/routing.lua
index 62ce0e6..e9d90a4 100644
--- a/api-gateway-config/scripts/lua/routing.lua
+++ b/api-gateway-config/scripts/lua/routing.lua
@@ -20,7 +20,6 @@
--- @module Routing
-- Used to dynamically handle nginx routing based on an object containing implementation details
--- @author Cody Walker (cmwalker), Alex Song (songs)
local cjson = require "cjson"
local utils = require "lib/utils"
@@ -31,7 +30,6 @@
local security = require "policies/security"
local mapping = require "policies/mapping"
local rateLimit = require "policies/rateLimit"
-local logger = require "lib/logger"
local REDIS_HOST = os.getenv("REDIS_HOST")
local REDIS_PORT = os.getenv("REDIS_PORT")
@@ -40,19 +38,15 @@
local _M = {}
--- Main function that handles parsing of invocation details and carries out implementation
-function processCall()
+function _M.processCall()
-- Get resource object from redis
local red = redis.init(REDIS_HOST, REDIS_PORT, REDIS_PASS, 10000)
- local redisKey = utils.concatStrings({"resources:", ngx.var.tenant, ":", ngx.var.gatewayPath})
- local obj = redis.getResource(red, redisKey, "resources")
- -- Check for path parameters
- if obj == nil then
- obj = checkForPathParams(red)
- if obj == nil then
- return request.err(404, 'Not found.')
- end
+ local resourceKeys = redis.getAllResourceKeys(red, ngx.var.tenant)
+ local redisKey = _M.findRedisKey(resourceKeys, ngx.var.tenant, ngx.var.gatewayPath)
+ if redisKey == nil then
+ return request.err(404, 'Not found.')
end
- obj = cjson.decode(obj)
+ local obj = cjson.decode(redis.getResource(red, redisKey, "resources"))
local found = false
for verb, opFields in pairs(obj.operations) do
if string.upper(verb) == ngx.req.get_method() then
@@ -88,33 +82,73 @@
end
end
---- Check redis for path parameters
--- @param red redis client instance
-function checkForPathParams(red)
- local resourceKeys = redis.getAllResourceKeys(red, ngx.var.tenant)
+--- Find the correct redis key based on the path that's passed in
+-- @param resourceKeys list of resourceKeys to search through
+-- @param tenant tenantId
+-- @param path path to look for
+function _M.findRedisKey(resourceKeys, tenant, path)
+ -- Construct a table of redisKeys based on number of slashes in the path
+ local keyTable = {}
for i, key in pairs(resourceKeys) do
- local res = {string.match(key, "([^,]+):([^,]+):([^,]+)")}
- local path = res[3] -- gatewayPath portion of redis key
- local pathParamVars = {}
- for w in string.gfind(path, "({%w+})") do
- w = string.gsub(w, "{", "")
- w = string.gsub(w, "}", "")
- pathParamVars[#pathParamVars + 1] = w
+ local _, count = string.gsub(key, "/", "")
+ -- handle cases where resource path is "/"
+ if count == 1 and string.sub(key, -1) == "/" then
+ count = count - 1
end
- if next(pathParamVars) ~= nil then
- local pathPattern, count = string.gsub(path, "%{(%w*)%}", "([^,]+)")
- local obj = {string.match(ngx.var.gatewayPath, pathPattern)}
- if (#obj == count) then
- for i, v in pairs(obj) do
- ngx.ctx[pathParamVars[i]] = v
+ count = tostring(count)
+ if keyTable[count] == nil then
+ keyTable[count] = {}
+ end
+ table.insert(keyTable[count], key)
+ end
+ -- Find the correct redisKey
+ local redisKey = utils.concatStrings({"resources:", tenant, ":", path})
+ local _, count = string.gsub(redisKey, "/", "")
+ for i = count, 0, -1 do
+ local countString = tostring(i)
+ if keyTable[countString] ~= nil then
+ for _, key in pairs(keyTable[countString]) do
+ -- Check for exact match or path parameter match
+ if key == redisKey or key == utils.concatStrings({redisKey, "/"}) or _M.pathParamMatch(key, redisKey) == true then
+ local res = {string.match(key, "([^:]+):([^:]+):([^:]+)")}
+ ngx.var.gatewayPath = res[3]
+ return key
end
- return redis.getResource(red, key, "resources")
end
+ -- substring redisKey upto last "/"
+ local index = redisKey:match("^.*()/")
+ if index == nil then
+ return nil
+ end
+ redisKey = string.sub(redisKey, 1, index - 1)
end
end
return nil
end
+--- Check redis if resourceKey matches path parameters
+-- @param key key that may have path parameter variables
+-- @param resourceKey redis resourceKey to check if it matches path parameter
+function _M.pathParamMatch(key, resourceKey)
+ local pathParamVars = {}
+ for w in string.gfind(key, "({%w+})") do
+ w = string.sub(w, 2, string.len(w) - 1)
+ pathParamVars[#pathParamVars + 1] = w
+ end
+ if next(pathParamVars) ~= nil then
+ local pathPattern, count = string.gsub(key, "%{(%w*)%}", "([^:]+)")
+ pathPattern = string.gsub(pathPattern, "%-", "%%-")
+ local obj = {string.match(resourceKey, pathPattern)}
+ if (#obj == count) then
+ for i, v in pairs(obj) do
+ ngx.ctx[pathParamVars[i]] = v
+ end
+ return true
+ end
+ end
+ return false
+end
+
--- Function to read the list of policies and send implementation to the correct backend
-- @param obj List of policies containing a type and value field. This function reads the type field and routes it appropriately.
-- @param apiKey optional subscription api key
@@ -145,12 +179,12 @@
local incomingPath = ((j and ngx.var.uri:sub(j + 1)) or nil)
-- Check for backendUrl path
if backendPath == nil or backendPath == '' or backendPath == '/' then
- return (incomingPath and incomingPath ~= '') and incomingPath or '/'
+ incomingPath = (incomingPath and incomingPath ~= '') and incomingPath or '/'
+ incomingPath = string.sub(incomingPath, 1, 1) == '/' and incomingPath or utils.concatStrings({'/', incomingPath})
+ return incomingPath
else
return utils.concatStrings({backendPath, incomingPath})
end
end
-_M.processCall = processCall
-
return _M
diff --git a/api-gateway-config/tests/install-deps.sh b/api-gateway-config/tests/install-deps.sh
index 5f9d12d..cda61f1 100755
--- a/api-gateway-config/tests/install-deps.sh
+++ b/api-gateway-config/tests/install-deps.sh
@@ -11,3 +11,4 @@
luarocks install --tree=lua_modules sha1
luarocks install --tree=lua_modules md5
luarocks install --tree=lua_modules fakeredis
+luarocks install --tree=lua_modules net-url
diff --git a/api-gateway-config/tests/run-tests.sh b/api-gateway-config/tests/run-tests.sh
index 1eaa926..64591ee 100755
--- a/api-gateway-config/tests/run-tests.sh
+++ b/api-gateway-config/tests/run-tests.sh
@@ -1,4 +1,4 @@
#!/bin/sh
# Run unit tests
-busted --output=TAP --helper=set_paths spec/test.lua
\ No newline at end of file
+busted --output=TAP --helper=set_paths --pattern=.lua scripts
\ No newline at end of file
diff --git a/api-gateway-config/tests/scripts/lua/lib/logger.lua b/api-gateway-config/tests/scripts/lua/lib/logger.lua
new file mode 100644
index 0000000..0070bf9
--- /dev/null
+++ b/api-gateway-config/tests/scripts/lua/lib/logger.lua
@@ -0,0 +1,36 @@
+-- Copyright (c) 2016 IBM. All rights reserved.
+--
+-- 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 fakengx = require 'fakengx'
+local logger = require 'lib/logger'
+
+describe('Testing logger module', function()
+ before_each(function()
+ _G.ngx = fakengx.new()
+ end)
+
+ 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)
\ No newline at end of file
diff --git a/api-gateway-config/tests/scripts/lua/lib/redis.lua b/api-gateway-config/tests/scripts/lua/lib/redis.lua
new file mode 100644
index 0000000..b0f2f96
--- /dev/null
+++ b/api-gateway-config/tests/scripts/lua/lib/redis.lua
@@ -0,0 +1,145 @@
+-- Copyright (c) 2016 IBM. All rights reserved.
+--
+-- 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 fakengx = require 'fakengx'
+local fakeredis = require 'fakeredis'
+local cjson = require 'cjson'
+local redis = require 'lib/redis'
+
+describe('Testing Redis module', function()
+ before_each(function()
+ _G.ngx = fakengx.new()
+ red = fakeredis.new()
+ operations = {
+ GET = {
+ backendUrl = 'https://httpbin.org/get',
+ backendMethod = 'GET'
+ }
+ }
+ end)
+
+ it('should generate resource object to store in redis', function()
+ -- Resource object with no policies or security
+ local apiId = 12345
+ local resourceObj = {
+ apiId = apiId,
+ operations = operations
+ }
+ local expected = resourceObj
+ local generated = cjson.decode(redis.generateResourceObj(operations, apiId))
+ assert.are.same(expected, generated)
+
+ -- Resource object with policy added
+ local policyList = [[
+ [{
+ "type":"rateLimit",
+ "value":[{
+ "interval":60,
+ "rate":100,
+ "scope":"api",
+ "subscription": "true"
+ }]
+ }]
+ ]]
+ resourceObj.operations.GET.policies = cjson.decode(policyList)
+ expected = resourceObj
+ generated = cjson.decode(redis.generateResourceObj(operations, apiId))
+ assert.are.same(expected, generated)
+
+ -- Resource object with security added
+ local securityObj = [[
+ {
+ "type":"apiKey",
+ "scope":"api",
+ "header":"myheader"
+ }
+ ]]
+ resourceObj.operations.GET.security = cjson.decode(securityObj)
+ expected = resourceObj
+ generated = cjson.decode(redis.generateResourceObj(operations, apiId))
+ assert.are.same(expected, generated)
+
+ -- Resource object with multiple operations
+ resourceObj.operations.PUT = {
+ backendUrl = 'https://httpbin.org/get',
+ backendMethod = 'PUT',
+ security = {}
+ }
+ expected = resourceObj
+ generated = cjson.decode(redis.generateResourceObj(operations, apiId))
+ 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(operations, 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(operations, 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(operations, 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)
\ No newline at end of file
diff --git a/api-gateway-config/tests/scripts/lua/lib/request.lua b/api-gateway-config/tests/scripts/lua/lib/request.lua
new file mode 100644
index 0000000..c7aadd4
--- /dev/null
+++ b/api-gateway-config/tests/scripts/lua/lib/request.lua
@@ -0,0 +1,44 @@
+-- Copyright (c) 2016 IBM. All rights reserved.
+--
+-- 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 fakengx = require 'fakengx'
+local request = require 'lib/request'
+
+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'
+ request.err(code, msg)
+ assert.are.equal('Error: ' .. msg .. '\n', ngx._body)
+ assert.are.equal(code, ngx._exit)
+ end)
+
+ it('should return correct success response', function()
+ local code = 200
+ local msg ='Success!\n'
+ request.success(code, msg)
+ assert.are.equal(msg .. '\n', ngx._body)
+ assert.are.equal(code, ngx._exit)
+ end)
+end)
\ No newline at end of file
diff --git a/api-gateway-config/tests/scripts/lua/lib/utils.lua b/api-gateway-config/tests/scripts/lua/lib/utils.lua
new file mode 100644
index 0000000..2e1bf13
--- /dev/null
+++ b/api-gateway-config/tests/scripts/lua/lib/utils.lua
@@ -0,0 +1,62 @@
+-- Copyright (c) 2016 IBM. All rights reserved.
+--
+-- 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 fakengx = require 'fakengx'
+local utils = require 'lib/utils'
+
+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 a simple lua table', function()
+ local expected = {
+ test = true
+ }
+ local serialized = utils.serializeTable(expected)
+ loadstring('generated = ' .. serialized)() -- convert serialzed string to lua table
+ assert.are.same(expected, generated)
+ end)
+
+ it('should serialize an empty table', function()
+ local expected = {}
+ local serialized = utils.serializeTable(expected)
+ loadstring('generated = ' .. serialized)() -- convert serialzed string to lua table
+ assert.are.same(expected, generated)
+ end)
+
+ it('should serialize complex nested table', function()
+ local expected = {
+ test1 = {
+ nested = 'value'
+ },
+ test2 = true
+ }
+ local serialized = utils.serializeTable(expected)
+ loadstring('generated = ' .. serialized)() -- convert serialzed string to lua table
+ assert.are.same(expected, generated)
+ end)
+end)
\ No newline at end of file
diff --git a/api-gateway-config/tests/scripts/lua/routing.lua b/api-gateway-config/tests/scripts/lua/routing.lua
new file mode 100644
index 0000000..4ca04d4
--- /dev/null
+++ b/api-gateway-config/tests/scripts/lua/routing.lua
@@ -0,0 +1,122 @@
+-- Copyright (c) 2016 IBM. All rights reserved.
+--
+-- 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 fakengx = require 'fakengx'
+local fakeredis = require 'fakeredis'
+local redis = require 'lib/redis'
+local routing = require 'routing'
+
+describe('Testing routing module', function()
+ before_each(function()
+ _G.ngx = fakengx.new()
+ ngx.var.gatewayPath = ''
+ red = fakeredis.new()
+ operations = {
+ GET = {
+ backendUrl = 'https://httpbin.org',
+ backendMethod = 'GET'
+ }
+ }
+ keys = {'resources:guest:bp1/test/hello', 'resources:guest:bp2/hello', 'resources:guest:bp3/testing/test/hi',
+ 'resources:guest:bp4/another/hello', 'resources:guest:nobp', 'resources:guest:noresource/'}
+ local field = 'resources'
+ for _, key in pairs(keys) do
+ red:hset(key, field, redis.generateResourceObj(operations, nil))
+ end
+ end)
+
+ it('should find the correct redis key', function()
+ local expected = 'resources:guest:bp1/test/hello'
+ local tenant = 'guest'
+ local path = 'bp1/test/hello'
+ local actual = routing.findRedisKey(keys, tenant, path)
+ assert.are.same(expected, actual)
+ expected = 'bp1/test/hello'
+ actual = ngx.var.gatewayPath
+ assert.are.same(expected, actual)
+ end)
+
+ it('should return nil if redis key doesn\'t exist', function()
+ local expected = nil
+ local tenant = 'guest'
+ local path = 'bp1/bad/path'
+ local actual = routing.findRedisKey(keys, tenant, path)
+ assert.are.same(expected, actual)
+ end)
+
+ it('should find correct key when basePath is "/"', function()
+ local expected = 'resources:guest:nobp'
+ local tenant = 'guest'
+ local path = 'nobp'
+ local actual = routing.findRedisKey(keys, tenant, path)
+ assert.are.same(expected, actual)
+ expected = 'nobp'
+ actual = ngx.var.gatewayPath
+ assert.are.same(expected, actual)
+ end)
+
+ it('should find correct key when resourcePath is "/"', function()
+ local expected = 'resources:guest:noresource/'
+ local tenant = 'guest'
+ local path = 'noresource/'
+ local actual = routing.findRedisKey(keys, tenant, path)
+ assert.are.same(expected, actual)
+ expected = 'noresource/'
+ actual = ngx.var.gatewayPath
+ assert.are.same(expected, actual)
+ end)
+
+ it('should match the correct path parameters', function()
+ local key = 'resources:guest:bp5/{pathVar}'
+ local redisKey = 'resources:guest:bp5/test'
+ local actual = routing.pathParamMatch(key, redisKey)
+ local expected = true
+ assert.are.same(expected, actual)
+ expected = 'test'
+ actual = ngx.ctx.pathVar
+ assert.are.same(expected, actual)
+ end)
+
+ it('should return false if there isn\'t a path parameter match', function()
+ local key = 'resources:guest:bp6/{pathVar}/hey'
+ local redisKey = 'resources:guest:bp6/test/hi'
+ local actual = routing.pathParamMatch(key, redisKey)
+ local expected = false
+ assert.are.same(expected, actual)
+ expected = nil
+ actual = ngx.ctx.pathVar
+ assert.are.same(expected, actual)
+ end)
+
+ it('should match multiple path parameters', function()
+ local key = 'resources:guest:base/{var1}/hello/{var2}'
+ local redisKey = 'resources:guest:base/test/hello/testing'
+ local actual = routing.pathParamMatch(key, redisKey)
+ local expected = true
+ assert.are.same(expected, actual)
+ expected = 'test'
+ actual = ngx.ctx.var1
+ assert.are.same(expected, actual)
+ expected = 'testing'
+ actual = ngx.ctx.var2
+ assert.are.same(expected, actual)
+ end)
+
+end)
\ No newline at end of file
diff --git a/api-gateway-config/tests/set_paths.lua b/api-gateway-config/tests/set_paths.lua
index bdc8afb..c1f2ef8 100644
--- a/api-gateway-config/tests/set_paths.lua
+++ b/api-gateway-config/tests/set_paths.lua
@@ -6,6 +6,7 @@
package.path = package.path ..
';' .. pwd .. '/lua_modules/share/lua/' .. version .. '/?.lua' ..
';' .. pwd .. '/lua_modules/share/lua/' .. version .. '/?/init.lua' ..
+ ';' .. pwd .. '/lua_modules/share/lua/' .. version .. '/net/?.lua' ..
';' .. pwd .. '/../scripts/lua/?.lua'
package.cpath = package.cpath ..
';' .. pwd .. '/lua_modules/lib/lua/' .. version .. '/?.so'
\ No newline at end of file
diff --git a/api-gateway-config/tests/spec/test.lua b/api-gateway-config/tests/spec/test.lua
deleted file mode 100644
index 5547e61..0000000
--- a/api-gateway-config/tests/spec/test.lua
+++ /dev/null
@@ -1,243 +0,0 @@
--- Copyright (c) 2016 IBM. All rights reserved.
---
--- 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.
-
--- Unit tests for the apigateway using the busted framework.
--- @author Alex Song (songs)
-
-local fakengx = require 'fakengx'
-local fakeredis = require 'fakeredis'
-local cjson = require 'cjson'
-local request = require 'lib/request'
-local utils = require 'lib/utils'
-local logger = require 'lib/logger'
-local redis = require 'lib/redis'
-local mapping = require 'policies/mapping'
-
-
-------------------------------------
----- 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'
- request.err(code, msg)
- assert.are.equal('Error: ' .. msg .. '\n', ngx._body)
- assert.are.equal(code, ngx._exit)
- end)
-
- it('should return correct success response', function()
- local code = 200
- local msg ='Success!\n'
- request.success(code, msg)
- assert.are.equal(msg .. '\n', ngx._body)
- assert.are.equal(code, ngx._exit)
- 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)
-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 apiId = 12345
- local operations = {
- GET = {
- backendUrl = 'https://httpbin.org/get',
- backendMethod = 'GET'
- }
- }
- local resourceObj = {
- apiId = apiId,
- operations = operations
- }
- local expected = resourceObj
- local generated = cjson.decode(redis.generateResourceObj(operations, apiId))
- assert.are.same(expected, generated)
-
- -- Resource object with policy added
- local policyList = [[
- [{
- "type":"rateLimit",
- "value":[{
- "interval":60,
- "rate":100,
- "scope":"api",
- "subscription": "true"
- }]
- }]
- ]]
- resourceObj.operations.GET.policies = cjson.decode(policyList)
- expected = resourceObj
- generated = cjson.decode(redis.generateResourceObj(operations, apiId))
- assert.are.same(expected, generated)
-
- -- Resource object with security added
- local securityObj = [[
- {
- "type":"apiKey",
- "scope":"api",
- "header":"myheader"
- }
- ]]
- resourceObj.operations.GET.security = cjson.decode(securityObj)
- expected = resourceObj
- generated = cjson.decode(redis.generateResourceObj(operations, apiId))
- assert.are.same(expected, generated)
-
- -- Resource object with multiple operations
- resourceObj.operations.PUT = {
- backendUrl = 'https://httpbin.org/get',
- backendMethod = 'PUT',
- security = {}
- }
- expected = resourceObj
- generated = cjson.decode(redis.generateResourceObj(operations, apiId))
- 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