local helpers = require "spec.helpers"
local cjson   = require "cjson"
local http_mock = require "spec.helpers.http_mock"

local md5 = ngx.md5


for _, client_protocol in ipairs({ "http", "https", "http2" }) do
for _, strategy in helpers.each_strategy() do
  describe("Buffered Proxying [#" .. strategy .. "] [#" .. client_protocol .. "]", function()

    -- TODO: http2 / grpc does not currently work with
    -- ngx.location.capture that buffered proxying uses

    describe("[http]", function()
      local TCP_PORT
      local proxy_client
      local proxy_ssl_client

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

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

        -- the test using this service requires the error handler to be
        -- triggered, which does not happen when using the mock upstream
        local s0 = bp.services:insert {
          name = "service0",
          url = "http://127.0.0.1:" .. TCP_PORT,
        }

        local r0 = bp.routes:insert {
          paths = { "/0" },
          service = s0,
        }

        bp.plugins:insert {
          name = "enable-buffering",
          route = r0,
          protocols = {
            "http",
            "https",
          },
          config = {},
          service = s0,
        }

        local r1 = bp.routes:insert {
          paths = { "/1" },
        }

        bp.plugins:insert {
          name = "enable-buffering",
          route = r1,
          protocols = {
            "http",
            "https",
          },
          config = {
            phase = "header_filter",
            mode = "md5-header",
          }
        }

        local r2 = bp.routes:insert {
          paths = { "/2" },
        }

        bp.plugins:insert {
          name = "enable-buffering",
          route = r2,
          protocols = {
            "http",
            "https",
          },
          config = {
            phase = "header_filter",
            mode = "modify-json"
          }
        }

        local r3 = bp.routes:insert {
          paths = { "/3" },
        }

        bp.plugins:insert {
          name = "enable-buffering-response",
          route = r3,
          protocols = {
            "http",
            "https",
          },
          config = {
            phase = "response",
            mode = "md5-header",
          }
        }

        local r4 = bp.routes:insert {
          paths = { "/4" },
        }

        bp.plugins:insert {
          name = "enable-buffering-response",
          route = r4,
          protocols = {
            "http",
            "https",
          },
          config = {
            phase = "response",
            mode = "modify-json"
          }
        }

        local s502 = bp.services:insert {
          name = "502",
          host = "127.0.0.2",
          port = 26865,
        }

        local r502 = bp.routes:insert {
          paths     = { "/502" },
          protocols = { "http" },
          service   = s502,
        }

        bp.plugins:insert {
          name = "enable-buffering-response",
          route = r502,
          protocols = {
            "http",
            "https",
          },
          config = {
            phase = "header_filter",
            mode = "md5-header",
          }
        }

        assert(helpers.start_kong({
          database      = strategy,
          plugins       = "bundled,enable-buffering,enable-buffering-response",
          nginx_conf    = "spec/fixtures/custom_nginx.template",
          stream_listen = "off",
          admin_listen  = "off",
        }))
      end)

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

      before_each(function()
        if client_protocol == "http" then
          proxy_client = helpers.proxy_client()
        elseif client_protocol == "https" then
          proxy_client = helpers.proxy_ssl_client()
        elseif client_protocol == "http2" then
          proxy_client = helpers.proxy_ssl_client(nil, nil, 2)
        end
        proxy_ssl_client = helpers.proxy_ssl_client()
      end)

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

        if proxy_ssl_client then
          proxy_ssl_client:close()
        end
      end)

      it("header can be set from upstream response body on header_filter phase", function()
        local res = proxy_client:get("/1/status/231")
        local body = assert.res_status(231, res) .. "\n"
        assert.equal(md5(body), res.headers["MD5"])

        local res = proxy_ssl_client:get("/1/status/232")
        local body = assert.res_status(232, res) .. "\n"
        assert.equal(md5(body), res.headers["MD5"])
      end)

      it("HEAD request work the same, without a body", function()
        local res = proxy_client:send{ method="HEAD", path="/1/status/231"}
        local body = assert.res_status(231, res)
        assert.equal(body, "")
        assert.equal(md5(body), res.headers["MD5"])

        local res = proxy_ssl_client:send{ method="HEAD", path="/1/status/232" }
        local body = assert.res_status(232, res)
        assert.equal(body, "")
        assert.equal(md5(body), res.headers["MD5"])
      end)

      it("header can be set from upstream response body and body can be modified on header_filter phase", function()
        local res = proxy_client:get("/2/status/233")
        local body = assert.res_status(233, res)
        local json = cjson.decode(body)
        assert.equal(true, json.modified)
        assert.equal("yes", res.headers["Modified"])

        local res = proxy_ssl_client:get("/2/status/234")
        local body = assert.res_status(234, res)
        local json = cjson.decode(body)
        assert.equal(true, json.modified)
        assert.equal("yes", res.headers["Modified"])
      end)

      it("header can be set from upstream response body on response phase", function()
        local res = proxy_client:get("/3/status/235")
        local body = assert.res_status(235, res) .. "\n"
        assert.equal(md5(body), res.headers["MD5"])

        local res = proxy_ssl_client:get("/3/status/236")
        local body = assert.res_status(236, res) .. "\n"
        assert.equal(md5(body), res.headers["MD5"])
      end)

      it("response phase works in HEAD request", function()
        local res = proxy_client:send{ method="HEAD", path="/3/status/235" }
        local body = assert.res_status(235, res)
        assert.equal(body, "")
        assert.equal(md5(body), res.headers["MD5"])

        local res = proxy_ssl_client:send{ method="HEAD", path="/3/status/236" }
        local body = assert.res_status(236, res)
        assert.equal(body, "")
        assert.equal(md5(body), res.headers["MD5"])
      end)

      it("header can be set from upstream response body and body can be modified on response phase", function()
        local res = proxy_client:get("/4/status/237")
        local body = assert.res_status(237, res)
        local json = cjson.decode(body)
        assert.equal(true, json.modified)
        assert.equal("yes", res.headers["Modified"])

        local res = proxy_ssl_client:get("/4/status/238")
        local body = assert.res_status(238, res)
        local json = cjson.decode(body)
        assert.equal(true, json.modified)
        assert.equal("yes", res.headers["Modified"])
      end)

      it("returns 502 on connectivity errors", function()
        local res = proxy_client:get("/502")
        assert.res_status(502, res)
        assert.equal(nil, res.headers["MD5"])

        local res = proxy_ssl_client:get("/502")
        assert.res_status(502, res)
        assert.equal(nil, res.headers["MD5"])
      end)

      -- this test sends an intentionally mismatched if-match header
      -- to produce an nginx output filter error and status code 412
      -- the response has to go through kong_error_handler (via error_page)
      it("remains healthy when if-match header is used with buffering", function()
        local mock = http_mock.new(TCP_PORT)
        mock:start()
        local res = assert(proxy_client:send {
          method  = "GET",
          path    = "/0",
          headers = {
            ["if-match"] = 1
          }
        })

        assert.response(res).has_status(412)
        assert.logfile().has.no.line("exited on signal 11")
        mock:stop(true)
      end)
    end)
  end)
end
end -- for _, client_protocol
