local admin_api = require "spec.fixtures.admin_api"
local helpers = require "spec.helpers"
local cjson   = require "cjson"
local pl_file = require "pl.file"
local http_mock = require "spec.helpers.http_mock"

local fmt = string.format


local function count_log_lines(pattern)
  local cfg = helpers.test_conf
  local logs = pl_file.read(cfg.prefix .. "/" .. cfg.proxy_error_log)
  local _, count = logs:gsub(pattern, "")
  return count
end


for _, strategy in helpers.each_strategy() do
describe("Plugin: request-transformer(access) [#" .. strategy .. "]", function()
  local client, mock_server
  local MOCK_PORT

  lazy_setup(function()
    MOCK_PORT = helpers.get_available_port()

    local bp = helpers.get_db_utils(strategy, {
      "routes",
      "services",
      "plugins",
    })

    local route1 = bp.routes:insert({
      hosts = { "test1.test" }
    })
    local route2 = bp.routes:insert({
      hosts = { "test2.test" },
      preserve_host = true,
    })
    local route3 = bp.routes:insert({
      hosts = { "test3.test" }
    })
    local route4 = bp.routes:insert({
      hosts = { "test4.test" }
    })
    local route5 = bp.routes:insert({
      hosts = { "test5.test" }
    })
    local route6 = bp.routes:insert({
      hosts = { "test6.test" }
    })
    local route7 = bp.routes:insert({
      hosts = { "test7.test" }
    })
    local route8 = bp.routes:insert({
      hosts = { "test8.test" }
    })
    local route9 = bp.routes:insert({
      hosts = { "test9.test" }
    })
    local route10 = bp.routes:insert({
      hosts = { "test10.test" },
      paths = { "~/requests/user1/(?P<user1>\\w+)/user2/(?P<user2>\\S+)" },
      strip_path = false
    })
    local route11 = bp.routes:insert({
      hosts = { "test11.test" },
      paths = { "~/requests/user1/(?P<user1>\\w+)/user2/(?P<user2>\\S+)" }
    })
    local route12 = bp.routes:insert({
      hosts = { "test12.test" },
      paths = { "/requests/" },
      strip_path = false
    })
    local route13 = bp.routes:insert({
      hosts = { "test13.test" },
      paths = { "~/requests/user1/(?P<user1>\\w+)/user2/(?P<user2>\\S+)" }
    })
    local route14 = bp.routes:insert({
      hosts = { "test14.test" },
      paths = { "~/user1/(?P<user1>\\w+)/user2/(?P<user2>\\S+)" }
    })
    local route15 = bp.routes:insert({
      hosts = { "test15.test" },
      paths = { "~/requests/user1/(?<user1>\\w+)/user2/(?<user2>\\S+)" },
      strip_path = false
    })
    local route16 = bp.routes:insert({
      hosts = { "test16.test" },
      paths = { "~/requests/user1/(?<user1>\\w+)/user2/(?<user2>\\S+)" },
      strip_path = false
    })
    local route17 = bp.routes:insert({
      hosts = { "test17.test" },
      paths = { "~/requests/user1/(?<user1>\\w+)/user2/(?<user2>\\S+)" },
      strip_path = false
    })
    local route18 = bp.routes:insert({
      hosts = { "test18.test" },
      paths = { "~/requests/user1/(?<user1>\\w+)/user2/(?<user2>\\S+)" },
      strip_path = false
    })
    local route19 = bp.routes:insert({
      hosts = { "test19.test" },
      paths = { "~/requests/user1/(?<user1>\\w+)/user2/(?<user2>\\S+)" },
      strip_path = false
    })
    local route20 = bp.routes:insert({
      hosts = { "test20.test" }
    })
    local route21 = bp.routes:insert({
      hosts = { "test21.test" }
    })
    local route22 = bp.routes:insert({
      hosts = { "test22.test" }
    })
    local route23 = bp.routes:insert({
      hosts = { "test23.test" },
      paths = { "/request" }
    })
    local route24 = bp.routes:insert({
      hosts = { "test24.test" }
    })
    local route25 = bp.routes:insert({
      hosts = { "test25.test" }
    })
    local route26 = bp.routes:insert({
      hosts = { "test26.test" }
    })

    local route27 = bp.routes:insert({
      hosts = { "test27.test" }
    })

    local route28 = bp.routes:insert({
      hosts = { "test28.test" }
    })

    local route29 = bp.routes:insert({
      hosts = { "test29.test" },
      paths = { "~/(gw/)?api/(?<subpath>htest)$" },
      strip_path = false,
    })

    bp.plugins:insert {
      route = { id = route1.id },
      name = "request-transformer",
      config = {
        add = {
          headers = {"h1:v1", "h2:value:2"}, -- payload containing a colon
          querystring = {"q1:v1"},
          body = {"p1:v1"}
        }
      }
    }
    bp.plugins:insert {
      route = { id = route2.id },
      name = "request-transformer",
      config = {
        add = {
          headers = {"host:mark"}
        }
      }
    }
    bp.plugins:insert {
      route = { id = route3.id },
      name = "request-transformer",
      config = {
        add = {
          headers = {"x-added:a1", "x-added2:b1", "x-added3:c2"},
          querystring = {"query-added:newvalue", "p1:anything:1"},   -- payload containing a colon
          body = {"newformparam:newvalue"}
        },
        remove = {
          headers = {"x-to-remove"},
          querystring = {"toremovequery"}
        },
        append = {
          headers = {"x-added:a2", "x-added:a3"},
          querystring = {"p1:a2", "p2:b1"}
        },
        replace = {
          headers = {"x-to-replace:false"},
          querystring = {"toreplacequery:no"}
        }
      }
    }
    bp.plugins:insert {
      route = { id = route4.id },
      name = "request-transformer",
      config = {
        remove = {
          headers = {"x-to-remove"},
          querystring = {"q1"},
          body = {"toremoveform"}
        }
      }
    }
    bp.plugins:insert {
      route = { id = route5.id },
      name = "request-transformer",
      config = {
        replace = {
          headers = {"h1:v1"},
          querystring = {"q1:v1"},
          body = {"p1:v1"}
        }
      }
    }
    bp.plugins:insert {
      route = { id = route6.id },
      name = "request-transformer",
      config = {
        append = {
          headers = {"h1:v1", "h1:v2", "h2:v1",},
          querystring = {"q1:v1", "q1:v2", "q2:v1"},
          body = {"p1:v1", "p1:v2", "p2:value:1"}     -- payload containing a colon
        }
      }
    }
    bp.plugins:insert {
      route = { id = route7.id },
      name = "request-transformer",
      config = {
        http_method = "POST"
      }
    }
    bp.plugins:insert {
      route = { id = route8.id },
      name = "request-transformer",
      config = {
        http_method = "GET"
      }
    }

    bp.plugins:insert {
      route = { id = route9.id },
      name = "request-transformer",
      config = {
        rename = {
          headers = {"x-to-rename:x-is-renamed"},
          querystring = {"originalparam:renamedparam"},
          body = {"originalparam:renamedparam"}
        }
      }
    }

    bp.plugins:insert {
      route = { id = route10.id },
      name = "request-transformer",
      config = {
        add = {
          querystring = {"uri_param1:$(uri_captures.user1)", "uri_param2[some_index][1]:$(uri_captures.user2)"},
        }
      }
    }

    bp.plugins:insert {
      route = { id = route11.id },
      name = "request-transformer",
      config = {
        replace = {
          uri = "/requests/user2/$(uri_captures.user2)/user1/$(uri_captures.user1)",
        }
      }
    }

    bp.plugins:insert {
      route = { id = route12.id },
      name = "request-transformer",
      config = {
        add = {
          querystring = {"uri_param1:$(uri_captures.user1 or 'default1')", "uri_param2:$(uri_captures.user2 or 'default2')"},
        }
      }
    }

    bp.plugins:insert {
      route = { id = route13.id },
      name = "request-transformer",
      config = {
        replace = {
          uri = "/requests/user2/$(10 * uri_captures.user1)",
        }
      }
    }

    bp.plugins:insert {
      route = { id = route14.id },
      name = "request-transformer",
      config = {
        replace = {
          uri = "/requests$(uri_captures[0])",
        }
      }
    }

    bp.plugins:insert {
      route = { id = route15.id },
      name = "request-transformer",
      config = {
        add = {
          querystring = {"uri_param1:$(uri_captures.user1)", "uri_param2:$(headers.host)"},
          headers = {"x-test-header:$(query_params.q1)"}
        },
        remove = {
          querystring = {"q1"},
        }
      }
    }

    bp.plugins:insert {
      route = { id = route16.id },
      name = "request-transformer",
      config = {
        replace = {
          querystring = {"q2:$(headers['x-remove-header'])"},
        },
        add = {
          querystring = {"q1:$(uri_captures.user1)"},
          headers = {"x-test-header:$(headers['x-remove-header'])"}
        },
        remove = {
          headers = {"x-remove-header"}
        },
      }
    }

    bp.plugins:insert {
      route = { id = route17.id },
      name = "request-transformer",
      config = {
        replace = {
          querystring = {"q2:$(headers['x-replace-header'])"},
          headers = {"x-replace-header:the new value"}
        },
        add = {
          querystring = {"q1:$(uri_captures.user1)"},
          headers = {"x-test-header:$(headers['x-replace-header'])"}
        }
      }
    }

    bp.plugins:insert {
      route = { id = route18.id },
      name = "request-transformer",
      config = {
        add = {
          querystring = {[[q1:$('$(uri_captures.user1)')]]},
        }
      }
    }

    bp.plugins:insert {
      route = { id = route19.id },
      name = "request-transformer",
      config = {
        add = {
          -- will trigger a runtime error
          querystring = {[[q1:$(ERROR())]]},
        }
      }
    }

    bp.plugins:insert {
      route = { id = route20.id },
      name = "request-transformer",
      config = {
        http_method = "POST",
        add = {
          headers = {
            "Content-Type:application/json"
          },
          body = { "body:somecontent" }
        },
      }
    }

    do
      -- 2 plugins:
      -- pre-function: plugin to inject a shared value in the kong.ctx.shared table
      -- transformer: pick up the injected value and add to the query string
      bp.plugins:insert {
        route = { id = route21.id },
        name = "pre-function",
        config = {
          access = {
            [[
              kong.ctx.shared.my_version = "1.2.3"
            ]]
          },
        }
      }
      bp.plugins:insert {
        route = { id = route21.id },
        name = "request-transformer",
        config = {
          add = {
            querystring = {"shared_param1:$(shared.my_version)"},
          }
        }
      }
    end
    bp.plugins:insert {
      route = { id = route22.id },
      name = "request-transformer",
      config = {
        http_method = "POST",
        remove = {
          headers = { "Authorization" },
        },
        add = {
          headers = { "Authorization:Basic test" },
        },
      },
    }

    bp.plugins:insert {
      route = { id = route23.id },
      name = "request-transformer",
      config = {}
    }

    bp.plugins:insert {
      route = { id = route24.id },
      name = "request-transformer",
      config = {
        add = {
          headers = { "x-user-agent:$(foo(headers[\"User-Agent\"]) == \"table\" and headers[\"User-Agent\"][1] or headers[\"User-Agent\"])", },
        },
      }
    }

    bp.plugins:insert {
      route = { id = route25.id },
      name = "request-transformer",
      config = {
        add = {
          headers = { "X-Foo-Transformed:$(type(headers[\"X-Foo\"]) == \"table\" and headers[\"X-Foo\"][1] .. \"-first\" or headers[\"X-Foo\"])", },
        },
      }
    }

    bp.plugins:insert {
      route = { id = route26.id },
      name = "request-transformer",
      config = {
        add = {
          headers = {"X-aDDed:a1", "x-aDDed2:b1", "x-Added3:c2"},
        },
        remove = {
          headers = {"X-To-Remove"},
        },
        append = {
          headers = {"X-aDDed:a2", "X-aDDed:a3"},
        },
        replace = {
          headers = {"x-to-Replace:false"},
        }
      }
    }

    bp.plugins:insert {
      route = { id = route27.id },
      name = "request-transformer",
      config = {
        replace = {
          uri = "/requests/tést",
        }
      }
    }

    -- transformer attempts to inject a value in ngx.ctx.shared, but that will result in an invalid template
    -- which provokes a failure
    bp.plugins:insert {
      route = { id = route28.id },
      name = "request-transformer",
      config = {
        add = {
          headers = { "X-Write-Attempt:$(shared.written = true)" },
        }
      }
    }

    bp.plugins:insert {
      route = { id = route29.id },
      name = "request-transformer",
      config = {
        replace = {
          uri = "/api/v2/$(uri_captures[\"subpath\"])",
        }
      }
    }

    do -- rename case tests
      -- as assert.request(r) does not support case-sensitive header checks
      -- we need to use a mock server
      mock_server = http_mock.new(MOCK_PORT)
      mock_server:start()
      local mock_service = bp.services:insert {
        url = "http://localhost:" .. MOCK_PORT
      }
      local route = bp.routes:insert {
        hosts = { "rename.mock" },
        service = mock_service,
      }
      bp.plugins:insert {
        route = { id = route.id },
        name = "request-transformer",
        config = {
          rename = {
            headers = { "rename:Rename", "identical:identical" },
            querystring = { "inexist:exist" },
          }
        }
      }
    end

    assert(helpers.start_kong({
      database = strategy,
      plugins = "bundled, request-transformer",
      nginx_conf = "spec/fixtures/custom_nginx.template",
    }))
  end)

  lazy_teardown(function()
    mock_server:stop()
    helpers.stop_kong()
  end)

  before_each(function()
    client = helpers.proxy_client()
  end)

  after_each(function()
    if client then client:close() end
  end)

  describe("http method", function()
    it("changes the HTTP method from GET to POST", function()
      local r = assert(client:send {
        method = "GET",
        path = "/request?hello=world&name=marco",
        headers = {
          host = "test7.test"
        }
      })
      assert.response(r).has.status(200)
      local json = assert.response(r).has.jsonbody()
      assert.equal("POST", json.vars.request_method)
      assert.equal("world", json.uri_args.hello)
      assert.equal("marco", json.uri_args.name)
    end)
    it("changes the HTTP method from POST to GET", function()
      local r = assert(client:send {
        method = "POST",
        path = "/request?hello=world",
        body = {
          name = "marco"
        },
        headers = {
          ["Content-Type"] = "application/x-www-form-urlencoded",
          host = "test8.test"
        }
      })
      assert.response(r).has.status(200)
      local json = assert.response(r).has.jsonbody()
      assert.equal("GET", json.vars.request_method)
      assert.equal("world", json.uri_args.hello)
      assert.equal("marco", json.uri_args.name)
    end)
    it("changes the HTTP method from GET to POST and adds JSON body", function()
      local r = assert(client:send {
        method = "GET",
        path = "/request",
        headers = {
          host = "test20.test",
        }
      })
      assert.response(r).has.status(200)
      local json = assert.response(r).has.jsonbody()
      assert.request(r).has.jsonbody()
      assert.equal("POST", json.vars.request_method)
      assert.is_nil(json.post_data.error)
      local header_content_type = assert.request(r).has.header("Content-Type")
      assert.equals("application/json", header_content_type)
    end)
  end)
  describe("remove", function()
    it("specified header", function()
      local r = assert(client:send {
        method = "GET",
        path = "/request",
        headers = {
          host = "test4.test",
          ["x-to-remove"] = "true",
          ["x-another-header"] = "true"
        }
      })
      assert.response(r).has.status(200)
      assert.response(r).has.jsonbody()
      assert.request(r).has.no.header("x-to-remove")
      assert.request(r).has.header("x-another-header")
    end)
    it("parameters on url encoded form POST", function()
      local r = assert(client:send {
        method = "POST",
        path = "/request",
        body = {
          ["toremoveform"] = "yes",
          ["nottoremove"] = "yes"
        },
        headers = {
          ["Content-Type"] = "application/x-www-form-urlencoded",
          host = "test4.test"
        }
      })
      assert.response(r).has.status(200)
      assert.response(r).has.jsonbody()
      assert.request(r).has.no.formparam("toremoveform")
      local value = assert.request(r).has.formparam("nottoremove")
      assert.equals("yes", value)
    end)
    it("parameters from JSON body in POST", function()
      local r = assert(client:send {
        method = "POST",
        path = "/request",
        body = {
          ["toremoveform"] = "yes",
          ["nottoremove"] = "yes"
        },
        headers = {
          host = "test4.test",
          ["content-type"] = "application/json"
        }
      })
      assert.response(r).has.status(200)
      assert.response(r).has.jsonbody()
      local json = assert.request(r).has.jsonbody()
      assert.is_nil(json.params["toremoveform"])
      assert.equals("yes", json.params["nottoremove"])
    end)
    it("does not fail if JSON body is malformed in POST", function()
      local r = assert(client:send {
        method = "POST",
        path = "/request",
        body = "malformed json body",
        headers = {
          host = "test4.test",
          ["content-type"] = "application/json"
        }
      })
      assert.response(r).has.status(200)
      local json = assert.response(r).has.jsonbody()
      assert.equals("json (error)", json.post_data.kind)
      assert.not_nil(json.post_data.error)
    end)
    it("does not fail if body is empty and content type is application/json in POST", function()
      local r = assert(client:send {
        method = "POST",
        path = "/request",
        body = {},
        headers = {
          host = "test4.test",
          ["content-type"] = "application/json"
        }
      })
      assert.response(r).has.status(200)
      local json = assert.response(r).has.jsonbody()
      assert.equals('{}', json.post_data.text)
      assert.equals("2", json.headers["content-length"])
    end)
    it("does not fail if body is empty in POST", function()
      local r = assert(client:send {
        method = "POST",
        path = "/request",
        body = "",
        headers = {
          host = "test4.test"
        }
      })
      assert.response(r).has.status(200)
      local json = assert.response(r).has.jsonbody()
      assert.same(cjson.null, json.post_data.params)
      assert.equal('', json.post_data.text)
      local value = assert.request(r).has.header("content-length")
      assert.equal("0", value)
    end)
    it("parameters on multipart POST", function()
      local r = assert(client:send {
        method = "POST",
        path = "/request",
        body = {
          ["toremoveform"] = "yes",
          ["nottoremove"] = "yes"
        },
        headers = {
          ["Content-Type"] = "multipart/form-data",
          host = "test4.test"
        }
      })
      assert.response(r).has.status(200)
      assert.response(r).has.jsonbody()
      assert.request(r).has.no.formparam("toremoveform")
      local value = assert.request(r).has.formparam("nottoremove")
      assert.equals("yes", value)
    end)
    it("args on GET if it exist", function()
      local r = assert( client:send {
        method = "GET",
        path = "/request",
        query = {
          q1 = "v1",
          q2 = "v2",
        },
        body = {
          hello = "world"
        },
        headers = {
          ["Content-Type"] = "application/x-www-form-urlencoded",
          host = "test4.test"
        }
      })
      assert.response(r).has.status(200)
      assert.response(r).has.jsonbody()
      assert.request(r).has.no.queryparam("q1")
      local value = assert.request(r).has.queryparam("q2")
      assert.equals("v2", value)
    end)
    it("preserves empty json array", function()
      local r = assert(client:send {
        method = "POST",
        path = "/request",
        body = [[{"emptyarray":[]}]],
        headers = {
          host = "test4.test",
          ["content-type"] = "application/json"
        }
      })
      assert.response(r).has.status(200)
      assert.response(r).has.jsonbody()
      local json = assert.request(r).has.jsonbody()
      assert.equals("{\"emptyarray\":[]}", json.data)
    end)
  end)

  describe("rename", function()
    it("specified header", function()
      local r = assert(client:send {
        method = "GET",
        path = "/request",
        headers = {
          host = "test9.test",
          ["x-to-rename"] = "true",
          ["x-another-header"] = "true"
        }
      })
      assert.response(r).has.status(200)
      assert.response(r).has.jsonbody()
      assert.request(r).has.no.header("x-to-rename")
      assert.request(r).has.header("x-is-renamed")
      assert.request(r).has.header("x-another-header")
    end)
    it("does not add as new header if header does not exist", function()
      local r = assert( client:send {
        method = "GET",
        path = "/request",
        body = {},
        headers = {
          host = "test9.test",
          ["x-a-header"] = "true",
        }
      })
      assert.response(r).has.status(200)
      assert.response(r).has.jsonbody()
      assert.request(r).has.no.header("renamedparam")
      local h_a_header = assert.request(r).has.header("x-a-header")
      assert.equals("true", h_a_header)
    end)
    it("override value if target header already exist: #%s", function ()
      local r = assert(client:send {
        method = "GET",
        path = "/request",
        headers = {
          host = "test9.test",
          ["x-to-rename"] = "new-result",
          ["x-is-renamed"] = "old-result",
        }
      })
      assert.response(r).has.status(200)
      assert.response(r).has.jsonbody()
      assert.request(r).has.no.header("x-to-rename")
      local h_is_renamed = assert.request(r).has.header("x-is-renamed")
      assert.equals("new-result", h_is_renamed)
    end)
    for _, seq in ipairs({ 1, 2, 3, 4, 5, 6}) do
      it(fmt("override value if target header already exist with different format: #%s", seq), function ()
        local r = assert(client:send {
          method = "GET",
          path = "/request",
          headers = {
            host = "test9.test",
            ["x-to-rename"] = "new-result",
            ["X-Is-Renamed"] = "old-result",
          }
        })
        assert.response(r).has.status(200)
        assert.response(r).has.jsonbody()
        assert.request(r).has.no.header("x-to-rename")
        local h_is_renamed = assert.request(r).has.header("x-is-renamed")
        assert.equals("new-result", h_is_renamed)
      end)
    end
    it("specified parameters in url encoded body on POST", function()
      local r = assert(client:send {
        method = "POST",
        path = "/request",
        body = {
          originalparam = "yes",
        },
        headers = {
          ["Content-Type"] = "application/x-www-form-urlencoded",
          host = "test9.test"
        }
      })
      assert.response(r).has.status(200)
      assert.response(r).has.jsonbody()
      assert.request(r).has.no.formparam("originalparam")
      local value = assert.request(r).has.formparam("renamedparam")
      assert.equals("yes", value)
    end)
    it("does not add as new parameter in url encoded body if parameter does not exist on POST", function()
      local r = assert(client:send {
        method = "POST",
        path = "/request",
        body = {
          ["x-a-header"] = "true",
        },
        headers = {
          ["Content-Type"] = "application/x-www-form-urlencoded",
          host = "test9.test"
        }
      })
      assert.response(r).has.status(200)
      assert.request(r).has.no.formparam("renamedparam")
      local value = assert.request(r).has.formparam("x-a-header")
      assert.equals("true", value)
    end)
    it("parameters from JSON body in POST", function()
      local r = assert(client:send {
        method = "POST",
        path = "/request",
        body = {
          ["originalparam"] = "yes",
          ["nottorename"] = "yes"
        },
        headers = {
          host = "test9.test",
          ["content-type"] = "application/json"
        }
      })
      assert.response(r).has.status(200)
      assert.response(r).has.jsonbody()
      local json = assert.request(r).has.jsonbody()
      assert.is_nil(json.params["originalparam"])
      assert.is_not_nil(json.params["renamedparam"])
      assert.equals("yes", json.params["renamedparam"])
      assert.equals("yes", json.params["nottorename"])
    end)
    it("does not fail if JSON body is malformed in POST", function()
      local r = assert(client:send {
        method = "POST",
        path = "/request",
        body = "malformed json body",
        headers = {
          host = "test9.test",
          ["content-type"] = "application/json"
        }
      })
      assert.response(r).has.status(200)
      local json = assert.response(r).has.jsonbody()
      assert.equals("json (error)", json.post_data.kind)
      assert.is_not_nil(json.post_data.error)
    end)
    it("parameters on multipart POST", function()
      local r = assert(client:send {
        method = "POST",
        path = "/request",
        body = {
          ["originalparam"] = "yes",
          ["nottorename"] = "yes"
        },
        headers = {
          ["Content-Type"] = "multipart/form-data",
          host = "test9.test"
        }
      })
      assert.response(r).has.status(200)
      assert.response(r).has.jsonbody()
      assert.request(r).has.no.formparam("originalparam")
      local value = assert.request(r).has.formparam("renamedparam")
      assert.equals("yes", value)
      local value2 = assert.request(r).has.formparam("nottorename")
      assert.equals("yes", value2)
    end)
    it("args on GET if it exists", function()
      local r = assert( client:send {
        method = "GET",
        path = "/request",
        query = {
          originalparam = "true",
          nottorename = "true",
        },
        headers = {
          ["Content-Type"] = "application/x-www-form-urlencoded",
          host = "test9.test"
        }
      })
      assert.response(r).has.status(200)
      assert.response(r).has.jsonbody()
      assert.request(r).has.no.queryparam("originalparam")
      local value1 = assert.request(r).has.queryparam("renamedparam")
      assert.equals("true", value1)
      local value2 = assert.request(r).has.queryparam("nottorename")
      assert.equals("true", value2)
    end)
    it("preserves empty json array", function()
      local r = assert(client:send {
        method = "POST",
        path = "/request",
        body = [[{"emptyarray":[]}]],
        headers = {
          host = "test9.test",
          ["content-type"] = "application/json"
        }
      })
      assert.response(r).has.status(200)
      assert.response(r).has.jsonbody()
      local json = assert.request(r).has.jsonbody()
      assert.equals("{\"emptyarray\":[]}", json.data)
    end)
    it("rename correctly when only changing capitalization", function()
      local r = assert(client:send {
        method = "GET",
        path = "/request",
        headers = {
          host = "rename.mock",
          ["rename"] = "true",
        }
      })
      assert.response(r).has.status(200)
      local ret = mock_server:get_request()

      assert.equals("true", ret.headers["Rename"])
      assert.is_nil(ret.headers["rename"])
    end)
    -- but should we override with a value?
    it("does not override existing value with nil", function()
      local r = assert(client:send {
        method = "GET",
        path = "/request?exist=true",
        headers = {
          host = "rename.mock",
        }
      })
      assert.response(r).has.status(200)
      local ret = mock_server:get_request()

      assert.equals("/request?exist=true", ret.uri)
    end)
    it("does not remove when renaming to the identical name", function()
      local r = assert(client:send {
        method = "GET",
        path = "/request",
        headers = {
          host = "rename.mock",
          ["identical"] = "true",
        }
      })
      assert.response(r).has.status(200)
      local ret = mock_server:get_request()

      assert.equals("true", ret.headers["identical"])
    end)
  end)

  describe("replace", function()
    it("specified header if it exist", function()
      local r = assert( client:send {
        method = "GET",
        path = "/request",
        body = {},
        headers = {
          host = "test5.test",
          h1 = "V",
          h2 = "v2",
        }
      })
      assert.response(r).has.status(200)
      assert.response(r).has.jsonbody()
      local h_h1 = assert.request(r).has.header("h1")
      assert.equals("v1", h_h1)
      local h_h2 = assert.request(r).has.header("h2")
      assert.equals("v2", h_h2)
    end)
    it("does not add as new header if header does not exist", function()
      local r = assert( client:send {
        method = "GET",
        path = "/request",
        body = {},
        headers = {
          host = "test5.test",
          h2 = "v2",
        }
      })
      assert.response(r).has.status(200)
      assert.response(r).has.jsonbody()
      assert.request(r).has.no.header("h1")
      local h_h2 = assert.request(r).has.header("h2")
      assert.equals("v2", h_h2)
    end)
    it("specified parameters in url encoded body on POST", function()
      local r = assert(client:send {
        method = "POST",
        path = "/request",
        body = {
          p1 = "v",
          p2 = "v1",
        },
        headers = {
          ["Content-Type"] = "application/x-www-form-urlencoded",
          host = "test5.test"
        }
      })
      assert.response(r).has.status(200)
      local value = assert.request(r).has.formparam("p1")
      assert.equals("v1", value)
      local value = assert.request(r).has.formparam("p2")
      assert.equals("v1", value)
    end)
    it("does not add as new parameter in url encoded body if parameter does not exist on POST", function()
      local r = assert(client:send {
        method = "POST",
        path = "/request",
        body = {
          p2 = "v1",
        },
        headers = {
          ["Content-Type"] = "application/x-www-form-urlencoded",
          host = "test5.test"
        }
      })
      assert.response(r).has.status(200)
      assert.request(r).has.no.formparam("p1")
      local value = assert.request(r).has.formparam("p2")
      assert.equals("v1", value)
    end)
    it("specified parameters in json body on POST", function()
      local r = assert(client:send {
        method = "POST",
        path = "/request",
        body = {
          p1 = "v",
          p2 = "v1"
        },
        headers = {
          host = "test5.test",
          ["content-type"] = "application/json"
        }
      })
      assert.response(r).has.status(200)
      local json = assert.request(r).has.jsonbody()
      assert.equals("v1", json.params.p1)
      assert.equals("v1", json.params.p2)
    end)
    it("does not fail if JSON body is malformed in POST", function()
      local r = assert(client:send {
        method = "POST",
        path = "/request",
        body = "malformed json body",
        headers = {
          host = "test5.test",
          ["content-type"] = "application/json"
        }
      })
      assert.response(r).has.status(200)
      local json = assert.response(r).has.jsonbody()
      assert.equal("json (error)", json.post_data.kind)
      assert.is_not_nil(json.post_data.error)
    end)
    it("does not add as new parameter in json if parameter does not exist on POST", function()
      local r = assert(client:send {
        method = "POST",
        path = "/request",
        body = {
          p2 = "v1",
        },
        headers = {
          host = "test5.test",
          ["content-type"] = "application/json"
        }
      })
      assert.response(r).has.status(200)
      local json = assert.request(r).has.jsonbody()
      assert.is_nil(json.params.p1)
      assert.equals("v1", json.params.p2)
    end)
    it("specified parameters on multipart POST", function()
      local r = assert(client:send {
        method = "POST",
        path = "/request",
        body = {
          p1 = "v",
          p2 = "v1",
        },
        headers = {
          ["Content-Type"] = "multipart/form-data",
          host = "test5.test"
        }
      })
      assert.response(r).has.status(200)
      assert.response(r).has.jsonbody()
      local value = assert.request(r).has.formparam("p1")
      assert.equals("v1", value)
      local value2 = assert.request(r).has.formparam("p2")
      assert.equals("v1", value2)
    end)
    it("does not add as new parameter if parameter does not exist on multipart POST", function()
      local r = assert(client:send {
        method = "POST",
        path = "/request",
        body = {
          p2 = "v1",
        },
        headers = {
          ["Content-Type"] = "multipart/form-data",
          host = "test5.test"
        }
      })
      assert.response(r).has.status(200)
      assert.response(r).has.jsonbody()

      assert.request(r).has.no.formparam("p1")

      local value = assert.request(r).has.formparam("p2")
      assert.equals("v1", value)
    end)
    it("args on POST if it exist", function()
      local r = assert(client:send {
        method = "POST",
        path = "/request",
        query = {
          q1 = "v",
          q2 = "v2",
        },
        body = {
          hello = "world"
        },
        headers = {
          ["Content-Type"] = "application/x-www-form-urlencoded",
          host = "test5.test"
        }
      })
      assert.response(r).has.status(200)
      local value = assert.request(r).has.queryparam("q1")
      assert.equals("v1", value)
      local value = assert.request(r).has.queryparam("q2")
      assert.equals("v2", value)
    end)
    it("does not add new args on POST if it does not exist", function()
      local r = assert(client:send {
        method = "POST",
        path = "/request",
        query = {
          q2 = "v2",
        },
        body = {
          hello = "world"
        },
        headers = {
          ["Content-Type"] = "application/x-www-form-urlencoded",
          host = "test5.test"
        }
      })
      assert.response(r).has.status(200)
      assert.request(r).has.no.queryparam("q1")
      local value = assert.request(r).has.queryparam("q2")
      assert.equals("v2", value)
    end)
    it("preserves empty json array", function()
      local r = assert(client:send {
        method = "POST",
        path = "/request",
        body = [[{"emptyarray":[], "p1":"v"}]],
        headers = {
          host = "test5.test",
          ["content-type"] = "application/json"
        }
      })
      assert.response(r).has.status(200)
      assert.response(r).has.jsonbody()
      local json = assert.request(r).has.jsonbody()
      assert.is_truthy(string.find(json.data, "\"emptyarray\":[]", 1, true))
    end)

    it("replaces request uri with optional capture prefix", function()
      local r = assert(client:send {
        method = "GET",
        path = "/api/htest",
        headers = {
          host = "test29.test"
        }
      })
      assert.response(r).has.status(404)
      local body = assert(assert.response(r).has.jsonbody())
      assert.equals("/api/v2/htest", body.vars.request_uri)
    end)

    it("replaces request uri with the capature prefix", function()
      local r = assert(client:send {
        method = "GET",
        path = "/gw/api/htest",
        headers = {
          host = "test29.test"
        }
      })
      assert.response(r).has.status(404)
      local body = assert(assert.response(r).has.jsonbody())
      assert.equals("/api/v2/htest", body.vars.request_uri)
    end)

    pending("escape UTF-8 characters when replacing upstream path - enable after Kong 2.4", function()
      local r = assert(client:send {
        method = "GET",
        path = "/requests/wrong_path",
        headers = {
          host = "test27.test"
        }
      })
      assert.response(r).has.status(200)
      local body = assert(assert.response(r).has.jsonbody())
      assert.equals(helpers.mock_upstream_url ..
                    "/requests/t%C3%A9st", body.url)
    end)
  end)

  describe("add", function()
    it("new headers", function()
      local r = assert(client:send {
        method = "GET",
        path = "/request",
        headers = {
          host = "test1.test"
        }
      })
      assert.response(r).has.status(200)
      assert.response(r).has.jsonbody()
      local h_h1 = assert.request(r).has.header("h1")
      assert.equals("v1", h_h1)
      local h_h2 = assert.request(r).has.header("h2")
      assert.equals("value:2", h_h2)
    end)
    it("does not change or append value if header already exists", function()
      local r = assert(client:send {
        method = "GET",
        path = "/request",
        headers = {
          h1 = "v3",
          host = "test1.test",
        }
      })
      assert.response(r).has.status(200)
      assert.response(r).has.jsonbody()
      local h_h1 = assert.request(r).has.header("h1")
      assert.equals("v3", h_h1)
      local h_h2 = assert.request(r).has.header("h2")
      assert.equals("value:2", h_h2)
    end)
    it("new parameter in url encoded body on POST", function()
      local r = assert(client:send {
        method = "POST",
        path = "/request",
        body = {
          hello = "world",
        },
        headers = {
          ["Content-Type"] = "application/x-www-form-urlencoded",
          host = "test1.test"
        }
      })
      assert.response(r).has.status(200)
      local value = assert.request(r).has.formparam("hello")
      assert.equals("world", value)
      local value = assert.request(r).has.formparam("p1")
      assert.equals("v1", value)
    end)
    it("does not change or append value to parameter in url encoded body on POST when parameter exists", function()
      local r = assert(client:send {
        method = "POST",
        path = "/request",
        body = {
          p1 = "should not change",
          hello = "world",
        },
        headers = {
          ["Content-Type"] = "application/x-www-form-urlencoded",
          host = "test1.test"
        }
      })
      assert.response(r).has.status(200)
      local value = assert.request(r).has.formparam("p1")
      assert.equals("should not change", value)
      local value = assert.request(r).has.formparam("hello")
      assert.equals("world", value)
    end)
    it("new parameter in JSON body on POST", function()
      local r = assert(client:send {
        method = "POST",
        path = "/request",
        body = {
          hello = "world",
        },
        headers = {
          ["Content-Type"] = "application/json",
          host = "test1.test"
        }
      })
      assert.response(r).has.status(200)
      local params = assert.request(r).has.jsonbody().params
      assert.equals("world", params.hello)
      assert.equals("v1", params.p1)
    end)
    it("does not change or append value to parameter in JSON on POST when parameter exists", function()
      local r = assert(client:send {
        method = "POST",
        path = "/request",
        body = {
          p1 = "this should not change",
          hello = "world",
        },
        headers = {
          ["Content-Type"] = "application/json",
          host = "test1.test"
        }
      })
      assert.response(r).has.status(200)
      local params = assert.request(r).has.jsonbody().params
      assert.equals("world", params.hello)
      assert.equals("this should not change", params.p1)
    end)
    it("does not fail if JSON body is malformed in POST", function()
      local r = assert(client:send {
        method = "POST",
        path = "/request",
        body = "malformed json body",
        headers = {
          host = "test1.test",
          ["content-type"] = "application/json"
        }
      })
      assert.response(r).has.status(200)
      local json = assert.response(r).has.jsonbody()
      assert.equal("json (error)", json.post_data.kind)
      assert.is_not_nil(json.post_data.error)
    end)
    it("new parameter on multipart POST", function()
      local r = assert(client:send {
        method = "POST",
        path = "/request",
        body = {},
        headers = {
          ["Content-Type"] = "multipart/form-data",
          host = "test1.test"
        }
      })
      assert.response(r).has.status(200)
      assert.response(r).has.jsonbody()
      local value = assert.request(r).has.formparam("p1")
      assert.equals("v1", value)
    end)
    it("does not change or append value to parameter on multipart POST when parameter exists", function()
      local r = assert(client:send {
        method = "POST",
        path = "/request",
        body = {
          p1 = "this should not change",
          hello = "world",
        },
        headers = {
          ["Content-Type"] = "multipart/form-data",
          host = "test1.test"
        },
      })
      assert.response(r).has.status(200)
      assert.response(r).has.jsonbody()
      local value = assert.request(r).has.formparam("p1")
      assert.equals("this should not change", value)

      local value2 = assert.request(r).has.formparam("hello")
      assert.equals("world", value2)
    end)
    it("new querystring on GET", function()
      local r = assert( client:send {
        method = "GET",
        path = "/request",
        query = {
          q2 = "v2",
        },
        headers = {
          ["Content-Type"] = "application/x-www-form-urlencoded",
          host = "test1.test"
        }
      })
      assert.response(r).has.status(200)
      local value = assert.request(r).has.queryparam("q2")
      assert.equals("v2", value)
      local value = assert.request(r).has.queryparam("q1")
      assert.equals("v1", value)
    end)
    it("does not change or append value to querystring on GET if querystring exists", function()
      local r = assert(client:send {
        method = "GET",
        path = "/request",
        query = {
          q1 = "v2",
        },
        headers = {
          ["Content-Type"] = "application/x-www-form-urlencoded",
          host = "test1.test"
        }
      })
      assert.response(r).has.status(200)
      local value = assert.request(r).has.queryparam("q1")
      assert.equals("v2", value)
    end)
    it("should not change the host header", function()
      local r = assert(client:send {
        method = "GET",
        path = "/get",
        headers = {
          ["Content-Type"] = "application/json",
          host = "test2.test"
        }
      })
      assert.response(r).has.status(200)
      local json = assert.response(r).has.jsonbody()
      local value = assert.has.header("host", json)
      assert.equals("test2.test", value)
    end)
    it("preserves empty json array", function()
      local r = assert(client:send {
        method = "POST",
        path = "/request",
        body = [[{"emptyarray":[]}]],
        headers = {
          host = "test1.test",
          ["content-type"] = "application/json"
        }
      })
      assert.response(r).has.status(200)
      assert.response(r).has.jsonbody()
      local json = assert.request(r).has.jsonbody()
      assert.is_truthy(string.find(json.data, "\"emptyarray\":[]", 1, true))
    end)
  end)

  describe("append", function()
    it("new header if header does not exists", function()
      local r = assert(client:send {
        method = "GET",
        path = "/request",
        headers = {
          host = "test6.test"
        }
      })
      assert.response(r).has.status(200)
      assert.response(r).has.jsonbody()
      local h_h2 = assert.request(r).has.header("h2")
      assert.equals("v1", h_h2)
    end)
    it("values to existing headers", function()
      local r = assert( client:send {
        method = "GET",
        path = "/request",
        headers = {
          host = "test6.test"
        }
      })
      assert.response(r).has.status(200)
      assert.response(r).has.jsonbody()
      local h_h1 = assert.request(r).has.header("h1")
      assert.same({"v1", "v2"}, h_h1)
    end)

    it("can append a value with '#' (regression test for #29)", function()
      local route = admin_api.routes:insert({
        hosts = { "test_append_hash.test" }
      })
      admin_api.plugins:insert {
        route = { id = route.id },
        name = "request-transformer",
        config = {
          append = {
            headers = {"h1:v1", "h1:v2", "h1:#value_with_hash", "h2:v1",},
            querystring = {"q1:v1", "q1:v2", "q2:v1"},
            body = {"p1:v1", "p1:v2", "p2:value:1"}     -- payload containing a colon
          }
        }
      }
      local r
      helpers.wait_until(function()
        r = assert( client:send {
          method = "GET",
          path = "/request",
          headers = {
            host = "test_append_hash.test"
          }
        })
        local body = r:read_body()
        return string.find(body, "h1", 1, true)
      end, 10)
      assert.response(r).has.status(200)
      assert.response(r).has.jsonbody()
      local h_h1 = assert.request(r).has.header("h1")
      assert.same({"v1", "v2", "#value_with_hash"}, h_h1)
    end)

    it("new querystring if querystring does not exists", function()
      local r = assert(client:send {
        method = "POST",
        path = "/request",
        body = {
          hello = "world",
        },
        headers = {
          ["Content-Type"] = "application/x-www-form-urlencoded",
          host = "test6.test"
        }
      })
      assert.response(r).has.status(200)
      local value = assert.request(r).has.queryparam("q2")
      assert.equals("v1", value)
    end)
    it("values to existing querystring", function()
      local r = assert(client:send {
        method = "POST",
        path = "/request",
        headers = {
          ["Content-Type"] = "application/x-www-form-urlencoded",
          host = "test6.test"
        }
      })
      assert.response(r).has.status(200)
      local value = assert.request(r).has.queryparam("q1")
      assert.are.same({"v1", "v2"}, value)
    end)
    it("new parameter in url encoded body on POST if it does not exist", function()
      local r = assert( client:send {
        method = "POST",
        path = "/request",
        headers = {
          ["Content-Type"] = "application/x-www-form-urlencoded",
          host = "test6.test"
        }
      })
      assert.response(r).has.status(200)
      assert.response(r).has.jsonbody()
      local value = assert.request(r).has.formparam("p1")
      assert.same({"v1", "v2"}, value)

      local value2 = assert.request(r).has.formparam("p2")
      assert.same("value:1", value2)
    end)
    it("values to existing parameter in url encoded body if parameter already exist on POST", function()
      local r = assert(client:send {
        method = "POST",
        path = "/request",
        body = {
          p1 = "v0",
        },
        headers = {
          ["Content-Type"] = "application/x-www-form-urlencoded",
          host = "test6.test"
        }
      })
      assert.response(r).has.status(200)
      assert.response(r).has.jsonbody()
      local value = assert.request(r).has.formparam("p1")
      assert.same({"v0", "v1", "v2"}, value)

      local value2 = assert.request(r).has.formparam("p2")
      assert.are.same("value:1", value2)
    end)
    it("does not fail if JSON body is malformed in POST", function()
      local r = assert(client:send {
        method = "POST",
        path = "/request",
        body = "malformed json body",
        headers = {
          host = "test6.test",
          ["content-type"] = "application/json"
        }
      })
      assert.response(r).has.status(200)
      local json = assert.response(r).has.jsonbody()
      assert.equal("json (error)", json.post_data.kind)
      assert.is_not_nil(json.post_data.error)
    end)
    it("does not change or append value to parameter on multipart POST", function()
      local r = assert(client:send {
        method = "POST",
        path = "/request",
        body = {
          p1 = "This should not change",
        },
        headers = {
          ["Content-Type"] = "multipart/form-data",
          host = "test6.test"
        }
      })
      assert.response(r).has.status(200)
      assert.response(r).has.jsonbody()
      local value = assert.request(r).has.formparam("p1")
      assert.equals("This should not change", value)
    end)
    it("preserves empty json array", function()
      local r = assert(client:send {
        method = "POST",
        path = "/request",
        body = [[{"emptyarray":[]}]],
        headers = {
          host = "test6.test",
          ["content-type"] = "application/json"
        }
      })
      assert.response(r).has.status(200)
      assert.response(r).has.jsonbody()
      local json = assert.request(r).has.jsonbody()
      assert.is_truthy(string.find(json.data, "\"emptyarray\":[]", 1, true))
    end)
  end)

  describe("remove, replace, add and append", function()
    it("removes a header", function()
      local r = assert(client:send {
        method = "GET",
        path = "/request",
        headers = {
          host = "test3.test",
          ["x-to-remove"] = "true",
        }
      })
      assert.response(r).has.status(200)
      assert.response(r).has.jsonbody()
      assert.request(r).has.no.header("x-to-remove")
    end)
    it("replaces value of header, if header exist", function()
      local r = assert( client:send {
        method = "GET",
        path = "/request",
        headers = {
          host = "test3.test",
          ["x-to-replace"] = "true",
        }
      })
      assert.response(r).has.status(200)
      assert.response(r).has.jsonbody()
      local hval = assert.request(r).has.header("x-to-replace")
      assert.equals("false", hval)
    end)
    it("does not add new header if to be replaced header does not exist", function()
      local r = assert(client:send {
        method = "GET",
        path = "/request",
        headers = {
          host = "test3.test",
        }
      })
      assert.response(r).has.status(200)
      assert.response(r).has.jsonbody()
      assert.request(r).has.no.header("x-to-replace")
    end)
    it("add new header if missing", function()
      local r = assert(client:send {
        method = "GET",
        path = "/request",
        headers = {
          host = "test3.test",
        }
      })
      assert.response(r).has.status(200)
      assert.response(r).has.jsonbody()
      local hval = assert.request(r).has.header("x-added2")
      assert.equals("b1", hval)
    end)
    it("does not add new header if it already exist", function()
      local r = assert(client:send {
        method = "GET",
        path = "/request",
        headers = {
          host = "test3.test",
          ["x-added3"] = "c1",
        }
      })
      assert.response(r).has.status(200)
      assert.response(r).has.jsonbody()
      local hval = assert.request(r).has.header("x-added3")
      assert.equals("c1", hval)
    end)
    it("appends values to existing headers", function()
      local r = assert(client:send {
        method = "GET",
        path = "/request",
        headers = {
          host = "test3.test",
        }
      })
      assert.response(r).has.status(200)
      assert.response(r).has.jsonbody()
      local hval = assert.request(r).has.header("x-added")
      assert.same({"a1", "a2", "a3"}, hval)
    end)
    it("adds new parameters on POST when query string key missing", function()
      local r = assert(client:send {
        method = "POST",
        path = "/request",
        body = {
          hello = "world",
        },
        headers = {
          host = "test3.test",
          ["Content-Type"] = "application/x-www-form-urlencoded",
        }
      })
      assert.response(r).has.status(200)
      local value = assert.request(r).has.queryparam("p2")
      assert.equals("b1", value)
    end)
    it("removes parameters on GET", function()
      local r = assert( client:send {
        method = "GET",
        path = "/request",
        query = {
          toremovequery = "yes",
          nottoremove = "yes",
        },
        body = {
          hello = "world",
        },
        headers = {
          host = "test3.test",
          ["Content-Type"] = "application/x-www-form-urlencoded",
        }
      })
      assert.response(r).has.status(200)
      assert.response(r).has.jsonbody()
      assert.request(r).has.no.queryparam("toremovequery")
      local value = assert.request(r).has.queryparam("nottoremove")
      assert.equals("yes", value)
    end)
    it("replaces parameters on GET", function()
      local r = assert(client:send {
        method = "GET",
        path = "/request",
        query = {
          toreplacequery = "yes",
        },
        body = {
          hello = "world",
        },
        headers = {
          host = "test3.test",
          ["Content-Type"] = "application/x-www-form-urlencoded",
        }
      })
      assert.response(r).has.status(200)
      local value = assert.request(r).has.queryparam("toreplacequery")
      assert.equals("no", value)
    end)
    it("does not add new parameter if to be replaced parameters does not exist on GET", function()
      local r = assert( client:send {
        method = "GET",
        path = "/request",
        headers = {
          host = "test3.test",
          ["Content-Type"] = "application/x-www-form-urlencoded",
        }
      })
      assert.response(r).has.status(200)
      assert.request(r).has.no.formparam("toreplacequery")
    end)
    it("adds parameters on GET if it does not exist", function()
      local r = assert(client:send {
        method = "GET",
        path = "/request",
        headers = {
          host = "test3.test",
          ["Content-Type"] = "application/x-www-form-urlencoded",
        }
      })
      assert.response(r).has.status(200)
      local value = assert.request(r).has.queryparam("query-added")
      assert.equals("newvalue", value)
    end)
    it("does not add new parameter if to be added parameters already exist on GET", function()
      local r = assert(client:send {
        method = "GET",
        path = "/request",
        query = {
          ["query-added"] = "oldvalue",
        },
        headers = {
          host = "test3.test",
          ["Content-Type"] = "application/x-www-form-urlencoded",
        }
      })
      assert.response(r).has.status(200)
      local value = assert.request(r).has.queryparam("query-added")
      assert.equals("oldvalue", value)
    end)
    it("appends parameters on GET", function()
      local r = assert(client:send {
        method = "GET",
        path = "/request",
        query = {
          q1 = "20",
        },
        body = {
          hello = "world",
        },
        headers = {
          host = "test3.test",
          ["Content-Type"] = "application/x-www-form-urlencoded",
        }
      })
      assert.response(r).has.status(200)
      local value = assert.request(r).has.queryparam("p1")
      assert.equals("anything:1", value[1])
      assert.equals("a2", value[2])
      local value = assert.request(r).has.queryparam("q1")
      assert.equals("20", value)
    end)

    it("removes a header -- ignore case", function()
      local r = assert(client:send {
        method = "GET",
        path = "/request",
        headers = {
          host = "test26.test",
          ["x-to-remove"] = "true",
        }
      })
      assert.response(r).has.status(200)
      assert.response(r).has.jsonbody()
      assert.request(r).has.no.header("X-To-Remove")
    end)
    it("replaces value of header, if header exist -- don't change header case", function()
      local r = assert( client:send {
        method = "GET",
        path = "/request",
        headers = {
          host = "test26.test",
          ["x-to-replace"] = "true",
        }
      })
      assert.response(r).has.status(200)
      assert.response(r).has.jsonbody()
      local hval = assert.request(r).has.header("x-to-Replace")
      assert.equals("false", hval)
    end)
    it("add new header if missing -- keep configured case", function()
      local r = assert(client:send {
        method = "GET",
        path = "/request",
        headers = {
          host = "test26.test",
        }
      })
      assert.response(r).has.status(200)
      assert.response(r).has.jsonbody()
      local hval = assert.request(r).has.header("x-aDDed2")
      assert.equals("b1", hval)
    end)
    it("does not add new header if it already exist -- keep request case", function()
      local r = assert(client:send {
        method = "GET",
        path = "/request",
        headers = {
          host = "test26.test",
          ["x-added3"] = "c1",
        }
      })
      assert.response(r).has.status(200)
      assert.response(r).has.jsonbody()
      local hval = assert.request(r).has.header("x-added3")
      assert.equals("c1", hval)
    end)
    it("appends values to existing headers -- keep config case", function()
      local r = assert(client:send {
        method = "GET",
        path = "/request",
        headers = {
          host = "test26.test",
          ["X-addeD"] = "a0",
        }
      })
      assert.response(r).has.status(200)
      assert.response(r).has.jsonbody()
      local hval = assert.request(r).has.header("X-aDDed")
      assert.same({"a0", "a2", "a3"}, hval)
    end)
    it("appends values to added headers -- keep 'add' config case", function()
      local r = assert(client:send {
        method = "GET",
        path = "/request",
        headers = {
          host = "test26.test",
        }
      })
      assert.response(r).has.status(200)
      assert.response(r).has.jsonbody()
      local hval = assert.request(r).has.header("X-Added")
      assert.same({"a1", "a2", "a3"}, hval)
    end)
    it("adds new parameters on POST when query string key missing", function()
      local r = assert(client:send {
        method = "POST",
        path = "/request",
        body = {
          hello = "world",
        },
        headers = {
          host = "test3.test",
          ["Content-Type"] = "application/x-www-form-urlencoded",
        }
      })
      assert.response(r).has.status(200)
      local value = assert.request(r).has.queryparam("p2")
      assert.equals("b1", value)
    end)
    it("removes parameters on GET", function()
      local r = assert( client:send {
        method = "GET",
        path = "/request",
        query = {
          toremovequery = "yes",
          nottoremove = "yes",
        },
        body = {
          hello = "world",
        },
        headers = {
          host = "test3.test",
          ["Content-Type"] = "application/x-www-form-urlencoded",
        }
      })
      assert.response(r).has.status(200)
      assert.response(r).has.jsonbody()
      assert.request(r).has.no.queryparam("toremovequery")
      local value = assert.request(r).has.queryparam("nottoremove")
      assert.equals("yes", value)
    end)
    it("replaces parameters on GET", function()
      local r = assert(client:send {
        method = "GET",
        path = "/request",
        query = {
          toreplacequery = "yes",
        },
        body = {
          hello = "world",
        },
        headers = {
          host = "test3.test",
          ["Content-Type"] = "application/x-www-form-urlencoded",
        }
      })
      assert.response(r).has.status(200)
      local value = assert.request(r).has.queryparam("toreplacequery")
      assert.equals("no", value)
    end)
    it("does not add new parameter if to be replaced parameters does not exist on GET", function()
      local r = assert( client:send {
        method = "GET",
        path = "/request",
        headers = {
          host = "test3.test",
          ["Content-Type"] = "application/x-www-form-urlencoded",
        }
      })
      assert.response(r).has.status(200)
      assert.request(r).has.no.formparam("toreplacequery")
    end)
    it("adds parameters on GET if it does not exist", function()
      local r = assert(client:send {
        method = "GET",
        path = "/request",
        headers = {
          host = "test3.test",
          ["Content-Type"] = "application/x-www-form-urlencoded",
        }
      })
      assert.response(r).has.status(200)
      local value = assert.request(r).has.queryparam("query-added")
      assert.equals("newvalue", value)
    end)
    it("does not add new parameter if to be added parameters already exist on GET", function()
      local r = assert(client:send {
        method = "GET",
        path = "/request",
        query = {
          ["query-added"] = "oldvalue",
        },
        headers = {
          host = "test3.test",
          ["Content-Type"] = "application/x-www-form-urlencoded",
        }
      })
      assert.response(r).has.status(200)
      local value = assert.request(r).has.queryparam("query-added")
      assert.equals("oldvalue", value)
    end)
    it("appends parameters on GET", function()
      local r = assert(client:send {
        method = "GET",
        path = "/request",
        query = {
          q1 = "20",
        },
        body = {
          hello = "world",
        },
        headers = {
          host = "test3.test",
          ["Content-Type"] = "application/x-www-form-urlencoded",
        }
      })
      assert.response(r).has.status(200)
      local value = assert.request(r).has.queryparam("p1")
      assert.equals("anything:1", value[1])
      assert.equals("a2", value[2])
      local value = assert.request(r).has.queryparam("q1")
      assert.equals("20", value)
    end)
    it("preserves empty json array", function()
      local r = assert(client:send {
        method = "POST",
        path = "/request",
        body = [[{"emptyarray":[]}]],
        headers = {
          host = "test3.test",
          ["content-type"] = "application/json"
        }
      })
      assert.response(r).has.status(200)
      assert.response(r).has.jsonbody()
      local json = assert.request(r).has.jsonbody()
      assert.is_truthy(string.find(json.data, "\"emptyarray\":[]", 1, true))
    end)
  end)



  describe("request rewrite using template", function()
    it("template as querystring parameters on GET", function()
      local r = assert(client:send {
        method = "GET",
        path = "/requests/user1/foo/user2/bar",
        query = {
          q1 = "20",
        },
        body = {
          hello = "world",
        },
        headers = {
          host = "test10.test",
          ["Content-Type"] = "application/x-www-form-urlencoded",
        }
      })
      local body = assert.response(r).has.status(200)
      local json_body = cjson.decode(body)
      local value = assert.request(r).has.queryparam("uri_param1")
      assert.equals("foo", value)
      assert.equals("/requests/user1/foo/user2/bar?q1=20&uri_param1=foo&uri_param2%5Bsome_index%5D%5B1%5D=bar",
                    json_body.vars.request_uri)
    end)
    it("should update request path using hash", function()
      local r = assert(client:send {
        method = "GET",
        path = "/requests/user1/foo/user2/bar",
        headers = {
          host = "test11.test",
        }
      })
      assert.response(r).has.status(200)
      local body = assert(assert.response(r).has.jsonbody())
      assert.equals(helpers.mock_upstream_url ..
                    "/requests/user2/bar/user1/foo", body.url)
    end)
    it("should not add querystring if hash missing", function()
      local r = assert(client:send {
        method = "GET",
        path = "/requests/",
        query = {
          q1 = "20",
        },
        headers = {
          host = "test12.test",
        }
      })
      assert.response(r).has.status(200)
      assert.request(r).has.queryparam("q1")
      local value = assert.request(r).has.queryparam("uri_param1")
      assert.equals("default1", value)
      value = assert.request(r).has.queryparam("uri_param2")
      assert.equals("default2", value)
    end)
    it("should fail when uri template is not a proper expression", function()
      local r = assert(client:send {
        method = "GET",
        path = "/requests/user1/foo/user2/bar",
        headers = {
          host = "test13.test",
        }
      })
      assert.response(r).has.status(500)
    end)
    it("should not fail when uri template rendered using index", function()
      local r = assert(client:send {
        method = "GET",
        path = "/user1/foo/user2/bar",
        headers = {
          host = "test14.test",
        }
      })
      assert.response(r).has.status(200)
      local body = assert(assert.response(r).has.jsonbody())
      assert.equals(helpers.mock_upstream_url ..
                    "/requests/user1/foo/user2/bar", body.url)
    end)
    it("validate using headers/req_querystring for rendering templates",
      function()
        local r = assert(client:send {
          method = "GET",
          path = "/requests/user1/foo/user2/bar",
          query = {
            q1 = "20",
          },
          headers = {
            host = "test15.test",
            ["Content-Type"] = "application/x-www-form-urlencoded",
          }
        })
        assert.response(r).has.status(200)
        assert.request(r).has.no.queryparam("q1")
        local value = assert.request(r).has.queryparam("uri_param1")
        assert.equals("foo", value)
        value = assert.request(r).has.queryparam("uri_param2")
        assert.equals("test15.test", value)
        value = assert.request(r).has.header("x-test-header")
        assert.equals("20", value)
      end)
    it("validate that removed header can be used as template", function()
      local r = assert(client:send {
        method = "GET",
        path = "/requests/user1/foo/user2/bar",
        query = {
          q2 = "20",
        },
        headers = {
          host = "test16.test",
          ["x-remove-header"] = "its a test",
          ["Content-Type"] = "application/x-www-form-urlencoded",
        }
      })
      assert.response(r).has.status(200)
      assert.request(r).has.no.header("x-remove-header")
      local value = assert.request(r).has.queryparam("q1")
      assert.equals("foo", value)
      value = assert.request(r).has.queryparam("q2")
      assert.equals("its a test", value)
      value = assert.request(r).has.header("x-test-header")
      assert.equals("its a test", value)
    end)
    it("validate template will be rendered with old value of replaced header",
      function()
        local r = assert(client:send {
          method = "GET",
          path = "/requests/user1/foo/user2/bar",
          query = {
            q2 = "20",
          },
          headers = {
            host = "test17.test",
            ["x-replace-header"] = "the old value",
            ["Content-Type"] = "application/x-www-form-urlencoded",
          }
        })
        assert.response(r).has.status(200)
        local value = assert.request(r).has.queryparam("q1")
        assert.equals("foo", value)
        value = assert.request(r).has.queryparam("q2")
        assert.equals("the old value", value)
        value = assert.request(r).has.header("x-test-header")
        assert.equals("the old value", value)
        value = assert.request(r).has.header("x-replace-header")
        assert.equals("the new value", value)
      end)
    it("validate template can be escaped",
      function()
        local r = assert(client:send {
          method = "GET",
          path = "/requests/user1/foo/user2/bar",
          query = {
            q2 = "20",
          },
          headers = {
            host = "test18.test",
            ["x-replace-header"] = "the old value",
            ["Content-Type"] = "application/x-www-form-urlencoded",
          }
        })
        assert.response(r).has.status(200)
        local value = assert.request(r).has.queryparam("q1")
        assert.equals([[$(uri_captures.user1)]], value)
        value = assert.request(r).has.queryparam("q2")
        assert.equals("20", value)
      end)
    it("rendering error (query) should fail when rendering errors out", function()
      local r = assert(client:send {
        method = "GET",
        path = "/requests/user1/foo/user2/bar",
        query = {
          q2 = "20",
        },
        headers = {
          host = "test19.test",
          ["x-replace-header"] = "the old value",
          ["Content-Type"] = "application/x-www-form-urlencoded",
        }
      })
      assert.response(r).has.status(500)
    end)
    it("rendering error (header) is correctly propagated in error.log, issue #25", function()
      local pattern = [[error:%[string "TMP"%]:1: attempt to call global 'foo' %(a nil value%)]]
      local start_count = count_log_lines(pattern)

      local r = assert(client:send {
        method = "GET",
        path = "/",
        headers = {
          host = "test24.test",
        }
      })
      assert.response(r).has.status(500)

      helpers.wait_until(function()
        local count = count_log_lines(pattern)
        return count - start_count >= 1 -- Kong 2.2+ == 1, Pre 2.2 == 2
      end, 5)
    end)
    it("type function is available in template environment", function()
      local r = assert(client:send {
        method = "GET",
        path = "/request",
        headers = {
          host = "test25.test",
          ["X-Foo"] = { "1", "2", },
        }
      })
      assert.response(r).has.status(200)
      assert.response(r).has.jsonbody()
      local h_h1 = assert.request(r).has.header("X-Foo-Transformed")
      assert.equals("1-first", h_h1)
    end)
    it("can inject a value from 'kong.ctx.shared'", function()
      local r = assert(client:send {
        method = "GET",
        path = "/",
        headers = {
          host = "test21.test",
        }
      })
      assert.response(r).has.status(200)
      local value = assert.request(r).has.queryparam("shared_param1")
      assert.equals("1.2.3", value)
    end)
    it("cannot write a value in `kong.ctx.shared`", function()
      local r = client:send {
        method = "GET",
        path = "/",
        headers = {
          host = "test28.test",
        }
      }
      assert.response(r).has.status(500)
    end)
  end)
  describe("remove then add header (regression test)", function()
    it("header already exists in request", function()
      local r = assert(client:send {
        method = "GET",
        path = "/",
        headers = {
          host = "test22.test",
          ["Authorization"] = "Basic dGVzdDp0ZXN0",
          ["Content-Type"] = "application/json",
        }
      })
      assert.response(r).has.status(200)
      local value = assert.request(r).has.header("authorization")
      assert.equals("Basic test", value)
    end)
    it("header does not exist in request", function()
      local r = assert(client:send {
        method = "GET",
        path = "/",
        headers = {
          host = "test22.test",
          ["Content-Type"] = "application/json",
        }
      })
      assert.response(r).has.status(200)
      local value = assert.request(r).has.header("authorization")
      assert.equals("Basic test", value)
    end)
  end)
  describe("query parameters are not #urlencoded to upstream URL", function()
    local expected = "$&+,/:;?@"
    it("when plugin is not configured", function()
      local r = assert(client:send {
        method = "GET",
        path = "/request?expected=" .. expected, -- Use inline to keep from getting URL encoded
        headers = {
          host = "test23.test"
        }
      })
      local res = assert.response(r).has.status(200)
      local body = cjson.decode(res)
      local expected_url = helpers.mock_upstream_url .. "/?expected=" .. expected
      assert.equals(expected_url, body.url)
    end)
  end)
end)

