local uuid    = require "kong.tools.uuid"
local cjson   = require "cjson"
local hybrid_helper = require "spec.hybrid"


hybrid_helper.run_for_each_deploy({}, function(helpers, strategy, deploy, rpc, rpc_sync)
  describe("Plugin: acl (API) [" .. helpers.format_tags() .. "]", function()
    local consumer
    local admin_client
    local bp
    local db

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

      assert(helpers.start_kong({
        database = strategy,
      }))

      admin_client = helpers.admin_client()
    end)

    lazy_teardown(function()
      if admin_client then
        admin_client:close()
      end

      helpers.stop_kong()
    end)

    describe("/consumers/:consumer/acls/", function()
      lazy_setup(function()
        consumer = assert(bp.consumers:insert({
          username = "bob"
        }, { nulls = true }))
      end)
      before_each(function()
        db:truncate("acls")
      end)

      describe("POST", function()
        it("creates an ACL association", function()
          local res = admin_client:post("/consumers/bob/acls", {
            body    = {
              group = "admin"
            },
            headers = {
              ["Content-Type"] = "application/json"
            }
          })
          local body = assert.res_status(201, res)
          local json = cjson.decode(body)
          assert.equal(consumer.id, json.consumer.id)
          assert.equal("admin", json.group)
        end)
        it("creates an ACL association with tags", function()
          local res = admin_client:send {
            method  = "POST",
            path    = "/consumers/bob/acls/",
            body    = {
              group    = "yoloers",
              tags     = { "tag1", "tag2" },
            },
            headers = {
              ["Content-Type"] = "application/json"
            }
          }
          local body = assert.res_status(201, res)
          local json = cjson.decode(body)
          assert.equal(consumer.id, json.consumer.id)
          assert.equal("tag1", json.tags[1])
          assert.equal("tag2", json.tags[2])
        end)
        describe("errors", function()
          it("returns bad request", function()
            local res = admin_client:post("/consumers/bob/acls", {
              body    = {},
              headers = {
                ["Content-Type"] = "application/json"
              }
            })
            local body = assert.res_status(400, res)
            local json = cjson.decode(body)
            assert.same({ group = "required field missing" }, json.fields)
          end)
        end)
      end)

      describe("GET", function()
        lazy_teardown(function()
          db:truncate("acls")
        end)
        it("retrieves the first page", function()
          bp.acls:insert_n(3, { consumer = { id = consumer.id } })

          local res = admin_client:get("/consumers/bob/acls")
          local body = assert.res_status(200, res)
          local json = cjson.decode(body)
          assert.is_table(json.data)
          assert.equal(3, #json.data)
        end)
      end)
    end)

    describe("/consumers/:consumer/acls/:id", function()
      local acl, acl2
      before_each(function()
        db:truncate("acls")
        acl = bp.acls:insert {
          group    = "hello",
          consumer = { id = consumer.id },
        }
        acl2 = bp.acls:insert {
          group    = "hello2",
          consumer = { id = consumer.id },
        }
      end)
      describe("GET", function()
        it("retrieves by id", function()
          local res = admin_client:get("/consumers/bob/acls/" .. acl.id)
          local body = assert.res_status(200, res)
          local json = cjson.decode(body)
          assert.equal(acl.id, json.id)
        end)
        it("retrieves by group", function()
          local res = admin_client:get("/consumers/bob/acls/" .. acl.group)
          local body = assert.res_status(200, res)
          local json = cjson.decode(body)
          assert.equal(acl.id, json.id)
        end)
        it("retrieves ACL by id only if the ACL belongs to the specified consumer", function()
          bp.consumers:insert {
            username = "alice"
          }

          local res = admin_client:get("/consumers/bob/acls/" .. acl.id)
          assert.res_status(200, res)

          res = admin_client:get("/consumers/alice/acls/" .. acl.id)
          assert.res_status(404, res)
        end)
        it("retrieves ACL by group only if the ACL belongs to the specified consumer", function()
          local res = admin_client:get("/consumers/bob/acls/" .. acl.group)
          assert.res_status(200, res)

          res = admin_client:get("/consumers/alice/acls/" .. acl.group)
          assert.res_status(404, res)
        end)
        it("retrieves right ACL by group when multiple consumers share the same group name created with POST", function()
          local res  = admin_client:post("/consumers", {
            body = {
              username = "anna",
            },
            headers = {
              ["Content-Type"] = "application/json",
            },
          })
          assert.res_status(201, res)
          assert.response(res).has.jsonbody()

          local res  = admin_client:post("/consumers", {
            body = {
              username = "jack",
            },
            headers = {
              ["Content-Type"] = "application/json",
            },
          })
          assert.res_status(201, res)
          assert.response(res).has.jsonbody()

          local res  = admin_client:post("/consumers/anna/acls", {
            body = {
              group = "foo"
            },
            headers = {
              ["Content-Type"] = "application/json"
            },
          })
          local body = assert.res_status(201, res)
          local ag   = cjson.decode(body)

          local res  = admin_client:post("/consumers/jack/acls", {
            body = {
              group = "foo"
            },
            headers = {
              ["Content-Type"] = "application/json"
            },
          })
          local body = assert.res_status(201, res)
          local jg   = cjson.decode(body)

          local res  = admin_client:get("/consumers/anna/acls/foo")
          local body = assert.res_status(200, res)
          local ag2  = cjson.decode(body)

          local res  = admin_client:get("/consumers/jack/acls/foo")
          local body = assert.res_status(200, res)
          local jg2  = cjson.decode(body)

          assert.same(ag, ag2)
          assert.same(jg, jg2)
          assert.not_same(jg, ag)
          assert.not_same(jg2, ag2)
          assert.not_same(jg, ag2)
          assert.not_same(jg2, ag)

          local res  = admin_client:delete("/consumers/anna")
          local _    = assert.res_status(204, res)

          local res  = admin_client:delete("/consumers/jack")
          local _    = assert.res_status(204, res)
        end)
        it("retrieves right ACL by group when multiple consumers share the same group name created with PUT", function()
          local res  = admin_client:put("/consumers/anna")
          assert.res_status(200, res)
          assert.response(res).has.jsonbody()

          local res  = admin_client:put("/consumers/jack")
          assert.res_status(200, res)
          assert.response(res).has.jsonbody()

          local res  = admin_client:put("/consumers/anna/acls/foo")
          local body = assert.res_status(200, res)
          local ag   = cjson.decode(body)

          local res  = admin_client:put("/consumers/jack/acls/foo")
          local body = assert.res_status(200, res)
          local jg   = cjson.decode(body)

          local res  = admin_client:get("/consumers/anna/acls/foo")
          local body = assert.res_status(200, res)
          local ag2  = cjson.decode(body)

          local res  = admin_client:get("/consumers/jack/acls/foo")
          local body = assert.res_status(200, res)
          local jg2  = cjson.decode(body)

          assert.same(ag, ag2)
          assert.same(jg, jg2)
          assert.not_same(jg, ag)
          assert.not_same(jg2, ag2)
          assert.not_same(jg, ag2)
          assert.not_same(jg2, ag)

          local res  = admin_client:delete("/consumers/anna")
          assert.res_status(204, res)

          local res  = admin_client:delete("/consumers/jack")
          assert.res_status(204, res)
        end)
      end)

      describe("PUT", function()
        it("upserts an ACL's groupname", function()
          local res = admin_client:put("/consumers/bob/acls/pro", {
            body = {},
            headers = {
              ["Content-Type"] = "application/json"
            }
          })
          local body = assert.res_status(200, res)
          local json = cjson.decode(body)
          assert.equal(consumer.id, json.consumer.id)
          assert.equal("pro", json.group)
        end)
        describe("errors", function()
          it("returns bad request", function()
            local res = admin_client:put("/consumers/bob/acls/f7852533-9160-4f5a-ae12-1ab99219ea95", {
              body    = {
                group = 123,
              },
              headers = {
                ["Content-Type"] = "application/json"
              }
            })
            local body = assert.res_status(400, res)
            local json = cjson.decode(body)
            assert.same({ group = "expected a string" }, json.fields)
          end)
        end)
      end)

      describe("PATCH", function()
        it("updates an ACL group by id", function()
          local previous_group = acl.group

          local res = admin_client:patch("/consumers/bob/acls/" .. acl.id, {
            body    = {
              group            = "updatedGroup"
            },
            headers = {
              ["Content-Type"] = "application/json"
            }
          })
          local body = assert.res_status(200, res)
          local json = cjson.decode(body)
          assert.not_equal(previous_group, json.group)
        end)
        it("updates an ACL group by group", function()
          local previous_group = acl.group

          local res = admin_client:patch("/consumers/bob/acls/" .. acl.group, {
            body    = {
              group            = "updatedGroup2"
            },
            headers = {
              ["Content-Type"] = "application/json"
            }
          })
          local body = assert.res_status(200, res)
          local json = cjson.decode(body)
          assert.not_equal(previous_group, json.group)
        end)
        describe("errors", function()
          it("handles invalid input", function()
            local res = admin_client:patch("/consumers/bob/acls/" .. acl.id, {
              body    = {
                group            = 123,
              },
              headers = {
                ["Content-Type"] = "application/json"
              }
            })
            local body = assert.res_status(400, res)
            local json = cjson.decode(body)
            assert.same({ group = "expected a string" }, json.fields)
          end)
        end)
      end)

      describe("DELETE", function()
        it("deletes an ACL group by id", function()
          local res = admin_client:delete("/consumers/bob/acls/" .. acl.id)
          assert.res_status(204, res)
        end)
        it("deletes an ACL group by group", function()
          local res = admin_client:delete("/consumers/bob/acls/" .. acl2.group)
          assert.res_status(204, res)
        end)
        describe("errors", function()
          it("returns 404 on missing group", function()
            local res = admin_client:delete("/consumers/bob/acls/blah")
            assert.res_status(404, res)
          end)
          it("returns 404 if not found", function()
            local res = admin_client:delete("/consumers/bob/acls/00000000-0000-0000-0000-000000000000")
            assert.res_status(404, res)
          end)
        end)
      end)
    end)

    describe("/acls", function()
      local consumer2

      describe("GET", function()
        lazy_setup(function()
          db:truncate("acls")

          for i = 1, 3 do
            bp.acls:insert {
              group    = "group" .. i,
              consumer = { id = consumer.id },
            }
          end

          consumer2 = bp.consumers:insert {
            username = "bob-the-buidler"
          }

          for i = 1, 3 do
            bp.acls:insert {
              group = "group" .. i,
              consumer = { id = consumer2.id },
            }
          end
        end)

        it("retrieves all the acls with trailing slash", function()
          local res = admin_client:get("/acls/")
          local body = assert.res_status(200, res)
          local json = cjson.decode(body)
          assert.is_table(json.data)
          assert.equal(6, #json.data)
        end)
        it("retrieves all the acls without trailing slash", function()
          local res = admin_client:get("/acls")
          local body = assert.res_status(200, res)
          local json = cjson.decode(body)
          assert.is_table(json.data)
          assert.equal(6, #json.data)
        end)
        it("paginates through the acls", function()
          local res = admin_client:get("/acls?size=3")
          local body = assert.res_status(200, res)
          local json_1 = cjson.decode(body)
          assert.is_table(json_1.data)
          assert.equal(3, #json_1.data)

          res = admin_client:get("/acls", {
            query = {
              size = 3,
              offset = json_1.offset,
            }
          })
          body = assert.res_status(200, res)
          local json_2 = cjson.decode(body)
          assert.is_table(json_2.data)
          assert.equal(3, #json_2.data)

          assert.not_same(json_1.data, json_2.data)
          assert.is_nil(json_2.offset) -- last page
        end)
      end)

      describe("POST", function()
        lazy_setup(function()
          db:truncate("acls")
        end)

        it("does not create acl when missing consumer", function()
          local res = admin_client:post("/acls", {
            body = {
              group = "test-group",
            },
            headers = {
              ["Content-Type"] = "application/json"
            }
          })
          local body = assert.res_status(400, res)
          local json = cjson.decode(body)
          assert.same("schema violation (consumer: required field missing)", json.message)
        end)

        it("creates acl", function()
          local res = admin_client:post("/acls", {
            body = {
              group = "test-group",
              consumer = {
                id = consumer.id
              }
            },
            headers = {
              ["Content-Type"] = "application/json"
            }
          })
          local body = assert.res_status(201, res)
          local json = cjson.decode(body)
          assert.equal("test-group", json.group)
        end)
      end)
    end)

    describe("/acls/:group_or_id", function()
      describe("PUT", function()
        lazy_setup(function()
          db:truncate("acls")
        end)

        it("does not create acl when missing consumer", function()
          local res = admin_client:put("/acls/" .. uuid.uuid(), {
            body = { group = "test-group" },
            headers = {
              ["Content-Type"] = "application/json"
            }
          })
          local body = assert.res_status(400, res)
          local json = cjson.decode(body)
          assert.same("schema violation (consumer: required field missing)", json.message)
        end)

        it("creates acl", function()
          local res = admin_client:put("/acls/" .. uuid.uuid(), {
            body = {
              group = "test-group",
              consumer = {
                id = consumer.id
              }
            },
            headers = {
              ["Content-Type"] = "application/json"
            }
          })
          local body = assert.res_status(200, res)
          local json = cjson.decode(body)
          assert.equal("test-group", json.group)
        end)
      end)
    end)

    describe("/acls/:acl_id/consumer", function()
      describe("GET", function()
        local credential

        lazy_setup(function()
          db:truncate("acls")
          credential = db.acls:insert {
            group = "foo-group",
            consumer = { id = consumer.id },
          }
        end)
        it("retrieves a Consumer from an acl's id", function()
          local res = admin_client:get("/acls/" .. credential.id .. "/consumer")
          local body = assert.res_status(200, res)
          local json = cjson.decode(body)
          assert.same(consumer, json)
        end)
      end)
    end)
  end)
end)
