dsl test: gather without pick

dsl test: gather without pick

alias Rockside.HTML

defmodule DSL.Test do
  use    ExUnit.Case
  import HTML.TestHelper
  import HTML.DSL
  import Kernel, except: [div: 2]

  test "basic builder" do
    buf = builder do
      add_val! :x
      _foo = "anything in between"
      add_val! :y
    end
    assert buf == [:x, :y]
  end

  test "builder with one content tag" do
    buf = builder do
      tag :foo, "content"
    end
    assert buf == ["<foo>", "content", "</foo>"]
  end

  test "builder with mixed level tags/text, no single enclosing element" do
    buf = builder do
      div "foo"
      div do
        span "bar"
      end
    end
    assert buf |> flush == """

foo

        bar

      """
      |> no_indent |> no_lf
  end

  test "more variations of tags w/ enclosing html element" do
    buf = builder do
      html do
        text "foo"
        div do
          text "inner"
        end
        text ["bar", "quux"]
      end
    end
    assert buf  |> flush == """

        foo

          inner

        barquux

      """
      |> no_indent |> no_lf
  end

  test "gather/pick with a list comprehension" do
    buf = builder do
      ul do
        gather for x <- 1..2, do: pick li "#{x}"
      end
    end
    assert buf |> flush == """

1

2

      """
      |> no_indent |> no_lf
  end

  test "gather/pick with Enum.map" do
    buf = builder do
      ul do
        gather Enum.map 1..2, &(pick li "#{&1}")
      end
    end
    assert buf |> flush == """

1

2

      """
      |> no_indent |> no_lf
  end

  test "gather/pick with a closure" do
    buf = builder do
      gather (fn -> pick span "foo" end).()
    end
    assert buf |> flush == "foo"
  end

  test "gather without proper pick raises clause error" do
    assert_raise CaseClauseError, fn ->
      builder do: (gather fn -> :foo end.())
    end
    assert_raise FunctionClauseError, fn ->
      builder do: (gather for _<-1..1, do: :foo)
    end
    assert_raise FunctionClauseError, fn ->
      builder do: (gather for _<-1..1, do: (pick nil; :foo))
    end
  end

  test "attrs" do
    buf = builder do
      span [class: "hilight"], "foo"
      div [id: :matrix, class: "shiny"] do
        text "heyy!"
        span [style: "outline: 1px"], "are you in matrix?"
      end
    end
    assert buf |> flush == """
      foo

        heyy!

          are you in matrix?

      """
      |> no_indent |> no_lf
  end
end

alias Rockside.HTML

defmodule DSL.Test do
  use    ExUnit.Case
  import HTML.TestHelper
  import HTML.DSL
  import Kernel, except: [div: 2]

  test "basic builder" do
    buf = builder do
      add_val! :x
      _foo = "anything in between"
      add_val! :y
    end
    assert buf == [:x, :y]
  end

  test "builder with one content tag" do
    buf = builder do
      tag :foo, "content"
    end
    assert buf == ["<foo>", "content", "</foo>"]
  end

  test "builder with mixed level tags/text, no single enclosing element" do
    buf = builder do
      div "foo"
      div do
        span "bar"
      end
    end
    assert buf |> flush == """

foo

        bar

      """
      |> no_indent |> no_lf
  end

  test "more variations of tags w/ enclosing html element" do
    buf = builder do
      html do
        text "foo"
        div do
          text "inner"
        end
        text ["bar", "quux"]
      end
    end
    assert buf  |> flush == """

        foo

          inner

        barquux

      """
      |> no_indent |> no_lf
  end

  test "gather/pick with a list comprehension" do
    buf = builder do
      ul do
        gather for x <- 1..2, do: pick li "#{x}"
      end
    end
    assert buf |> flush == """

1

2

      """
      |> no_indent |> no_lf
  end

  test "gather/pick with Enum.map" do
    buf = builder do
      ul do
        gather Enum.map 1..2, &(pick li "#{&1}")
      end
    end
    assert buf |> flush == """

1

2

      """
      |> no_indent |> no_lf
  end

  test "gather/pick with a closure" do
    buf = builder do
      gather (fn -> pick span "foo" end).()
    end
    assert buf |> flush == "foo"
  end

  test "attrs" do
    buf = builder do
      span [class: "hilight"], "foo"
      div [id: :matrix, class: "shiny"] do
        text "heyy!"
        span [style: "outline: 1px"], "are you in matrix?"
      end
    end
    assert buf |> flush == """
      foo

        heyy!

          are you in matrix?

      """
      |> no_indent |> no_lf
  end
end


Remove setup block

Remove setup block

Co-authored-by: Jake Worth <c8d99c2f7cd5f432c163abcd422672b9f77550bb@jakeworth.com>

defmodule Features.DeveloperViewsStatsTest do
  use Tilex.IntegrationCase, async: Application.get_env(:tilex, :async_feature_test)

  test "sees total number of posts by channel", %{session: session} do
    developer = Factory.insert!(:developer)
    phoenix_channel = Factory.insert!(:channel, name: "phoenix")
    other_channel = Factory.insert!(:channel, name: "other")

    Factory.insert!(
      :post,
      title: "functional programming rocks",
      channel: phoenix_channel
    )

    Enum.each(["i'm fine", "all these people out here", "what?"], fn title ->
      Factory.insert!(:post, title: title, channel: other_channel)
    end)

    Enum.each(["i'm not fine", "where are all the people", "okay."], fn title ->
      Factory.insert!(
        :post,
        inserted_at: Timex.to_datetime({2010, 1, 1}),
        title: title,
        channel: other_channel
      )
    end)

    session
    |> sign_in(developer)
    |> visit("/developer/statistics")

    assert(
      session
      |> find(Query.css(".stats_column header", text: "7 posts in 2 channels"))
    )

    channels = find(session, Query.css(".stats_column ul#channels"))
    [other_channel, phoenix_channel] = all(channels, Query.css("li"))

    assert text_without_newlines(other_channel) =~ "#other 6 posts"
    assert text_without_newlines(phoenix_channel) =~ "#phoenix 1 post"

    session
    |> fill_in(Query.css("#start-date"), with: "01-01-2009")
    |> fill_in(Query.css("#end-date"), with: "01-01-2011")
    |> click(Query.css("#filter-submit"))

    assert(
      session
      |> find(Query.css(".stats_column header", text: "3 posts in 1 channel"))
    )

    channels = find(session, Query.css(".stats_column ul#channels"))
    [other_channel] = all(channels, Query.css("li"))

    assert text_without_newlines(other_channel) =~ "#other 3 posts"
  end

  test "does not see sees til activity chart", %{session: session} do
    developer = Factory.insert!(:developer)

    session
    |> sign_in(developer)
    |> visit("/developer/statistics")

    refute_has(session, Query.css("ul#activity"))
  end
end

defmodule Features.DeveloperViewsStatsTest do
  use Tilex.IntegrationCase, async: Application.get_env(:tilex, :async_feature_test)

  setup_all do
    developer = Factory.insert!(:developer)
    {:ok, developer: developer}
  end

  test "sees total number of posts by channel", %{session: session, developer: developer} do
    phoenix_channel = Factory.insert!(:channel, name: "phoenix")
    other_channel = Factory.insert!(:channel, name: "other")

    Factory.insert!(
      :post,
      title: "functional programming rocks",
      channel: phoenix_channel
    )

    Enum.each(["i'm fine", "all these people out here", "what?"], fn title ->
      Factory.insert!(:post, title: title, channel: other_channel)
    end)

    Enum.each(["i'm not fine", "where are all the people", "okay."], fn title ->
      Factory.insert!(
        :post,
        inserted_at: Timex.to_datetime({2010, 1, 1}),
        title: title,
        channel: other_channel
      )
    end)

    session
    |> sign_in(developer)
    |> visit("/developer/statistics")

    assert(
      session
      |> find(Query.css(".stats_column header", text: "7 posts in 2 channels"))
    )

    channels = find(session, Query.css(".stats_column ul#channels"))
    [other_channel, phoenix_channel] = all(channels, Query.css("li"))

    assert text_without_newlines(other_channel) =~ "#other 6 posts"
    assert text_without_newlines(phoenix_channel) =~ "#phoenix 1 post"

    session
    |> fill_in(Query.css("#start-date"), with: "01-01-2009")
    |> fill_in(Query.css("#end-date"), with: "01-01-2011")
    |> click(Query.css("#filter-submit"))

    assert(
      session
      |> find(Query.css(".stats_column header", text: "3 posts in 1 channel"))
    )

    channels = find(session, Query.css(".stats_column ul#channels"))
    [other_channel] = all(channels, Query.css("li"))

    assert text_without_newlines(other_channel) =~ "#other 3 posts"
  end

  test "does not see sees til activity chart", %{session: session, developer: developer} do
    session
    |> sign_in(developer)
    |> visit("/developer/statistics")

    refute_has(session, Query.css("ul#activity"))
  end
end


reverting id column

reverting id column

defmodule GraphQL.Schema.HelloWorld do
  alias GraphQL.Schema
  alias GraphQL.ObjectType

  def schema do
    %Schema{
      query: %ObjectType{
        name: "HelloWorld",
        fields: %{
          greeting: %{
            type: "String",
            args: %{name: %{type: "String"}},
            resolve: {Schema.HelloWorld, :greeting}
          }
        }
      }
    }
  end

  def greeting(_, %{name: name}, _), do: "Hello, #{name}!"
  def greeting(_, _, _), do: "Hello, world!"
end

defmodule GraphQL.Schema.HelloWorld do
  alias GraphQL.Schema
  alias GraphQL.ObjectType

  def schema do
    %Schema{
      query: %ObjectType{
        name: "HelloWorld",
        fields: %{
          greeting: %{
            type: "String",
            args: %{
              id: %{type: "String"},
              name: %{type: "String"},
            },
            resolve: {Schema.HelloWorld, :greeting}
          }
        }
      }
    }
  end

  def greeting(_, %{name: name}, _), do: "Hello, #{name}!"
  def greeting(_, _, _), do: "Hello, world!"
end


Added TODO for duplicate name rule

Added TODO for duplicate name rule