describe("Plugin: request-transformer (thread safety) [#" .. strategy .. "]", function()
  local db_strategy = strategy ~= "off" and strategy or nil

  lazy_setup(function()
    local bp = helpers.get_db_utils(db_strategy, {
      "routes",
      "services",
      "plugins",
    }, { "request-transformer", "pre-function" })

    local route = bp.routes:insert({
      hosts = { "test_thread_safety.test" }
    })

    bp.plugins:insert {
      route = { id = route.id },
      name = "pre-function",
      config = {
        access = {
          [[
            local delay = kong.request.get_header("slow_body_delay")
            local orig_read_body = ngx.req.read_body
            ngx.ctx.orig_read_body = orig_read_body
            ngx.req.read_body = function()
              ngx.sleep(tonumber(delay))
              return orig_read_body()
            end
          ]]
        },
        header_filter = {
          [[
            ngx.req.read_body = ngx.ctx.orig_read_body or ngx.req.read_body
          ]]
        },
      }
    }

    bp.plugins:insert {
      route = { id = route.id },
      name = "request-transformer",
      config = {
        add = {
          querystring = { "added_q:yes_q" },
          headers = { "added_h:yes_h" },
          body = { "added_b:yes_b" }
        }
      }
    }

    assert(helpers.start_kong({
      database = db_strategy,
      plugins = "bundled, request-transformer",
      nginx_conf = "spec/fixtures/custom_nginx.template",
      nginx_worker_processes = 1
    }))
  end)

  lazy_teardown(function()
    helpers.stop_kong()
  end)

  it("sends requests with the expected values for headers, body, query", function()
    local race_conditions = ""

    local get_handler = function(header_val, body_param_val, query_param_val, delay)
      return function()
        local tmp_client = helpers.proxy_client()
        local r = assert(tmp_client:send({
          method = "POST",
          path = "/request",
          headers = {
            ["Content-Type"] = "application/json",
            host = "test_thread_safety.test",
            slow_body_delay = delay,
            h = header_val
          },
          body = {
            k = body_param_val
          },
          query = {
            q = query_param_val
          }
        }))

        assert.response(r).has.status(200)
        local header = assert.request(r).has.header("h")
        local body_param = assert.request(r).has.jsonbody().params.k
        local query_param = assert.request(r).has.queryparam("q")
        if header_val ~= header then
          race_conditions = race_conditions .. fmt("expected: %s, received: %s", header_val, header)
        end
        if body_param_val ~= body_param then
          race_conditions = race_conditions .. fmt("expected: %s, received: %s", body_param_val, body_param)
        end
        if query_param ~= query_param_val then
          race_conditions = race_conditions .. fmt("expected: %s, received: %s", query_param, query_param_val)
        end
        tmp_client:close()
      end
    end

    local thread_1 = ngx.thread.spawn(get_handler("vh1", "b1", "vq1", 2))
    local thread_2 = ngx.thread.spawn(get_handler("vh2", "b2", "vq2", 0))
    ngx.thread.wait(thread_1)
    ngx.thread.wait(thread_2)

    assert.equals("", race_conditions)
  end)
end)

describe("Plugin: request-transformer(access) [#" .. strategy .. "] untrusted_lua=off", function()
  local client

  lazy_setup(function()
    local bp = helpers.get_db_utils(strategy, {
      "routes",
      "services",
      "plugins",
    })

    local route1 = bp.routes:insert({
      hosts = { "testLuaOff.test" }
    })

    local route2 = bp.routes:insert({
      hosts = { "testLuaOff2.test" }
    })

    bp.plugins:insert {
      route = { id = route1.id },
      name = "request-transformer",
      config = {
        add = {
          headers = {"x-added:some_value"},
        }
      }
    }

    bp.plugins:insert {
      route = { id = route2.id },
      name = "request-transformer",
      config = {
        add = {
          headers = {"x-added:$(require('inspect')(string.sub(query_params.user, 1, 3)))"},
        }
      }
    }

    assert(helpers.start_kong({
      database = strategy,
      untrusted_lua = "off",
      plugins = "bundled, request-transformer",
      nginx_conf = "spec/fixtures/custom_nginx.template",
    }))
  end)

  lazy_teardown(function()
    helpers.stop_kong()
  end)

  before_each(function()
    client = helpers.proxy_client()
  end)

  after_each(function()
    if client then client:close() end
  end)


  it("correctly handles plain text value", function()
    local r = assert(client:send {
      method = "GET",
      path = "/",
      headers = {
        host = "testLuaOff.test",
        ["Content-Type"] = "application/x-www-form-urlencoded",
      }
    })
    assert.response(r).has.status(200)
    local value = assert.request(r).has.header("x-added")
    assert.equals("some_value", value)
  end)

  it("does not render lua expressions", function()
    local pattern = [[loading of untrusted Lua code disabled because 'untrusted_lua' config option is set to 'off']]
    local start_count = count_log_lines(pattern)
    local r = assert(client:send {
      method = "GET",
      path = "/",
      query = {
        user = "foo"
      },
      headers = {
        host = "testLuaOff2.test",
        ["Content-Type"] = "application/x-www-form-urlencoded",
      }
    })
    assert.response(r).has.status(500)
    helpers.wait_until(function()
      local count = count_log_lines(pattern)
      return count - start_count >= 1
    end, 5)
  end)
end)