defmodule Annon.Acceptance.Controllers.PluginsTest do
  @moduledoc false
  use Annon.AcceptanceCase, async: true

  setup do
    api = create_api() |> get_body()
    api_id = get_in(api, ["data", "id"])

    %{api: api, api_id: api_id}
  end

  describe "JWT Plugin" do
    test "create", %{api_id: api_id} do
      jwt_plugin = :jwt_plugin
      |> build_factory_params()

      "apis/#{api_id}/plugins"
      |> put_management_url()
      |> post!(jwt_plugin)
      |> assert_status(201)

      %{
        "data" => [%{
          "name" => "jwt",
          "api_id" => ^api_id
        }
      ]} = "apis/#{api_id}/plugins"
      |> put_management_url()
      |> get!()
      |> get_body()
    end

    test "create with invalid settings", %{api_id: api_id} do
      "apis/#{api_id}/plugins"
      |> put_management_url()
      |> post!(%{})
      |> assert_status(422)

      "apis/#{api_id}/plugins"
      |> put_management_url()
      |> post!(build_invalid_plugin("jwt"))
      |> assert_status(422)

      %{
        "error" => %{
          "invalid" => [%{"entry" => "$.signature", "rules" => [%{"rule" => "cast"}]}]
          # TODO: Entry should be $.settings.signature
        }
      } = "apis/#{api_id}/plugins"
      |> put_management_url()
      |> post!(%{name: "jwt", is_enabled: false, settings: %{"signature" => 1000}})
      |> assert_status(422)
      |> get_body()
    end

    test "create duplicates", %{api_id: api_id} do
      jwt_plugin = :jwt_plugin
      |> build_factory_params()

      "apis/#{api_id}/plugins"
      |> put_management_url()
      |> post!(jwt_plugin)
      |> assert_status(201)

      actual_result = "apis/#{api_id}/plugins"
      |> put_management_url()
      |> post!(jwt_plugin)
      |> assert_status(422)
      |> get_body()
      |> get_in(["error", "invalid"])

      expected_result = [%{
        "entry" => "$.name",
        "entry_type" => "json_data_property",
        "rules" => [%{
          "description" => "has already been taken",
          "params" => [],
          "rule" => nil # TODO: Fix EView/Ecto to set "unique" in this case
        }]
      }]

      assert expected_result == actual_result
    end
  end

  describe "Validator Plugin" do
    test "create", %{api_id: api_id} do
      validator = :validator_plugin
      |> build_factory_params(%{settings: %{
        rules: [%{methods: ["POST", "PUT", "PATCH"], path: ".*", schema: %{}}]
      }})

      "apis/#{api_id}/plugins"
      |> put_management_url()
      |> post!(validator)
      |> assert_status(201)

      %{
        "data" => [%{
          "name" => "validator",
          "api_id" => ^api_id
        }
      ]} = "apis/#{api_id}/plugins"
      |> put_management_url()
      |> get!()
      |> get_body()
    end

    test "create with invalid settings", %{api_id: api_id} do
      "apis/#{api_id}/plugins"
      |> put_management_url()
      |> post!(%{})
      |> assert_status(422)

      "apis/#{api_id}/plugins"
      |> put_management_url()
      |> post!(build_invalid_plugin("validator"))
      |> assert_status(422)

      %{
        "error" => %{
          "invalid" => [%{"entry" => "$.settings", "rules" => [%{"rule" => "cast"}]}]
          # TODO: There are should be more entries with valid paths $.settings.schema
        }
      } = "apis/#{api_id}/plugins"
      |> put_management_url()
      |> post!(%{
        name: "validator",
        is_enabled: false,
        settings: %{
          rules: [%{methods: ["UNKNOWN"], path: 123, schema: nil}]
        }
      })
      |> assert_status(422)
      |> get_body()
    end
  end

  describe "ACL Plugin" do
    test "create", %{api_id: api_id} do
      acl = :acl_plugin
      |> build_factory_params()

      "apis/#{api_id}/plugins"
      |> put_management_url()
      |> post!(acl)
      |> assert_status(201)

      %{
        "data" => [%{
          "name" => "acl",
          "api_id" => ^api_id
        }
      ]} = "apis/#{api_id}/plugins"
      |> put_management_url()
      |> get!()
      |> get_body()
    end

    test "create with invalid settings", %{api_id: api_id} do
      "apis/#{api_id}/plugins"
      |> put_management_url()
      |> post!(%{})
      |> assert_status(422)

      "apis/#{api_id}/plugins"
      |> put_management_url()
      |> post!(build_invalid_plugin("acl"))
      |> assert_status(422)

      %{
        "error" => %{
          "invalid" => [%{"entry" => "$.settings", "rules" => [%{"rule" => "cast"}]}]
          # TODO: Entry should be $.settings.rules[0].scopes
        }
      } = "apis/#{api_id}/plugins"
      |> put_management_url()
      |> post!(%{name: "acl",
        is_enabled: false,
        settings: %{rules: [%{"methods" => ["GET"], "path" => ".*", "scopes" => 100}]}
      })
      |> assert_status(422)
      |> get_body()
    end
  end

  describe "IPRestriction Plugin" do
    test "create whitelist", %{api_id: api_id} do
      ip_restriction = :ip_restriction_plugin
      |> build_factory_params(%{settings: %{whitelist: ["127.0.0.1"]}})

      "apis/#{api_id}/plugins"
      |> put_management_url()
      |> post!(ip_restriction)
      |> assert_status(201)

      %{
        "data" => [%{
          "name" => "ip_restriction",
          "api_id" => ^api_id
        }
      ]} = "apis/#{api_id}/plugins"
      |> put_management_url()
      |> get!()
      |> get_body()
    end

    test "create blacklist", %{api_id: api_id} do
      ip_restriction = :ip_restriction_plugin
      |> build_factory_params(%{settings: %{blacklist: ["127.0.0.1"]}})

      "apis/#{api_id}/plugins"
      |> put_management_url()
      |> post!(ip_restriction)
      |> assert_status(201)

      %{
        "data" => [%{
          "name" => "ip_restriction",
          "api_id" => ^api_id
        }
      ]} = "apis/#{api_id}/plugins"
      |> put_management_url()
      |> get!()
      |> get_body()
    end

    test "create whitelist and blacklist", %{api_id: api_id} do
      ip_restriction = :ip_restriction_plugin
      |> build_factory_params(%{settings: %{whitelist: ["127.0.0.1"], blacklist: ["127.0.0.1"]}})

      "apis/#{api_id}/plugins"
      |> put_management_url()
      |> post!(ip_restriction)
      |> assert_status(201)

      %{
        "data" => [%{
          "name" => "ip_restriction",
          "api_id" => ^api_id
        }
      ]} = "apis/#{api_id}/plugins"
      |> put_management_url()
      |> get!()
      |> get_body()
    end

    test "create with invalid settings", %{api_id: api_id} do
      "apis/#{api_id}/plugins"
      |> put_management_url()
      |> post!(%{})
      |> assert_status(422)

      "apis/#{api_id}/plugins"
      |> put_management_url()
      |> post!(build_invalid_plugin("ip_restriction"))
      |> assert_status(422)

      %{
        "error" => %{
          "invalid" => [%{"entry" => "$.settings", "rules" => [%{"rule" => "cast"}]}]
          # TODO: Entry should be $.settings.blacklist
          # "invalid" => [%{"entry" => "$.settings.ip_whitelis", "rules" => [%{"rule" => "format"}]}]
          # TODO: different fields should not be merged together in one `entry`
        }
      } = "apis/#{api_id}/plugins"
      |> put_management_url()
      |> post!(%{
        name: "ip_restriction",
        is_enabled: false,
        settings: %{"blacklist" => 100} # , "whitelist" => ["127.0.0.256"]
      })
      |> assert_status(422)
      |> get_body()
    end
  end

  describe "Proxy Plugin" do
    test "create", %{api_id: api_id} do
      proxy = :proxy_plugin
      |> build_factory_params(%{settings: %{host: "host.com"}})

      "apis/#{api_id}/plugins"
      |> put_management_url()
      |> post!(proxy)
      |> assert_status(201)

      %{
        "data" => [%{
          "name" => "proxy",
          "api_id" => ^api_id
        }
      ]} = "apis/#{api_id}/plugins"
      |> put_management_url()
      |> get!()
      |> get_body()
    end

    test "create with invalid settings", %{api_id: api_id} do
      "apis/#{api_id}/plugins"
      |> put_management_url()
      |> post!(%{})
      |> assert_status(422)

      "apis/#{api_id}/plugins"
      |> put_management_url()
      |> post!(build_invalid_plugin("proxy"))
      |> assert_status(422)

      %{
        "error" => %{
          "invalid" => [%{"entry" => "$.settings", "rules" => [%{"rule" => "cast"}]}]
          # TODO: Entry should be $.settings.scope
        }
      } = "apis/#{api_id}/plugins"
      |> put_management_url()
      |> post!(%{name: "proxy", is_enabled: false, settings: %{host: "localhost", path: 100}})
      |> assert_status(422)
      |> get_body()
    end
  end

  describe "Idempotency Plugin" do
    test "create", %{api_id: api_id} do
      idempotency = :idempotency_plugin
      |> build_factory_params()

      "apis/#{api_id}/plugins"
      |> put_management_url()
      |> post!(idempotency)
      |> assert_status(201)

      %{
        "data" => [%{
          "name" => "idempotency",
          "api_id" => ^api_id
        }
      ]} = "apis/#{api_id}/plugins"
      |> put_management_url()
      |> get!()
      |> get_body()
    end
  end

  defp build_invalid_plugin(plugin_name) when is_binary(plugin_name) do
    %{
      name: plugin_name,
      is_enabled: false,
      settings: %{"invalid" => "data"}
    }
  end
end