describe("Plugin: request-transformer(access) [#" .. strategy .. "] untrusted_lua=on", function()
  local client

  lazy_setup(function()
    local bp = helpers.get_db_utils(strategy, {
      "routes",
      "services",
      "plugins",
    })

    local route1 = bp.routes:insert({
      hosts = { "testLuaOff1.test" },
    })

    bp.plugins:insert {
      route = { id = route1.id },
      name = "request-transformer",
      config = {
        add = {
          headers = {"x-added:$(require('inspect')(string.sub(query_params.user, 1, 3)))"},
        }
      }
    }

    assert(helpers.start_kong({
      database = strategy,
      untrusted_lua = "on",
      plugins = "bundled, request-transformer",
      nginx_conf = "spec/fixtures/custom_nginx.template",
    }))
  end)

  lazy_teardown(function()
    helpers.stop_kong()
  end)

  before_each(function()
    client = helpers.proxy_client()
  end)

  after_each(function()
    if client then client:close() end
  end)

  describe("using template", function()
    it("renders the Lua expression without restrictions", function()
      local r = assert(client:send {
        method = "GET",
        path = "/",
        query = {
          user = "foo123"
        },
        headers = {
          host = "testLuaOff1.test",
          ["Content-Type"] = "application/x-www-form-urlencoded",
        }
      })
      assert.response(r).has.status(200)
      local value = assert.request(r).has.header("x-added")
      assert.equals('"foo"', value)
    end)
  end)
end)

describe("Plugin: request-transformer(access) [#" .. strategy .. "] untrusted_lua=sandbox", function()
  local client

  lazy_setup(function()
    local bp = helpers.get_db_utils(strategy, {
      "routes",
      "services",
      "plugins",
    })

    local route1 = bp.routes:insert({
      hosts = { "testLuaSandbox1.test" },
    })

    local route2 = bp.routes:insert({
      hosts = { "testLuaSandbox2.test" },
    })

    local route3 = bp.routes:insert({
      hosts = { "testLuaSandbox3.test" },
    })

    bp.plugins:insert {
      route = { id = route1.id },
      name = "request-transformer",
      config = {
        add = {
          headers = {"x-added:$(type(query_params.user))"},
        }
      }
    }

    bp.plugins:insert {
      route = { id = route2.id },
      name = "request-transformer",
      config = {
        add = {
          headers = {"x-added:$(string.sub(query_params.user, 1, 3))"},
        }
      }
    }

    bp.plugins:insert {
      route = { id = route3.id },
      name = "request-transformer",
      config = {
        add = {
          headers = {"x-added:$(require('ngx.process')('somestring'))"},
        }
      }
    }

    assert(helpers.start_kong({
      database = strategy,
      untrusted_lua = "sandbox",
      plugins = "bundled, request-transformer",
      nginx_conf = "spec/fixtures/custom_nginx.template",
    }))
  end)

  lazy_teardown(function()
    helpers.stop_kong()
  end)

  before_each(function()
    client = helpers.proxy_client()
  end)

  after_each(function()
    if client then client:close() end
  end)

  describe("using template", function()
    it("should succeed when template accesses allowed Lua function from sandbox", function()
      local r = assert(client:send {
        method = "GET",
        path = "/",
        query = {
          user = "foo"
        },
        headers = {
          host = "testLuaSandbox1.test",
          ["Content-Type"] = "application/x-www-form-urlencoded",
        }
      })
      assert.response(r).has.status(200)
      local value = assert.request(r).has.header("x-added")
      assert.equals("string", value)
    end)

    it("should fail when template tries to access non allowed Lua function from sandbox", function()
      local pattern = [[attempt to index global 'string' %(a nil value%)]]
      local start_count = count_log_lines(pattern)
      local r = assert(client:send {
        method = "GET",
        path = "/",
        query = {
          user = "foo"
        },
        headers = {
          host = "testLuaSandbox2.test",
          ["Content-Type"] = "application/x-www-form-urlencoded",
        }
      })
      assert.response(r).has.status(500)
      helpers.wait_until(function()
        local count = count_log_lines(pattern)
        return count - start_count >= 1
      end, 5)
    end)

    it("should fail when template tries to require non whitelisted module from sandbox", function()
      local pattern = [[require 'ngx.process' not allowed within sandbox]]
      local start_count = count_log_lines(pattern)

      local r = assert(client:send {
        method = "GET",
        path = "/",
        query = {
          user = "foo"
        },
        headers = {
          host = "testLuaSandbox3.test",
          ["Content-Type"] = "application/x-www-form-urlencoded",
        }
      })
      assert.response(r).has.status(500)

      helpers.wait_until(function()
        local count = count_log_lines(pattern)
        return count - start_count >= 1
      end, 5)
    end)
  end)
end)