defmodule Annon.Acceptance.Controllers.PluginsTest do
  @moduledoc false
  use Annon.AcceptanceCase, async: true

  setup do
    api = create_api() |> get_body()
    api_id = get_in(api, ["data", "id"])

    %{api: api, api_id: api_id}
  end

  describe "JWT Plugin" do
    test "create", %{api_id: api_id} do
      jwt_plugin = :jwt_plugin
      |> build_factory_params()

      "apis/#{api_id}/plugins"
      |> put_management_url()
      |> post!(jwt_plugin)
      |> assert_status(201)

      %{
        "data" => [%{
          "name" => "jwt",
          "api_id" => ^api_id
        }
      ]} = "apis/#{api_id}/plugins"
      |> put_management_url()
      |> get!()
      |> get_body()
    end

    test "create with invalid settings", %{api_id: api_id} do
      "apis/#{api_id}/plugins"
      |> put_management_url()
      |> post!(%{})
      |> assert_status(422)

      "apis/#{api_id}/plugins"
      |> put_management_url()
      |> post!(build_invalid_plugin("jwt"))
      |> assert_status(422)

      %{
        "error" => %{
          "invalid" => [%{"entry" => "$.signature", "rules" => [%{"rule" => "cast"}]}]
          # TODO: Entry should be $.settings.signature
        }
      } = "apis/#{api_id}/plugins"
      |> put_management_url()
      |> post!(%{name: "jwt", is_enabled: false, settings: %{"signature" => 1000}})
      |> assert_status(422)
      |> get_body()
    end

    test "create duplicates", %{api_id: api_id} do
      jwt_plugin = :jwt_plugin
      |> build_factory_params()

      "apis/#{api_id}/plugins"
      |> put_management_url()
      |> post!(jwt_plugin)
      |> assert_status(201)

      actual_result = "apis/#{api_id}/plugins"
      |> put_management_url()
      |> post!(jwt_plugin)
      |> assert_status(422)
      |> get_body()
      |> get_in(["error", "invalid"])

      expected_result = [%{
        "entry" => "$.name",
        "entry_type" => "json_data_property",
        "rules" => [%{
          "description" => "has already been taken",
          "params" => [],
          "rule" => "unique"
        }]
      }]

      assert expected_result == actual_result
    end
  end

  describe "Validator Plugin" do
    test "create", %{api_id: api_id} do
      validator = :validator_plugin
      |> build_factory_params(%{settings: %{
        rules: [%{methods: ["POST", "PUT", "PATCH"], path: ".*", schema: %{}}]
      }})

      "apis/#{api_id}/plugins"
      |> put_management_url()
      |> post!(validator)
      |> assert_status(201)

      %{
        "data" => [%{
          "name" => "validator",
          "api_id" => ^api_id
        }
      ]} = "apis/#{api_id}/plugins"
      |> put_management_url()
      |> get!()
      |> get_body()
    end

    test "create with invalid settings", %{api_id: api_id} do
      "apis/#{api_id}/plugins"
      |> put_management_url()
      |> post!(%{})
      |> assert_status(422)

      "apis/#{api_id}/plugins"
      |> put_management_url()
      |> post!(build_invalid_plugin("validator"))
      |> assert_status(422)

      %{
        "error" => %{
          "invalid" => [%{"entry" => "$.settings", "rules" => [%{"rule" => "cast"}]}]
          # TODO: There are should be more entries with valid paths $.settings.schema
        }
      } = "apis/#{api_id}/plugins"
      |> put_management_url()
      |> post!(%{
        name: "validator",
        is_enabled: false,
        settings: %{
          rules: [%{methods: ["UNKNOWN"], path: 123, schema: nil}]
        }
      })
      |> assert_status(422)
      |> get_body()
    end
  end

  describe "ACL Plugin" do
    test "create", %{api_id: api_id} do
      acl = :acl_plugin
      |> build_factory_params()

      "apis/#{api_id}/plugins"
      |> put_management_url()
      |> post!(acl)
      |> assert_status(201)

      %{
        "data" => [%{
          "name" => "acl",
          "api_id" => ^api_id
        }
      ]} = "apis/#{api_id}/plugins"
      |> put_management_url()
      |> get!()
      |> get_body()
    end

    test "create with invalid settings", %{api_id: api_id} do
      "apis/#{api_id}/plugins"
      |> put_management_url()
      |> post!(%{})
      |> assert_status(422)

      "apis/#{api_id}/plugins"
      |> put_management_url()
      |> post!(build_invalid_plugin("acl"))
      |> assert_status(422)

      %{
        "error" => %{
          "invalid" => [%{"entry" => "$.settings", "rules" => [%{"rule" => "cast"}]}]
          # TODO: Entry should be $.settings.rules[0].scopes
        }
      } = "apis/#{api_id}/plugins"
      |> put_management_url()
      |> post!(%{name: "acl",
        is_enabled: false,
        settings: %{rules: [%{"methods" => ["GET"], "path" => ".*", "scopes" => 100}]}
      })
      |> assert_status(422)
      |> get_body()
    end
  end

  describe "IPRestriction Plugin" do
    test "create whitelist", %{api_id: api_id} do
      ip_restriction = :ip_restriction_plugin
      |> build_factory_params(%{settings: %{whitelist: ["127.0.0.1"]}})

      "apis/#{api_id}/plugins"
      |> put_management_url()
      |> post!(ip_restriction)
      |> assert_status(201)

      %{
        "data" => [%{
          "name" => "ip_restriction",
          "api_id" => ^api_id
        }
      ]} = "apis/#{api_id}/plugins"
      |> put_management_url()
      |> get!()
      |> get_body()
    end

    test "create blacklist", %{api_id: api_id} do
      ip_restriction = :ip_restriction_plugin
      |> build_factory_params(%{settings: %{blacklist: ["127.0.0.1"]}})

      "apis/#{api_id}/plugins"
      |> put_management_url()
      |> post!(ip_restriction)
      |> assert_status(201)

      %{
        "data" => [%{
          "name" => "ip_restriction",
          "api_id" => ^api_id
        }
      ]} = "apis/#{api_id}/plugins"
      |> put_management_url()
      |> get!()
      |> get_body()
    end

    test "create whitelist and blacklist", %{api_id: api_id} do
      ip_restriction = :ip_restriction_plugin
      |> build_factory_params(%{settings: %{whitelist: ["127.0.0.1"], blacklist: ["127.0.0.1"]}})

      "apis/#{api_id}/plugins"
      |> put_management_url()
      |> post!(ip_restriction)
      |> assert_status(201)

      %{
        "data" => [%{
          "name" => "ip_restriction",
          "api_id" => ^api_id
        }
      ]} = "apis/#{api_id}/plugins"
      |> put_management_url()
      |> get!()
      |> get_body()
    end

    test "create with invalid settings", %{api_id: api_id} do
      "apis/#{api_id}/plugins"
      |> put_management_url()
      |> post!(%{})
      |> assert_status(422)

      "apis/#{api_id}/plugins"
      |> put_management_url()
      |> post!(build_invalid_plugin("ip_restriction"))
      |> assert_status(422)

      %{
        "error" => %{
          "invalid" => [%{"entry" => "$.settings", "rules" => [%{"rule" => "cast"}]}]
          # TODO: Entry should be $.settings.blacklist
          # "invalid" => [%{"entry" => "$.settings.ip_whitelis", "rules" => [%{"rule" => "format"}]}]
          # TODO: different fields should not be merged together in one `entry`
        }
      } = "apis/#{api_id}/plugins"
      |> put_management_url()
      |> post!(%{
        name: "ip_restriction",
        is_enabled: false,
        settings: %{"blacklist" => 100} # , "whitelist" => ["127.0.0.256"]
      })
      |> assert_status(422)
      |> get_body()
    end
  end

  describe "Proxy Plugin" do
    test "create", %{api_id: api_id} do
      proxy = :proxy_plugin
      |> build_factory_params(%{settings: %{host: "host.com"}})

      "apis/#{api_id}/plugins"
      |> put_management_url()
      |> post!(proxy)
      |> assert_status(201)

      %{
        "data" => [%{
          "name" => "proxy",
          "api_id" => ^api_id
        }
      ]} = "apis/#{api_id}/plugins"
      |> put_management_url()
      |> get!()
      |> get_body()
    end

    test "create with invalid settings", %{api_id: api_id} do
      "apis/#{api_id}/plugins"
      |> put_management_url()
      |> post!(%{})
      |> assert_status(422)

      "apis/#{api_id}/plugins"
      |> put_management_url()
      |> post!(build_invalid_plugin("proxy"))
      |> assert_status(422)

      %{
        "error" => %{
          "invalid" => [%{"entry" => "$.settings", "rules" => [%{"rule" => "cast"}]}]
          # TODO: Entry should be $.settings.scope
        }
      } = "apis/#{api_id}/plugins"
      |> put_management_url()
      |> post!(%{name: "proxy", is_enabled: false, settings: %{host: "localhost", path: 100}})
      |> assert_status(422)
      |> get_body()
    end
  end

  describe "Idempotency Plugin" do
    test "create", %{api_id: api_id} do
      idempotency = :idempotency_plugin
      |> build_factory_params()

      "apis/#{api_id}/plugins"
      |> put_management_url()
      |> post!(idempotency)
      |> assert_status(201)

      %{
        "data" => [%{
          "name" => "idempotency",
          "api_id" => ^api_id
        }
      ]} = "apis/#{api_id}/plugins"
      |> put_management_url()
      |> get!()
      |> get_body()
    end
  end

  defp build_invalid_plugin(plugin_name) when is_binary(plugin_name) do
    %{
      name: plugin_name,
      is_enabled: false,
      settings: %{"invalid" => "data"}
    }
  end
end


uses tag for creating connection and seperating test

defmodule CoursePlanner.SettingControllerTest do
  use CoursePlanner.ConnCase

  alias CoursePlanner.SystemVariable
  import CoursePlanner.Factory

  setup(%{user_role: role}) do
    user = build(role)

    conn =
      Phoenix.ConnTest.build_conn()
      |> assign(:current_user, user)
    {:ok, conn: conn}
  end

  describe "settings functionality for coordinator user" do
    @tag user_role: :coordinator
    test "shows chosen resource only for coordinator", %{conn: conn} do
      conn = get conn, setting_path(conn, :show)
      assert html_response(conn, 200) =~ "Show settings"
    end

    @tag user_role: :coordinator
    test "renders form for editing for coordinator", %{conn: conn} do
      conn = get conn, setting_path(conn, :edit)
      assert html_response(conn, 200) =~ "Edit settings"
    end

    @tag user_role: :coordinator
    test "does not update chosen resource and renders errors when data is invalid for coordinator user", %{conn: conn} do
      system_variable = insert(:system_variable)
      updated_params = [{system_variable.id, %{"key" => system_variable.key, "value" => ""}}]

      conn = put conn, setting_path(conn, :update), settings: updated_params
      assert html_response(conn, 200) =~ "Edit settings"
      assert html_response(conn, 200) =~ "can't be blank"
    end

    @tag user_role: :coordinator
    test "updates chosen resource and redirects when data is valid for coordinator user", %{conn: conn} do
      system_variable = insert(:system_variable)
      updating_value = "new program name"
      updated_params = [{system_variable.id, %{"key" => system_variable.key, "value" => updating_value}}]

      conn = put conn, setting_path(conn, :update), settings: updated_params
      assert redirected_to(conn) == setting_path(conn, :show)
      assert Repo.get(SystemVariable, system_variable.id).value == updating_value
    end

    @tag user_role: :coordinator
    test "does not update chosen resource when is not editable", %{conn: conn} do
      insert(:system_variable)
      system_variable = insert(:system_variable, %{editable: false})
      updating_value = "new program name"
      updated_params = [{system_variable.id, %{"key" => system_variable.key, "value" => updating_value}}]

      conn = put conn, setting_path(conn, :update), settings: updated_params

      assert html_response(conn, 403)
      refute Repo.get(SystemVariable, system_variable.id).value == updating_value
    end

    @tag user_role: :coordinator
    test "does not update chosen inexisting resource", %{conn: conn} do
      updated_params = [{"-1", %{"key" => "random key", "value" => "random value"}}]

      conn = put conn, setting_path(conn, :update), settings: updated_params

      assert html_response(conn, 404)
    end
  end

  describe "setting functionality for student user" do
    @tag user_role: :student
    test "student can't see settings", %{conn: conn} do
      conn = get conn, setting_path(conn, :show)
      assert html_response(conn, 403)
    end

    @tag user_role: :student
    test "student cannot edit settings", %{conn: conn} do
      conn = get conn, setting_path(conn, :edit)
      assert html_response(conn, 403)
    end

    @tag user_role: :student
    test "student can't update settings", %{conn: conn} do
      system_variable = insert(:system_variable)
      updated_params = [{system_variable.id, %{"key" => system_variable.key, "value" => "new program name"}}]

      conn = put conn, setting_path(conn, :update), settings: updated_params
      assert html_response(conn, 403)
    end
  end

  describe "settings functionality for teacher user" do
    @tag user_role: :teacher
    test "teacher can't see settings", %{conn: conn} do
      conn = get conn, setting_path(conn, :show)
      assert html_response(conn, 403)
    end

    @tag user_role: :teacher
    test "teacher cannot edit settings", %{conn: conn} do
      conn = get conn, setting_path(conn, :edit)
      assert html_response(conn, 403)
    end

    @tag user_role: :teacher
    test "teacher can't update settings", %{conn: conn} do
      system_variable = insert(:system_variable)
      updated_params = [{system_variable.id, %{"key" => system_variable.key, "value" => "new program name"}}]

      conn = put conn, setting_path(conn, :update), settings: updated_params
      assert html_response(conn, 403)
    end
  end

  describe "settings functionality for volunteer user" do
    @tag user_role: :volunteer
    test "volunteer can't see settings", %{conn: conn} do
      conn = get conn, setting_path(conn, :show)
      assert html_response(conn, 403)
    end

    @tag user_role: :volunteer
    test "volunteer cannot edit settings", %{conn: conn} do
      conn = get conn, setting_path(conn, :edit)
      assert html_response(conn, 403)
    end

    @tag user_role: :volunteer
    test "volunteer can't update settings", %{conn: conn} do
      system_variable = insert(:system_variable)
      updated_params = [{system_variable.id, %{"key" => system_variable.key, "value" => "new program name"}}]

      conn = put conn, setting_path(conn, :update), settings: updated_params
      assert html_response(conn, 403)
    end
  end
end