describe("Plugin: request-transformer(access) [#" .. strategy .. "] untrusted_lua_sandbox_requires", function()
  local client

  lazy_setup(function()
    local bp = helpers.get_db_utils(strategy, {
      "routes",
      "services",
      "plugins",
    })

    local route1 = bp.routes:insert({
      hosts = { "testLuaRequires.test" },
    })

    bp.plugins:insert {
      route = { id = route1.id },
      name = "request-transformer",
      config = {
        add = {
          headers = {"x-added:$(require('inspect')({query_params.user1,query_params.user2}))"},
        }
      }
    }

    assert(helpers.start_kong({
      database = strategy,
      untrusted_lua = "sandbox",
      untrusted_lua_sandbox_requires =  "inspect",
      plugins = "bundled, request-transformer",
      nginx_conf = "spec/fixtures/custom_nginx.template",
    }))
  end)

  lazy_teardown(function()
    helpers.stop_kong()
  end)

  before_each(function()
    client = helpers.proxy_client()
  end)

  after_each(function()
    if client then client:close() end
  end)

  describe("using template", function()
    it("should successfully require whitelisted module from sandbox", function()
      local r = assert(client:send {
        method = "GET",
        path = "/",
        query = {
          user1 = "foo",
          user2 = "bar",
        },
        headers = {
          host = "testLuaRequires.test",
          ["Content-Type"] = "application/x-www-form-urlencoded",
        }
      })
      assert.response(r).has.status(200)
      local value = assert.request(r).has.header("x-added")
      assert.equals('{ "foo", "bar" }', value)
    end)
  end)
end)

describe("Plugin: request-transformer(access) [#" .. strategy .. "] untrusted_lua_sandbox_environment", function()
  local client

  lazy_setup(function()
    local bp = helpers.get_db_utils(strategy, {
      "routes",
      "services",
      "plugins",
    })

    local route1 = bp.routes:insert({
      hosts = { "testLuaRequires.test" },
    })

    bp.plugins:insert {
      route = { id = route1.id },
      name = "request-transformer",
      config = {
        add = {
          headers = {"x-added:$(string.format('u1:%s;u2:%s', query_params.user1,query_params.user2))"},
        }
      }
    }

    assert(helpers.start_kong({
      database = strategy,
      untrusted_lua = "sandbox",
      untrusted_lua_sandbox_environment =  "string",
      plugins = "bundled, request-transformer",
      nginx_conf = "spec/fixtures/custom_nginx.template",
    }))
  end)

  lazy_teardown(function()
    helpers.stop_kong()
  end)

  before_each(function()
    client = helpers.proxy_client()
  end)

  after_each(function()
    if client then client:close() end
  end)

  describe("using template", function()
    it("should successfully access whitelisted Lua variables from sandbox", function()
      local r = assert(client:send {
        method = "GET",
        path = "/",
        query = {
          user1 = "foo",
          user2 = "bar",
        },
        headers = {
          host = "testLuaRequires.test",
          ["Content-Type"] = "application/x-www-form-urlencoded",
        }
      })
      assert.response(r).has.status(200)
      local value = assert.request(r).has.header("x-added")
      assert.equals('u1:foo;u2:bar', value)
    end)
  end)
end)
end