defmodule CoursePlanner.SettingControllerTest do
  use CoursePlanner.ConnCase

  alias CoursePlanner.SystemVariable
  import CoursePlanner.Factory

  defp login_as(user_type) do
    user = insert(user_type)

    Phoenix.ConnTest.build_conn()
    |> assign(:current_user, user)
  end

  test "shows chosen resource only for coordinator", %{conn: _conn} do
    coordinator_conn = login_as(:coordinator)

    conn = get coordinator_conn, setting_path(coordinator_conn, :show)
    assert html_response(conn, 200) =~ "Show settings"
  end

  test "does not show chosen resource for non coordinator users", %{conn: _conn} do
    student_conn   = login_as(:student)
    teacher_conn   = login_as(:teacher)
    volunteer_conn = login_as(:volunteer)

    conn = get student_conn, setting_path(student_conn, :show)
    assert html_response(conn, 403)

    conn = get teacher_conn, setting_path(teacher_conn, :show)
    assert html_response(conn, 403)

    conn = get volunteer_conn, setting_path(volunteer_conn, :show)
    assert html_response(conn, 403)
  end

  test "renders form for editing for coordinator", %{conn: _conn} do
    coordinator_conn = login_as(:coordinator)

    conn = get coordinator_conn, setting_path(coordinator_conn, :edit)
    assert html_response(conn, 200) =~ "Edit settings"
  end

  test "does not renders form for editing for non coordinator users", %{conn: _conn} do
    student_conn   = login_as(:student)
    teacher_conn   = login_as(:teacher)
    volunteer_conn = login_as(:volunteer)

    conn = get student_conn, setting_path(student_conn, :edit)
    assert html_response(conn, 403)

    conn = get teacher_conn, setting_path(teacher_conn, :edit)
    assert html_response(conn, 403)

    conn = get volunteer_conn, setting_path(volunteer_conn, :edit)
    assert html_response(conn, 403)
  end

  test "does not update chosen resource and redirects when data is valid for non coordinator users", %{conn: _conn} do
    student_conn   = login_as(:student)
    teacher_conn   = login_as(:teacher)
    volunteer_conn = login_as(:volunteer)

    system_variable = insert(:system_variable)
    updated_params = [{system_variable.id, %{"key" => system_variable.key, "value" => "new program name"}}]

    conn = put student_conn, setting_path(student_conn, :update), settings: updated_params
    assert html_response(conn, 403)

    conn = put teacher_conn, setting_path(teacher_conn, :update), setting: updated_params
    assert html_response(conn, 403)

    conn = put volunteer_conn, setting_path(volunteer_conn, :update), setting: updated_params
    assert html_response(conn, 403)
  end

  test "does not update chosen resource and renders errors when data is invalid for coordinator user", %{conn: _conn} do
    coordinator_conn = login_as(:coordinator)

    system_variable = insert(:system_variable)
    updated_params = [{system_variable.id, %{"key" => system_variable.key, "value" => ""}}]

    conn = put coordinator_conn, setting_path(coordinator_conn, :update), settings: updated_params
    assert html_response(conn, 200) =~ "Edit settings"
    assert html_response(conn, 200) =~ "can't be blank"
  end

  test "updates chosen resource and redirects when data is valid for coordinator user", %{conn: _conn} do
    coordinator_conn = login_as(:coordinator)

    system_variable = insert(:system_variable)
    updating_value = "new program name"
    updated_params = [{system_variable.id, %{"key" => system_variable.key, "value" => updating_value}}]

    conn = put coordinator_conn, setting_path(coordinator_conn, :update), settings: updated_params
    assert redirected_to(conn) == setting_path(coordinator_conn, :show)
    assert Repo.get(SystemVariable, system_variable.id).value == updating_value
  end

  test "does not update chosen resource when is not editable", %{conn: _conn} do
    coordinator_conn = login_as(:coordinator)

    insert(:system_variable)
    system_variable = insert(:system_variable, %{editable: false})
    updating_value = "new program name"
    updated_params = [{system_variable.id, %{"key" => system_variable.key, "value" => updating_value}}]

    conn = put coordinator_conn, setting_path(coordinator_conn, :update), settings: updated_params

    assert html_response(conn, 403)
    refute Repo.get(SystemVariable, system_variable.id).value == updating_value
  end

  test "does not update chosen inexisting resource", %{conn: _conn} do
    coordinator_conn = login_as(:coordinator)

    updated_params = [{"-1", %{"key" => "random key", "value" => "random value"}}]

    conn = put coordinator_conn, setting_path(coordinator_conn, :update), settings: updated_params

    assert html_response(conn, 404)
  end
end


Fix tests

Fix tests

defmodule CoursePlanner.StudentControllerTest do
  use CoursePlanner.ConnCase

  alias CoursePlanner.{Repo, User, Students}
  import CoursePlanner.Factory

  @valid_attrs %{name: "some content", email: "valid@email"}
  @invalid_attrs %{}

  setup do
    {:ok, conn: login_as(:coordinator)}
  end

  defp login_as(user_type) do
    user = insert(user_type)

    Phoenix.ConnTest.build_conn()
    |> assign(:current_user, user)
  end

  test "lists all entries on index", %{conn: conn} do
    conn = get conn, student_path(conn, :index)
    assert html_response(conn, 200) =~ "Students"
  end

  test "shows chosen resource", %{conn: conn} do
    student = insert(:student)
    conn = get conn, student_path(conn, :show, student)
    assert html_response(conn, 200) =~ "#{student.name} #{student.family_name}"
  end

  test "renders page not found when id is nonexistent", %{conn: conn} do
    assert_error_sent 404, fn ->
      get conn, student_path(conn, :show, -1)
    end
  end

  test "renders form for editing chosen resource", %{conn: conn} do
    student = Repo.insert! %User{name: "Foo", family_name: "Bar"}
    conn = get conn, student_path(conn, :edit, student)
    assert html_response(conn, 200) =~ "Foo Bar"
  end

  test "updates chosen resource and redirects when data is valid", %{conn: conn} do
    student = Repo.insert! %User{}
    conn = put conn, student_path(conn, :update, student), user: @valid_attrs
    assert redirected_to(conn) == student_path(conn, :show, student)
    assert Repo.get_by(User, @valid_attrs)
  end

  test "does not update chosen resource and renders errors when data is invalid", %{conn: conn} do
    student = Repo.insert! %User{name: "Foo", family_name: "Bar"}
    conn = put conn, student_path(conn, :update, student), user: @invalid_attrs
    assert html_response(conn, 200) =~ "Foo Bar"
  end

  test "deletes chosen resource", %{conn: conn} do
    {:ok, student} = Students.new(@valid_attrs, "whatever")
    conn = delete conn, student_path(conn, :delete, student)
    assert redirected_to(conn) == student_path(conn, :index)
    refute Repo.get(User, student.id)
  end

  test "renders form for new resources", %{conn: conn} do
    conn = get conn, student_path(conn, :new)
    assert html_response(conn, 200) =~ "New student"
  end

  test "does not shows chosen resource for non coordinator user", %{conn: _conn} do
    student_conn   = login_as(:student)
    teacher_conn   = login_as(:teacher)
    volunteer_conn = login_as(:volunteer)

    student = insert(:student)

    conn = get student_conn, student_path(student_conn, :show, student)
    assert html_response(conn, 403)

    conn = get teacher_conn, student_path(teacher_conn, :show, student)
    assert html_response(conn, 403)

    conn = get volunteer_conn, student_path(volunteer_conn, :show, student)
    assert html_response(conn, 403)
  end

  test "does not list entries on index for non coordinator user", %{conn: _conn} do
    student_conn   = login_as(:student)
    teacher_conn   = login_as(:teacher)
    volunteer_conn = login_as(:volunteer)

    conn = get student_conn, student_path(student_conn, :index)
    assert html_response(conn, 403)

    conn = get teacher_conn, student_path(teacher_conn, :index)
    assert html_response(conn, 403)

    conn = get volunteer_conn, student_path(volunteer_conn, :index)
    assert html_response(conn, 403)
  end

  test "does not renders form for editing chosen resource for non coordinator user", %{conn: _conn} do
    student_conn   = login_as(:student)
    teacher_conn   = login_as(:teacher)
    volunteer_conn = login_as(:volunteer)

    student = insert(:student)

    conn = get student_conn, student_path(student_conn, :edit, student)
    assert html_response(conn, 403)

    conn = get teacher_conn, student_path(teacher_conn, :edit, student)
    assert html_response(conn, 403)

    conn = get volunteer_conn, student_path(volunteer_conn, :edit, student)
    assert html_response(conn, 403)
  end

  test "does not delete a chosen resource for non coordinator user", %{conn: _conn} do
    student_conn   = login_as(:student)
    teacher_conn   = login_as(:teacher)
    volunteer_conn = login_as(:volunteer)

    student = insert(:student)

    conn = delete student_conn, student_path(student_conn, :delete, student.id)
    assert html_response(conn, 403)

    conn = delete teacher_conn, student_path(teacher_conn, :delete, student.id)
    assert html_response(conn, 403)

    conn = delete volunteer_conn, student_path(volunteer_conn, :delete, student.id)
    assert html_response(conn, 403)
  end

  test "does not render form for new class for non coordinator user", %{conn: _conn} do
    student_conn   = login_as(:student)
    teacher_conn   = login_as(:teacher)
    volunteer_conn = login_as(:volunteer)

    conn = get student_conn, student_path(student_conn, :new)
    assert html_response(conn, 403)

    conn = get teacher_conn, student_path(teacher_conn, :new)
    assert html_response(conn, 403)

    conn = get volunteer_conn, student_path(volunteer_conn, :new)
    assert html_response(conn, 403)
  end

  test "does not update chosen student for non coordinator use", %{conn: _conn} do
    student_conn   = login_as(:student)
    teacher_conn   = login_as(:teacher)
    volunteer_conn = login_as(:volunteer)

    student = Repo.insert! %User{}

    conn = put student_conn, student_path(student_conn, :update, student), student: @valid_attrs
    assert html_response(conn, 403)

    conn = put teacher_conn, student_path(teacher_conn, :update, student), student: @valid_attrs
    assert html_response(conn, 403)

    conn = put volunteer_conn, student_path(volunteer_conn, :update, student), student: @valid_attrs
    assert html_response(conn, 403)
  end

  test "show the student himself" do
    student = insert(:student)
    student_conn = Phoenix.ConnTest.build_conn()
    |> assign(:current_user, student)

    conn = get student_conn, student_path(student_conn, :show, student)
    assert html_response(conn, 200) =~ "#{student.name} #{student.family_name}"
  end

  test "edit the student himself" do
    student = insert(:student, name: "Foo", family_name: "Bar")
    student_conn = Phoenix.ConnTest.build_conn()
    |> assign(:current_user, student)

    conn = get student_conn, student_path(student_conn, :edit, student)
    assert html_response(conn, 200) =~ "Foo Bar"
  end

  test "update the student himself" do
    student = insert(:student)
    student_conn = Phoenix.ConnTest.build_conn()
    |> assign(:current_user, student)

    conn = put student_conn, student_path(student_conn, :update, student), user: @valid_attrs
    assert redirected_to(conn) == student_path(conn, :show, student)
    assert Repo.get_by(User, @valid_attrs)
  end

end

defmodule CoursePlanner.StudentControllerTest do
  use CoursePlanner.ConnCase

  alias CoursePlanner.{Repo, User, Students}
  import CoursePlanner.Factory

  @valid_attrs %{name: "some content", email: "valid@email"}
  @invalid_attrs %{}

  setup do
    {:ok, conn: login_as(:coordinator)}
  end

  defp login_as(user_type) do
    user = insert(user_type)

    Phoenix.ConnTest.build_conn()
    |> assign(:current_user, user)
  end

  test "lists all entries on index", %{conn: conn} do
    conn = get conn, student_path(conn, :index)
    assert html_response(conn, 200) =~ "Students"
  end

  test "shows chosen resource", %{conn: conn} do
    student = Repo.insert! %User{}
    conn = get conn, student_path(conn, :show, student)
    assert html_response(conn, 200) =~ "Show student"
  end

  test "renders page not found when id is nonexistent", %{conn: conn} do
    assert_error_sent 404, fn ->
      get conn, student_path(conn, :show, -1)
    end
  end

  test "renders form for editing chosen resource", %{conn: conn} do
    student = Repo.insert! %User{name: "Foo", family_name: "Bar"}
    conn = get conn, student_path(conn, :edit, student)
    assert html_response(conn, 200) =~ "Foo Bar"
  end

  test "updates chosen resource and redirects when data is valid", %{conn: conn} do
    student = Repo.insert! %User{}
    conn = put conn, student_path(conn, :update, student), user: @valid_attrs
    assert redirected_to(conn) == student_path(conn, :show, student)
    assert Repo.get_by(User, @valid_attrs)
  end

  test "does not update chosen resource and renders errors when data is invalid", %{conn: conn} do
    student = Repo.insert! %User{name: "Foo", family_name: "Bar"}
    conn = put conn, student_path(conn, :update, student), user: @invalid_attrs
    assert html_response(conn, 200) =~ "Foo Bar"
  end

  test "deletes chosen resource", %{conn: conn} do
    {:ok, student} = Students.new(@valid_attrs, "whatever")
    conn = delete conn, student_path(conn, :delete, student)
    assert redirected_to(conn) == student_path(conn, :index)
    refute Repo.get(User, student.id)
  end

  test "renders form for new resources", %{conn: conn} do
    conn = get conn, student_path(conn, :new)
    assert html_response(conn, 200) =~ "New student"
  end

  test "does not shows chosen resource for non coordinator user", %{conn: _conn} do
    student_conn   = login_as(:student)
    teacher_conn   = login_as(:teacher)
    volunteer_conn = login_as(:volunteer)

    student = insert(:student)

    conn = get student_conn, student_path(student_conn, :show, student)
    assert html_response(conn, 403)

    conn = get teacher_conn, student_path(teacher_conn, :show, student)
    assert html_response(conn, 403)

    conn = get volunteer_conn, student_path(volunteer_conn, :show, student)
    assert html_response(conn, 403)
  end

  test "does not list entries on index for non coordinator user", %{conn: _conn} do
    student_conn   = login_as(:student)
    teacher_conn   = login_as(:teacher)
    volunteer_conn = login_as(:volunteer)

    conn = get student_conn, student_path(student_conn, :index)
    assert html_response(conn, 403)

    conn = get teacher_conn, student_path(teacher_conn, :index)
    assert html_response(conn, 403)

    conn = get volunteer_conn, student_path(volunteer_conn, :index)
    assert html_response(conn, 403)
  end

  test "does not renders form for editing chosen resource for non coordinator user", %{conn: _conn} do
    student_conn   = login_as(:student)
    teacher_conn   = login_as(:teacher)
    volunteer_conn = login_as(:volunteer)

    student = insert(:student)

    conn = get student_conn, student_path(student_conn, :edit, student)
    assert html_response(conn, 403)

    conn = get teacher_conn, student_path(teacher_conn, :edit, student)
    assert html_response(conn, 403)

    conn = get volunteer_conn, student_path(volunteer_conn, :edit, student)
    assert html_response(conn, 403)
  end

  test "does not delete a chosen resource for non coordinator user", %{conn: _conn} do
    student_conn   = login_as(:student)
    teacher_conn   = login_as(:teacher)
    volunteer_conn = login_as(:volunteer)

    student = insert(:student)

    conn = delete student_conn, student_path(student_conn, :delete, student.id)
    assert html_response(conn, 403)

    conn = delete teacher_conn, student_path(teacher_conn, :delete, student.id)
    assert html_response(conn, 403)

    conn = delete volunteer_conn, student_path(volunteer_conn, :delete, student.id)
    assert html_response(conn, 403)
  end

  test "does not render form for new class for non coordinator user", %{conn: _conn} do
    student_conn   = login_as(:student)
    teacher_conn   = login_as(:teacher)
    volunteer_conn = login_as(:volunteer)

    conn = get student_conn, student_path(student_conn, :new)
    assert html_response(conn, 403)

    conn = get teacher_conn, student_path(teacher_conn, :new)
    assert html_response(conn, 403)

    conn = get volunteer_conn, student_path(volunteer_conn, :new)
    assert html_response(conn, 403)
  end

  test "does not update chosen student for non coordinator use", %{conn: _conn} do
    student_conn   = login_as(:student)
    teacher_conn   = login_as(:teacher)
    volunteer_conn = login_as(:volunteer)

    student = Repo.insert! %User{}

    conn = put student_conn, student_path(student_conn, :update, student), student: @valid_attrs
    assert html_response(conn, 403)

    conn = put teacher_conn, student_path(teacher_conn, :update, student), student: @valid_attrs
    assert html_response(conn, 403)

    conn = put volunteer_conn, student_path(volunteer_conn, :update, student), student: @valid_attrs
    assert html_response(conn, 403)
  end

  test "show the student himself" do
    student = insert(:student)
    student_conn = Phoenix.ConnTest.build_conn()
    |> assign(:current_user, student)

    conn = get student_conn, student_path(student_conn, :show, student)
    assert html_response(conn, 200) =~ "Show student"
  end

  test "edit the student himself" do
    student = insert(:student, name: "Foo", family_name: "Bar")
    student_conn = Phoenix.ConnTest.build_conn()
    |> assign(:current_user, student)

    conn = get student_conn, student_path(student_conn, :edit, student)
    assert html_response(conn, 200) =~ "Foo Bar"
  end

  test "update the student himself" do
    student = insert(:student)
    student_conn = Phoenix.ConnTest.build_conn()
    |> assign(:current_user, student)

    conn = put student_conn, student_path(student_conn, :update, student), user: @valid_attrs
    assert redirected_to(conn) == student_path(conn, :show, student)
    assert Repo.get_by(User, @valid_attrs)
  end

end


remove zombie code

remove zombie code

defmodule BsWeb.Endpoint do
  use Phoenix.Endpoint, otp_app: :bs

  socket("/socket", BsWeb.UserSocket)

  # Serve at "/" the static files from "priv/static" directory.
  #
  # You should set gzip to true if you are running phoenix.digest
  # when deploying your static files in production.
  plug(
    Plug.Static,
    at: "/",
    from: :bs,
    gzip: false,
    only: ~w(css fonts audio images js favicon.ico robots.txt)
  )

  # Code reloading can be explicitly enabled under the
  # :code_reloader configuration of your endpoint.
  if code_reloading? do
    socket("/phoenix/live_reload/socket", Phoenix.LiveReloader.Socket)
    plug(Phoenix.LiveReloader)
    plug(Phoenix.CodeReloader)
  end

  plug(Plug.RequestId)
  plug(Plug.Logger)

  plug(
    Plug.Parsers,
    parsers: [:urlencoded, :multipart, :json],
    pass: ["*/*"],
    json_decoder: Poison
  )

  plug(Plug.MethodOverride)
  plug(Plug.Head)

  # The session will be stored in the cookie and signed,
  # this means its contents can be read but not tampered with.
  # Set :encryption_salt if you would also like to encrypt it.
  plug(
    Plug.Session,
    store: :cookie,
    key: "_bs_server_key",
    signing_salt: "FYK+D7zm"
  )

  plug(BsWeb.Router)

  def init(_type, config) do
    {:ok, config} = Confex.Resolver.resolve(config)
  end
end

defmodule BsWeb.Endpoint do
  use Phoenix.Endpoint, otp_app: :bs

  socket("/socket", BsWeb.UserSocket)

  # Serve at "/" the static files from "priv/static" directory.
  #
  # You should set gzip to true if you are running phoenix.digest
  # when deploying your static files in production.
  plug(
    Plug.Static,
    at: "/",
    from: :bs,
    gzip: false,
    only: ~w(css fonts audio images js favicon.ico robots.txt)
  )

  # Code reloading can be explicitly enabled under the
  # :code_reloader configuration of your endpoint.
  if code_reloading? do
    socket("/phoenix/live_reload/socket", Phoenix.LiveReloader.Socket)
    plug(Phoenix.LiveReloader)
    plug(Phoenix.CodeReloader)
  end

  plug(Plug.RequestId)
  plug(Plug.Logger)

  plug(
    Plug.Parsers,
    parsers: [:urlencoded, :multipart, :json],
    pass: ["*/*"],
    json_decoder: Poison
  )

  plug(Plug.MethodOverride)
  plug(Plug.Head)

  # The session will be stored in the cookie and signed,
  # this means its contents can be read but not tampered with.
  # Set :encryption_salt if you would also like to encrypt it.
  plug(
    Plug.Session,
    store: :cookie,
    key: "_bs_server_key",
    signing_salt: "FYK+D7zm"
  )

  plug(BsWeb.Router)

  def init(_type, config) do
    {:ok, config} = Confex.Resolver.resolve(config)

#     unless config[:secret_key_base] do
#       raise "Set SECRET_KEY_BASE environment variable!"
#     end

    {:ok, config}
  end
end


Fix a typo in Dict's docs

Fix a typo in Dict's docs

defmodule Dict do
  @moduledoc ~S"""
  This module specifies the Dict API expected to be
  implemented by different dictionaries. It also provides
  functions that redirect to the underlying Dict, allowing
  a developer to work with different Dict implementations
  using one API.

  To create a new dict, use the `new` functions defined
  by each dict type:

      HashDict.new  #=> creates an empty HashDict

  In the examples below, `dict_impl` means a specific
  `Dict` implementation, for example `HashDict` or `Map`.

  ## Protocols

  Besides implementing the functions in this module, all
  dictionaries are required to implement the `Access`
  protocol:

      iex> dict = dict_impl.new
      iex> dict = Dict.put(dict, :hello, :world)
      iex> dict[:hello]
      :world

  As well as the `Enumerable` and `Collectable` protocols.

  ## Match

  Dictionaries are required to implement all operations
  using the match (`===`) operator.

  ## Default implementation

  Default implementations for some functions in the `Dict` module
  are provided via `use Dict`.

  For example:

      defmodule MyDict do
        use Dict

        # implement required functions (see below)
        # override default implementations if optimization
        # is needed
      end

  The client module must contain the following functions:

    * `delete/2`
    * `fetch/2`
    * `put/3`
    * `reduce/3`
    * `size/1`

  All functions, except `reduce/3`, are required by the Dict behaviour.
  `reduce/3` must be implemented as per the Enumerable protocol.

  Based on these functions, `Dict` generates default implementations
  for the following functions:

    * `drop/2`
    * `equal?/2`
    * `fetch!/2`
    * `get/2`
    * `get/3`
    * `has_key?/2`
    * `keys/1`
    * `merge/2`
    * `merge/3`
    * `pop/2`
    * `pop/3`
    * `put_new/3`
    * `split/2`
    * `take/2`
    * `to_list/1`
    * `update/4`
    * `update!/3`
    * `values/1`

  All of these functions are defined as overridable, so you can provide
  your own implementation if needed.

  Note you can also test your custom module via `Dict`'s doctests:

      defmodule MyDict do
        # ...
      end

      defmodule MyTests do
        use ExUnit.Case
        doctest Dict
        defp dict_impl, do: MyDict
      end

  """

  use Behaviour

  @type key :: any
  @type value :: any
  @type t :: list | map

  defcallback new :: t
  defcallback delete(t, key) :: t
  defcallback drop(t, Enum.t) :: t
  defcallback equal?(t, t) :: boolean
  defcallback get(t, key) :: value
  defcallback get(t, key, value) :: value
  defcallback fetch(t, key) :: {:ok, value} | :error
  defcallback fetch!(t, key) :: value | no_return
  defcallback has_key?(t, key) :: boolean
  defcallback keys(t) :: [key]
  defcallback merge(t, t) :: t
  defcallback merge(t, t, (key, value, value -> value)) :: t
  defcallback pop(t, key) :: {value, t}
  defcallback pop(t, key, value) :: {value, t}
  defcallback put(t, key, value) :: t
  defcallback put_new(t, key, value) :: t
  defcallback size(t) :: non_neg_integer()
  defcallback split(t, Enum.t) :: {t, t}
  defcallback take(t, Enum.t) :: t
  defcallback to_list(t) :: list()
  defcallback update(t, key, value, (value -> value)) :: t
  defcallback update!(t, key, (value -> value)) :: t | no_return
  defcallback values(t) :: list(value)

  defmacro __using__(_) do
    # Use this import to guarantee proper code expansion
    import Kernel, except: [size: 1]

    quote do
      @behaviour Dict

      def get(dict, key, default \\ nil) do
        case fetch(dict, key) do
          {:ok, value} -> value
          :error -> default
        end
      end

      def fetch!(dict, key) do
        case fetch(dict, key) do
          {:ok, value} -> value
          :error -> raise KeyError, key: key, term: dict
        end
      end

      def has_key?(dict, key) do
        match? {:ok, _}, fetch(dict, key)
      end

      def put_new(dict, key, value) do
        case has_key?(dict, key) do
          true  -> dict
          false -> put(dict, key, value)
        end
      end

      def drop(dict, keys) do
        Enum.reduce(keys, dict, &delete(&2, &1))
      end

      def take(dict, keys) do
        Enum.reduce(keys, new, fn key, acc ->
          case fetch(dict, key) do
            {:ok, value} -> put(acc, key, value)
            :error -> acc
          end
        end)
      end

      def to_list(dict) do
        reduce(dict, {:cont, []}, fn
          kv, acc -> {:cont, [kv|acc]}
        end) |> elem(1) |> :lists.reverse
      end

      def keys(dict) do
        reduce(dict, {:cont, []}, fn
          {k, _}, acc -> {:cont, [k|acc]}
        end) |> elem(1) |> :lists.reverse
      end

      def values(dict) do
        reduce(dict, {:cont, []}, fn
          {_, v}, acc -> {:cont, [v|acc]}
        end) |> elem(1) |> :lists.reverse
      end

      def equal?(dict1, dict2) do
        # Use this import to avoid conflicts in the user code
        import Kernel, except: [size: 1]

        case size(dict1) == size(dict2) do
          false -> false
          true  ->
            reduce(dict1, {:cont, true}, fn({k, v}, _acc) ->
              case fetch(dict2, k) do
                {:ok, ^v} -> {:cont, true}
                _ -> {:halt, false}
              end
            end) |> elem(1)
        end
      end

      def merge(dict1, dict2, fun \\ fn(_k, _v1, v2) -> v2 end) do
        # Use this import to avoid conflicts in the user code
        import Kernel, except: [size: 1]

        if size(dict1) < size(dict2) do
          reduce(dict1, {:cont, dict2}, fn {k, v1}, acc ->
            {:cont, update(acc, k, v1, &fun.(k, v1, &1))}
          end)
        else
          reduce(dict2, {:cont, dict1}, fn {k, v2}, acc ->
            {:cont, update(acc, k, v2, &fun.(k, &1, v2))}
          end)
        end |> elem(1)
      end

      def update(dict, key, initial, fun) do
        case fetch(dict, key) do
          {:ok, value} ->
            put(dict, key, fun.(value))
          :error ->
            put(dict, key, initial)
        end
      end

      def update!(dict, key, fun) do
        case fetch(dict, key) do
          {:ok, value} ->
            put(dict, key, fun.(value))
          :error ->
            raise KeyError, key: key, term: dict
        end
      end

      def pop(dict, key, default \\ nil) do
        case fetch(dict, key) do
          {:ok, value} ->
            {value, delete(dict, key)}
          :error ->
            {default, dict}
        end
      end

      def split(dict, keys) do
        Enum.reduce(keys, {new, dict}, fn key, {inc, exc} = acc ->
          case fetch(exc, key) do
            {:ok, value} ->
              {put(inc, key, value), delete(exc, key)}
            :error ->
              acc
          end
        end)
      end

      defoverridable merge: 2, merge: 3, equal?: 2, to_list: 1, keys: 1,
                     values: 1, take: 2, drop: 2, get: 2, get: 3, fetch!: 2,
                     has_key?: 2, put_new: 3, pop: 2, pop: 3, split: 2,
                     update: 4, update!: 3
    end
  end

  defmacrop target(dict) do
    quote do
      case unquote(dict) do
        %{__struct__: x} when is_atom(x) ->
          x
        %{} ->
          Map
        x when is_list(x) ->
          Keyword
        x ->
          unsupported_dict(x)
      end
    end
  end

  @doc """
  Returns a list of all keys in `dict`.
  The keys are not guaranteed to be in any order.

  ## Examples

      iex> dict = Enum.into([a: 1, b: 2], dict_impl.new)
      iex> Enum.sort(Dict.keys(dict))
      [:a,:b]

  """
  @spec keys(t) :: [key]
  def keys(dict) do
    target(dict).keys(dict)
  end

  @doc """
  Returns a list of all values in `dict`.
  The values are not guaranteed to be in any order.

  ## Examples

      iex> dict = Enum.into([a: 1, b: 2], dict_impl.new)
      iex> Enum.sort(Dict.values(dict))
      [1,2]

  """
  @spec values(t) :: [value]
  def values(dict) do
    target(dict).values(dict)
  end

  @doc """
  Returns the number of elements in `dict`.

  ## Examples

      iex> dict = Enum.into([a: 1, b: 2], dict_impl.new)
      iex> Dict.size(dict)
      2

  """
  @spec size(t) :: non_neg_integer
  def size(dict) do
    target(dict).size(dict)
  end

  @doc """
  Returns whether the given `key` exists in the given `dict`.

  ## Examples

      iex> dict = Enum.into([a: 1], dict_impl.new)
      iex> Dict.has_key?(dict, :a)
      true
      iex> Dict.has_key?(dict, :b)
      false

  """
  @spec has_key?(t, key) :: boolean
  def has_key?(dict, key) do
    target(dict).has_key?(dict, key)
  end

  @doc """
  Returns the value associated with `key` in `dict`. If `dict` does not
  contain `key`, returns `default` (or `nil` if not provided).

  ## Examples

      iex> dict = Enum.into([a: 1], dict_impl.new)
      iex> Dict.get(dict, :a)
      1
      iex> Dict.get(dict, :b)
      nil
      iex> Dict.get(dict, :b, 3)
      3
  """
  @spec get(t, key, value) :: value
  def get(dict, key, default \\ nil) do
    target(dict).get(dict, key, default)
  end

  @doc """
  Returns `{:ok, value}` associated with `key` in `dict`.
  If `dict` does not contain `key`, returns `:error`.

  ## Examples

      iex> dict = Enum.into([a: 1], dict_impl.new)
      iex> Dict.fetch(dict, :a)
      {:ok, 1}
      iex> Dict.fetch(dict, :b)
      :error

  """
  @spec fetch(t, key) :: value
  def fetch(dict, key) do
    target(dict).fetch(dict, key)
  end

  @doc """
  Returns the value associated with `key` in `dict`. If `dict` does not
  contain `key`, it raises `KeyError`.

  ## Examples

      iex> dict = Enum.into([a: 1], dict_impl.new)
      iex> Dict.fetch!(dict, :a)
      1

  """
  @spec fetch!(t, key) :: value | no_return
  def fetch!(dict, key) do
    target(dict).fetch!(dict, key)
  end

  @doc """
  Stores the given `value` under `key` in `dict`.
  If `dict` already has `key`, the stored value is replaced by the new one.

  ## Examples

      iex> dict = Enum.into([a: 1, b: 2], dict_impl.new)
      iex> dict = Dict.put(dict, :a, 3)
      iex> Dict.get(dict, :a)
      3

  """
  @spec put(t, key, value) :: t
  def put(dict, key, val) do
    target(dict).put(dict, key, val)
  end

  @doc """
  Puts the given `value` under `key` in `dict` unless `key` already exists.

  ## Examples

      iex> dict = Enum.into([a: 1, b: 2], dict_impl.new)
      iex> dict = Dict.put_new(dict, :a, 3)
      iex> Dict.get(dict, :a)
      1

  """
  @spec put_new(t, key, value) :: t
  def put_new(dict, key, val) do
    target(dict).put_new(dict, key, val)
  end

  @doc """
  Removes the entry stored under the given `key` from `dict`.
  If `dict` does not contain `key`, returns the dictionary unchanged.

  ## Examples

      iex> dict = Enum.into([a: 1, b: 2], dict_impl.new)
      iex> dict = Dict.delete(dict, :a)
      iex> Dict.get(dict, :a)
      nil

      iex> dict = Enum.into([b: 2], dict_impl.new)
      iex> Dict.delete(dict, :a) == dict
      true

  """
  @spec delete(t, key) :: t
  def delete(dict, key) do
    target(dict).delete(dict, key)
  end

  @doc """
  Merges the dict `dict2` into dict `dict1`.

  If one of the `dict2` entries already exists in `dict1`, the
  functions in entries in `dict2` have higher precedence unless a
  function is given to resolve conflicts.

  Notice this function is polymorphic as it merges dicts of any
  type. Each dict implementation also provides a `merge` function,
  but they can only merge dicts of the same type.

  ## Examples

      iex> dict1 = Enum.into([a: 1, b: 2], dict_impl.new)
      iex> dict2 = Enum.into([a: 3, d: 4], dict_impl.new)
      iex> dict = Dict.merge(dict1, dict2)
      iex> [a: Dict.get(dict, :a), b: Dict.get(dict, :b), d: Dict.get(dict, :d)]
      [a: 3, b: 2, d: 4]

      iex> dict1 = Enum.into([a: 1, b: 2], dict_impl.new)
      iex> dict2 = Enum.into([a: 3, d: 4], dict_impl.new)
      iex> dict = Dict.merge(dict1, dict2, fn(_k, v1, v2) ->
      ...>   v1 + v2
      ...> end)
      iex> [a: Dict.get(dict, :a), b: Dict.get(dict, :b), d: Dict.get(dict, :d)]
      [a: 4, b: 2, d: 4]

  """
  @spec merge(t, t, (key, value, value -> value)) :: t
  def merge(dict1, dict2, fun \\ fn(_k, _v1, v2) -> v2 end) do
    target1 = target(dict1)
    target2 = target(dict2)

    if target1 == target2 do
      target1.merge(dict1, dict2, fun)
    else
      Enumerable.reduce(dict2, {:cont, dict1}, fn({k, v}, acc) ->
        {:cont, target1.update(acc, k, v, fn(other) -> fun.(k, other, v) end)}
      end) |> elem(1)
    end
  end

  @doc """
  Returns the value associated with `key` in `dict` as
  well as the `dict` without `key`.

  ## Examples

      iex> dict = Enum.into([a: 1], dict_impl.new)
      iex> {v, dict} = Dict.pop dict, :a
      iex> {v, Enum.sort(dict)}
      {1,[]}

      iex> dict = Enum.into([a: 1], dict_impl.new)
      iex> {v, dict} = Dict.pop dict, :b
      iex> {v, Enum.sort(dict)}
      {nil,[a: 1]}

      iex> dict = Enum.into([a: 1], dict_impl.new)
      iex> {v, dict} = Dict.pop dict, :b, 3
      iex> {v, Enum.sort(dict)}
      {3,[a: 1]}

  """
  @spec pop(t, key, value) :: {value, t}
  def pop(dict, key, default \\ nil) do
    target(dict).pop(dict, key, default)
  end

  @doc """
  Update a value in `dict` by calling `fun` on the value to get a new
  value. An exception is generated if `key` is not present in the dict.

  ## Examples

      iex> dict = Enum.into([a: 1, b: 2], dict_impl.new)
      iex> dict = Dict.update!(dict, :a, fn(val) -> -val end)
      iex> Dict.get(dict, :a)
      -1

  """
  @spec update!(t, key, (value -> value)) :: t
  def update!(dict, key, fun) do
    target(dict).update!(dict, key, fun)
  end

  @doc """
  Update a value in `dict` by calling `fun` on the value to get a new value. If
  `key` is not present in `dict` then `initial` will be stored as the first
  value.

  ## Examples

      iex> dict = Enum.into([a: 1, b: 2], dict_impl.new)
      iex> dict = Dict.update(dict, :c, 3, fn(val) -> -val end)
      iex> Dict.get(dict, :c)
      3

  """
  @spec update(t, key, value, (value -> value)) :: t
  def update(dict, key, initial, fun) do
    target(dict).update(dict, key, initial, fun)
  end

  @doc """
  Returns a tuple of two dicts, where the first dict contains only
  entries from `dict` with keys in `keys`, and the second dict
  contains only entries from `dict` with keys not in `keys`

  Any non-member keys are ignored.

  ## Examples

      iex> dict = Enum.into([a: 1, b: 2, c: 3, d: 4], dict_impl.new)
      iex> {dict1, dict2} = Dict.split(dict, [:a, :c, :e])
      iex> {Dict.to_list(dict1) |> Enum.sort, Dict.to_list(dict2) |> Enum.sort}
      {[a: 1, c: 3], [b: 2, d: 4]}

      iex> dict = Enum.into([], dict_impl.new)
      iex> {dict1, dict2} = Dict.split(dict, [:a, :c])
      iex> {Dict.to_list(dict1), Dict.to_list(dict2)}
      {[], []}

      iex> dict = Enum.into([a: 1, b: 2], dict_impl.new)
      iex> {dict1, dict2} = Dict.split(dict, [:a, :b, :c])
      iex> {Dict.to_list(dict1) |> Enum.sort, Dict.to_list(dict2)}
      {[a: 1, b: 2], []}

  """
  @spec split(t, [key]) :: {t, t}
  def split(dict, keys) do
    target(dict).split(dict, keys)
  end

  @doc """
  Returns a new dict where the given `keys` are removed from `dict`.
  Any non-member keys are ignored.

  ## Examples

      iex> dict = Enum.into([a: 1, b: 2], dict_impl.new)
      iex> dict = Dict.drop(dict, [:a, :c, :d])
      iex> Dict.to_list(dict)
      [b: 2]

      iex> dict = Enum.into([a: 1, b: 2], dict_impl.new)
      iex> dict = Dict.drop(dict, [:c, :d])
      iex> Dict.to_list(dict) |> Enum.sort
      [a: 1, b: 2]

  """
  @spec drop(t, [key]) :: t
  def drop(dict, keys) do
    target(dict).drop(dict, keys)
  end

  @doc """
  Returns a new dict where only the keys in `keys` from `dict` are included.

  Any non-member keys are ignored.

  ## Examples

      iex> dict = Enum.into([a: 1, b: 2], dict_impl.new)
      iex> dict = Dict.take(dict, [:a, :c, :d])
      iex> Dict.to_list(dict)
      [a: 1]
      iex> dict = Dict.take(dict, [:c, :d])
      iex> Dict.to_list(dict)
      []

  """
  @spec take(t, [key]) :: t
  def take(dict, keys) do
    target(dict).take(dict, keys)
  end

  @doc false
  @spec empty(t) :: t
  def empty(dict) do
    target(dict).empty(dict)
  end

  @doc """
  Check if two dicts are equal using `===`.

  Notice this function is polymorphic as it compares dicts of any
  type. Each dict implementation also provides an `equal?` function,
  but they can only compare dicts of the same type.

  ## Examples

      iex> dict1 = Enum.into([a: 2, b: 3, f: 5, c: 123], dict_impl.new)
      iex> dict2 = [a: 2, b: 3, f: 5, c: 123]
      iex> Dict.equal?(dict1, dict2)
      true

      iex> dict1 = Enum.into([a: 2, b: 3, f: 5, c: 123], dict_impl.new)
      iex> dict2 = []
      iex> Dict.equal?(dict1, dict2)
      false

  """
  @spec equal?(t, t) :: boolean
  def equal?(dict1, dict2) do
    target1 = target(dict1)
    target2 = target(dict2)

    cond do
      target1 == target2 ->
        target1.equal?(dict1, dict2)

      target1.size(dict1) == target2.size(dict2) ->
        Enumerable.reduce(dict2, {:cont, true}, fn({k, v}, _acc) ->
          case target1.fetch(dict1, k) do
            {:ok, ^v} -> {:cont, true}
            _           -> {:halt, false}
          end
        end) |> elem(1)

      true ->
        false
    end
  end

  @doc """
  Returns a list of key-value pairs stored in `dict`.
  No particular order is enforced.
  """
  @spec to_list(t) :: list
  def to_list(dict) do
    target(dict).to_list(dict)
  end

  defp unsupported_dict(dict) do
    raise ArgumentError, "unsupported dict: #{inspect dict}"
  end
end

defmodule Dict do
  @moduledoc ~S"""
  This module specifies the Dict API expected to be
  implemented by different dictionaries. It also provides
  functions that redirect to the underlying Dict, allowing
  a developer to work with different Dict implementations
  using one API.

  To create a new dict, use the `new` functions defined
  by each dict type:

      HashDict.new  #=> creates an empty HashDict

  In the examples below, `dict_impl` means a specific
  `Dict` implementation, for example `HashDict` or `Map`.

  ## Protocols

  Besides implementing the functions in this module, all
  dictionaries are required to implement the `Access`
  protocol:

      iex> dict = dict_impl.new
      iex> dict = Dict.put(dict, :hello, :world)
      iex> dict[:hello]
      :world

  As well as the `Enumerable` and `Collectable` protocols.

  ## Match

  Dictionaries are required to implement all operations
  using the match (`===`) operator.

  ## Default implementation

  Default implementations for some functions in the `Dict` module
  are provided via `use Dict`.

  For example:

      defmodule MyDict do
        use Dict

        # implement required functions (see below)
        # override default implementations if optimization
        # is needed
      end

  The client module must contain the following functions:

    * `delete/2`
    * `fetch/2`
    * `put/3`
    * `reduce/3`
    * `size/1`

  All functions, except `reduce/3`, are required by the Dict behaviour.
  `reduce/3` must be implemtented as per the Enumerable protocol.

  Based on these functions, `Dict` generates default implementations
  for the following functions:

    * `drop/2`
    * `equal?/2`
    * `fetch!/2`
    * `get/2`
    * `get/3`
    * `has_key?/2`
    * `keys/1`
    * `merge/2`
    * `merge/3`
    * `pop/2`
    * `pop/3`
    * `put_new/3`
    * `split/2`
    * `take/2`
    * `to_list/1`
    * `update/4`
    * `update!/3`
    * `values/1`

  All of these functions are defined as overridable, so you can provide
  your own implementation if needed.

  Note you can also test your custom module via `Dict`'s doctests:

      defmodule MyDict do
        # ...
      end

      defmodule MyTests do
        use ExUnit.Case
        doctest Dict
        defp dict_impl, do: MyDict
      end

  """

  use Behaviour

  @type key :: any
  @type value :: any
  @type t :: list | map

  defcallback new :: t
  defcallback delete(t, key) :: t
  defcallback drop(t, Enum.t) :: t
  defcallback equal?(t, t) :: boolean
  defcallback get(t, key) :: value
  defcallback get(t, key, value) :: value
  defcallback fetch(t, key) :: {:ok, value} | :error
  defcallback fetch!(t, key) :: value | no_return
  defcallback has_key?(t, key) :: boolean
  defcallback keys(t) :: [key]
  defcallback merge(t, t) :: t
  defcallback merge(t, t, (key, value, value -> value)) :: t
  defcallback pop(t, key) :: {value, t}
  defcallback pop(t, key, value) :: {value, t}
  defcallback put(t, key, value) :: t
  defcallback put_new(t, key, value) :: t
  defcallback size(t) :: non_neg_integer()
  defcallback split(t, Enum.t) :: {t, t}
  defcallback take(t, Enum.t) :: t
  defcallback to_list(t) :: list()
  defcallback update(t, key, value, (value -> value)) :: t
  defcallback update!(t, key, (value -> value)) :: t | no_return
  defcallback values(t) :: list(value)

  defmacro __using__(_) do
    # Use this import to guarantee proper code expansion
    import Kernel, except: [size: 1]

    quote do
      @behaviour Dict

      def get(dict, key, default \\ nil) do
        case fetch(dict, key) do
          {:ok, value} -> value
          :error -> default
        end
      end

      def fetch!(dict, key) do
        case fetch(dict, key) do
          {:ok, value} -> value
          :error -> raise KeyError, key: key, term: dict
        end
      end

      def has_key?(dict, key) do
        match? {:ok, _}, fetch(dict, key)
      end

      def put_new(dict, key, value) do
        case has_key?(dict, key) do
          true  -> dict
          false -> put(dict, key, value)
        end
      end

      def drop(dict, keys) do
        Enum.reduce(keys, dict, &delete(&2, &1))
      end

      def take(dict, keys) do
        Enum.reduce(keys, new, fn key, acc ->
          case fetch(dict, key) do
            {:ok, value} -> put(acc, key, value)
            :error -> acc
          end
        end)
      end

      def to_list(dict) do
        reduce(dict, {:cont, []}, fn
          kv, acc -> {:cont, [kv|acc]}
        end) |> elem(1) |> :lists.reverse
      end

      def keys(dict) do
        reduce(dict, {:cont, []}, fn
          {k, _}, acc -> {:cont, [k|acc]}
        end) |> elem(1) |> :lists.reverse
      end

      def values(dict) do
        reduce(dict, {:cont, []}, fn
          {_, v}, acc -> {:cont, [v|acc]}
        end) |> elem(1) |> :lists.reverse
      end

      def equal?(dict1, dict2) do
        # Use this import to avoid conflicts in the user code
        import Kernel, except: [size: 1]

        case size(dict1) == size(dict2) do
          false -> false
          true  ->
            reduce(dict1, {:cont, true}, fn({k, v}, _acc) ->
              case fetch(dict2, k) do
                {:ok, ^v} -> {:cont, true}
                _ -> {:halt, false}
              end
            end) |> elem(1)
        end
      end

      def merge(dict1, dict2, fun \\ fn(_k, _v1, v2) -> v2 end) do
        # Use this import to avoid conflicts in the user code
        import Kernel, except: [size: 1]

        if size(dict1) < size(dict2) do
          reduce(dict1, {:cont, dict2}, fn {k, v1}, acc ->
            {:cont, update(acc, k, v1, &fun.(k, v1, &1))}
          end)
        else
          reduce(dict2, {:cont, dict1}, fn {k, v2}, acc ->
            {:cont, update(acc, k, v2, &fun.(k, &1, v2))}
          end)
        end |> elem(1)
      end

      def update(dict, key, initial, fun) do
        case fetch(dict, key) do
          {:ok, value} ->
            put(dict, key, fun.(value))
          :error ->
            put(dict, key, initial)
        end
      end

      def update!(dict, key, fun) do
        case fetch(dict, key) do
          {:ok, value} ->
            put(dict, key, fun.(value))
          :error ->
            raise KeyError, key: key, term: dict
        end
      end

      def pop(dict, key, default \\ nil) do
        case fetch(dict, key) do
          {:ok, value} ->
            {value, delete(dict, key)}
          :error ->
            {default, dict}
        end
      end

      def split(dict, keys) do
        Enum.reduce(keys, {new, dict}, fn key, {inc, exc} = acc ->
          case fetch(exc, key) do
            {:ok, value} ->
              {put(inc, key, value), delete(exc, key)}
            :error ->
              acc
          end
        end)
      end

      defoverridable merge: 2, merge: 3, equal?: 2, to_list: 1, keys: 1,
                     values: 1, take: 2, drop: 2, get: 2, get: 3, fetch!: 2,
                     has_key?: 2, put_new: 3, pop: 2, pop: 3, split: 2,
                     update: 4, update!: 3
    end
  end

  defmacrop target(dict) do
    quote do
      case unquote(dict) do
        %{__struct__: x} when is_atom(x) ->
          x
        %{} ->
          Map
        x when is_list(x) ->
          Keyword
        x ->
          unsupported_dict(x)
      end
    end
  end

  @doc """
  Returns a list of all keys in `dict`.
  The keys are not guaranteed to be in any order.

  ## Examples

      iex> dict = Enum.into([a: 1, b: 2], dict_impl.new)
      iex> Enum.sort(Dict.keys(dict))
      [:a,:b]

  """
  @spec keys(t) :: [key]
  def keys(dict) do
    target(dict).keys(dict)
  end

  @doc """
  Returns a list of all values in `dict`.
  The values are not guaranteed to be in any order.

  ## Examples

      iex> dict = Enum.into([a: 1, b: 2], dict_impl.new)
      iex> Enum.sort(Dict.values(dict))
      [1,2]

  """
  @spec values(t) :: [value]
  def values(dict) do
    target(dict).values(dict)
  end

  @doc """
  Returns the number of elements in `dict`.

  ## Examples

      iex> dict = Enum.into([a: 1, b: 2], dict_impl.new)
      iex> Dict.size(dict)
      2

  """
  @spec size(t) :: non_neg_integer
  def size(dict) do
    target(dict).size(dict)
  end

  @doc """
  Returns whether the given `key` exists in the given `dict`.

  ## Examples

      iex> dict = Enum.into([a: 1], dict_impl.new)
      iex> Dict.has_key?(dict, :a)
      true
      iex> Dict.has_key?(dict, :b)
      false

  """
  @spec has_key?(t, key) :: boolean
  def has_key?(dict, key) do
    target(dict).has_key?(dict, key)
  end

  @doc """
  Returns the value associated with `key` in `dict`. If `dict` does not
  contain `key`, returns `default` (or `nil` if not provided).

  ## Examples

      iex> dict = Enum.into([a: 1], dict_impl.new)
      iex> Dict.get(dict, :a)
      1
      iex> Dict.get(dict, :b)
      nil
      iex> Dict.get(dict, :b, 3)
      3
  """
  @spec get(t, key, value) :: value
  def get(dict, key, default \\ nil) do
    target(dict).get(dict, key, default)
  end

  @doc """
  Returns `{:ok, value}` associated with `key` in `dict`.
  If `dict` does not contain `key`, returns `:error`.

  ## Examples

      iex> dict = Enum.into([a: 1], dict_impl.new)
      iex> Dict.fetch(dict, :a)
      {:ok, 1}
      iex> Dict.fetch(dict, :b)
      :error

  """
  @spec fetch(t, key) :: value
  def fetch(dict, key) do
    target(dict).fetch(dict, key)
  end

  @doc """
  Returns the value associated with `key` in `dict`. If `dict` does not
  contain `key`, it raises `KeyError`.

  ## Examples

      iex> dict = Enum.into([a: 1], dict_impl.new)
      iex> Dict.fetch!(dict, :a)
      1

  """
  @spec fetch!(t, key) :: value | no_return
  def fetch!(dict, key) do
    target(dict).fetch!(dict, key)
  end

  @doc """
  Stores the given `value` under `key` in `dict`.
  If `dict` already has `key`, the stored value is replaced by the new one.

  ## Examples

      iex> dict = Enum.into([a: 1, b: 2], dict_impl.new)
      iex> dict = Dict.put(dict, :a, 3)
      iex> Dict.get(dict, :a)
      3

  """
  @spec put(t, key, value) :: t
  def put(dict, key, val) do
    target(dict).put(dict, key, val)
  end

  @doc """
  Puts the given `value` under `key` in `dict` unless `key` already exists.

  ## Examples

      iex> dict = Enum.into([a: 1, b: 2], dict_impl.new)
      iex> dict = Dict.put_new(dict, :a, 3)
      iex> Dict.get(dict, :a)
      1

  """
  @spec put_new(t, key, value) :: t
  def put_new(dict, key, val) do
    target(dict).put_new(dict, key, val)
  end

  @doc """
  Removes the entry stored under the given `key` from `dict`.
  If `dict` does not contain `key`, returns the dictionary unchanged.

  ## Examples

      iex> dict = Enum.into([a: 1, b: 2], dict_impl.new)
      iex> dict = Dict.delete(dict, :a)
      iex> Dict.get(dict, :a)
      nil

      iex> dict = Enum.into([b: 2], dict_impl.new)
      iex> Dict.delete(dict, :a) == dict
      true

  """
  @spec delete(t, key) :: t
  def delete(dict, key) do
    target(dict).delete(dict, key)
  end

  @doc """
  Merges the dict `dict2` into dict `dict1`.

  If one of the `dict2` entries already exists in `dict1`, the
  functions in entries in `dict2` have higher precedence unless a
  function is given to resolve conflicts.

  Notice this function is polymorphic as it merges dicts of any
  type. Each dict implementation also provides a `merge` function,
  but they can only merge dicts of the same type.

  ## Examples

      iex> dict1 = Enum.into([a: 1, b: 2], dict_impl.new)
      iex> dict2 = Enum.into([a: 3, d: 4], dict_impl.new)
      iex> dict = Dict.merge(dict1, dict2)
      iex> [a: Dict.get(dict, :a), b: Dict.get(dict, :b), d: Dict.get(dict, :d)]
      [a: 3, b: 2, d: 4]

      iex> dict1 = Enum.into([a: 1, b: 2], dict_impl.new)
      iex> dict2 = Enum.into([a: 3, d: 4], dict_impl.new)
      iex> dict = Dict.merge(dict1, dict2, fn(_k, v1, v2) ->
      ...>   v1 + v2
      ...> end)
      iex> [a: Dict.get(dict, :a), b: Dict.get(dict, :b), d: Dict.get(dict, :d)]
      [a: 4, b: 2, d: 4]

  """
  @spec merge(t, t, (key, value, value -> value)) :: t
  def merge(dict1, dict2, fun \\ fn(_k, _v1, v2) -> v2 end) do
    target1 = target(dict1)
    target2 = target(dict2)

    if target1 == target2 do
      target1.merge(dict1, dict2, fun)
    else
      Enumerable.reduce(dict2, {:cont, dict1}, fn({k, v}, acc) ->
        {:cont, target1.update(acc, k, v, fn(other) -> fun.(k, other, v) end)}
      end) |> elem(1)
    end
  end

  @doc """
  Returns the value associated with `key` in `dict` as
  well as the `dict` without `key`.

  ## Examples

      iex> dict = Enum.into([a: 1], dict_impl.new)
      iex> {v, dict} = Dict.pop dict, :a
      iex> {v, Enum.sort(dict)}
      {1,[]}

      iex> dict = Enum.into([a: 1], dict_impl.new)
      iex> {v, dict} = Dict.pop dict, :b
      iex> {v, Enum.sort(dict)}
      {nil,[a: 1]}

      iex> dict = Enum.into([a: 1], dict_impl.new)
      iex> {v, dict} = Dict.pop dict, :b, 3
      iex> {v, Enum.sort(dict)}
      {3,[a: 1]}

  """
  @spec pop(t, key, value) :: {value, t}
  def pop(dict, key, default \\ nil) do
    target(dict).pop(dict, key, default)
  end

  @doc """
  Update a value in `dict` by calling `fun` on the value to get a new
  value. An exception is generated if `key` is not present in the dict.

  ## Examples

      iex> dict = Enum.into([a: 1, b: 2], dict_impl.new)
      iex> dict = Dict.update!(dict, :a, fn(val) -> -val end)
      iex> Dict.get(dict, :a)
      -1

  """
  @spec update!(t, key, (value -> value)) :: t
  def update!(dict, key, fun) do
    target(dict).update!(dict, key, fun)
  end

  @doc """
  Update a value in `dict` by calling `fun` on the value to get a new value. If
  `key` is not present in `dict` then `initial` will be stored as the first
  value.

  ## Examples

      iex> dict = Enum.into([a: 1, b: 2], dict_impl.new)
      iex> dict = Dict.update(dict, :c, 3, fn(val) -> -val end)
      iex> Dict.get(dict, :c)
      3

  """
  @spec update(t, key, value, (value -> value)) :: t
  def update(dict, key, initial, fun) do
    target(dict).update(dict, key, initial, fun)
  end

  @doc """
  Returns a tuple of two dicts, where the first dict contains only
  entries from `dict` with keys in `keys`, and the second dict
  contains only entries from `dict` with keys not in `keys`

  Any non-member keys are ignored.

  ## Examples

      iex> dict = Enum.into([a: 1, b: 2, c: 3, d: 4], dict_impl.new)
      iex> {dict1, dict2} = Dict.split(dict, [:a, :c, :e])
      iex> {Dict.to_list(dict1) |> Enum.sort, Dict.to_list(dict2) |> Enum.sort}
      {[a: 1, c: 3], [b: 2, d: 4]}

      iex> dict = Enum.into([], dict_impl.new)
      iex> {dict1, dict2} = Dict.split(dict, [:a, :c])
      iex> {Dict.to_list(dict1), Dict.to_list(dict2)}
      {[], []}

      iex> dict = Enum.into([a: 1, b: 2], dict_impl.new)
      iex> {dict1, dict2} = Dict.split(dict, [:a, :b, :c])
      iex> {Dict.to_list(dict1) |> Enum.sort, Dict.to_list(dict2)}
      {[a: 1, b: 2], []}

  """
  @spec split(t, [key]) :: {t, t}
  def split(dict, keys) do
    target(dict).split(dict, keys)
  end

  @doc """
  Returns a new dict where the given `keys` are removed from `dict`.
  Any non-member keys are ignored.

  ## Examples

      iex> dict = Enum.into([a: 1, b: 2], dict_impl.new)
      iex> dict = Dict.drop(dict, [:a, :c, :d])
      iex> Dict.to_list(dict)
      [b: 2]

      iex> dict = Enum.into([a: 1, b: 2], dict_impl.new)
      iex> dict = Dict.drop(dict, [:c, :d])
      iex> Dict.to_list(dict) |> Enum.sort
      [a: 1, b: 2]

  """
  @spec drop(t, [key]) :: t
  def drop(dict, keys) do
    target(dict).drop(dict, keys)
  end

  @doc """
  Returns a new dict where only the keys in `keys` from `dict` are included.

  Any non-member keys are ignored.

  ## Examples

      iex> dict = Enum.into([a: 1, b: 2], dict_impl.new)
      iex> dict = Dict.take(dict, [:a, :c, :d])
      iex> Dict.to_list(dict)
      [a: 1]
      iex> dict = Dict.take(dict, [:c, :d])
      iex> Dict.to_list(dict)
      []

  """
  @spec take(t, [key]) :: t
  def take(dict, keys) do
    target(dict).take(dict, keys)
  end

  @doc false
  @spec empty(t) :: t
  def empty(dict) do
    target(dict).empty(dict)
  end

  @doc """
  Check if two dicts are equal using `===`.

  Notice this function is polymorphic as it compares dicts of any
  type. Each dict implementation also provides an `equal?` function,
  but they can only compare dicts of the same type.

  ## Examples

      iex> dict1 = Enum.into([a: 2, b: 3, f: 5, c: 123], dict_impl.new)
      iex> dict2 = [a: 2, b: 3, f: 5, c: 123]
      iex> Dict.equal?(dict1, dict2)
      true

      iex> dict1 = Enum.into([a: 2, b: 3, f: 5, c: 123], dict_impl.new)
      iex> dict2 = []
      iex> Dict.equal?(dict1, dict2)
      false

  """
  @spec equal?(t, t) :: boolean
  def equal?(dict1, dict2) do
    target1 = target(dict1)
    target2 = target(dict2)

    cond do
      target1 == target2 ->
        target1.equal?(dict1, dict2)

      target1.size(dict1) == target2.size(dict2) ->
        Enumerable.reduce(dict2, {:cont, true}, fn({k, v}, _acc) ->
          case target1.fetch(dict1, k) do
            {:ok, ^v} -> {:cont, true}
            _           -> {:halt, false}
          end
        end) |> elem(1)

      true ->
        false
    end
  end

  @doc """
  Returns a list of key-value pairs stored in `dict`.
  No particular order is enforced.
  """
  @spec to_list(t) :: list
  def to_list(dict) do
    target(dict).to_list(dict)
  end

  defp unsupported_dict(dict) do
    raise ArgumentError, "unsupported dict: #{inspect dict}"
  end
end