Run the code formatter on Mix.UmbrellaTest (#6852)

Code.require_file("../test_helper.exs", __DIR__)

defmodule Mix.UmbrellaTest do
  use MixTest.Case

  @moduletag apps: [:foo, :bar]

  test "apps_paths" do
    in_fixture "umbrella_dep/deps/umbrella", fn ->
      assert Mix.Project.apps_paths() == nil

      Mix.Project.in_project(:umbrella, ".", fn _ ->
        assert Mix.Project.apps_paths() == %{bar: "apps/bar", foo: "apps/foo"}

        assert_received {
          :mix_shell,
          :error,
          ["warning: path \"apps/dont_error_on_missing_mixfile\"" <> _]
        }

        refute_received {:mix_shell, :error, ["warning: path \"apps/dont_error_on_files\"" <> _]}
      end)
    end
  end

  test "apps_paths with selection" do
    in_fixture "umbrella_dep/deps/umbrella", fn ->
      Mix.Project.in_project(:umbrella, ".", [apps: [:foo, :bar]], fn _ ->
        File.mkdir_p!("apps/errors")
        File.write!("apps/errors/mix.exs", "raise :oops")
        assert Mix.Project.apps_paths() == %{bar: "apps/bar", foo: "apps/foo"}
      end)
    end
  end

  test "compiles umbrella" do
    in_fixture "umbrella_dep/deps/umbrella", fn ->
      Mix.Project.in_project(:umbrella, ".", fn _ ->
        Mix.Task.run("deps")
        assert_received {:mix_shell, :info, ["* bar (apps/bar) (mix)"]}
        assert_received {:mix_shell, :info, ["* foo (apps/foo) (mix)"]}

        # Ensure we can compile and run checks
        Mix.Task.run("deps.compile")
        Mix.Task.run("deps.loadpaths")
        Mix.Task.run("compile", ["--verbose"])

        assert_received {:mix_shell, :info, ["==> bar"]}
        assert_received {:mix_shell, :info, ["Generated bar app"]}
        assert File.regular?("_build/dev/lib/bar/ebin/Elixir.Bar.beam")
        assert_received {:mix_shell, :info, ["==> foo"]}
        assert_received {:mix_shell, :info, ["Generated foo app"]}
        assert File.regular?("_build/dev/lib/foo/ebin/Elixir.Foo.beam")

        # Ensure foo was loaded and in the same env as Mix.env
        assert_received {:mix_shell, :info, [":foo env is dev"]}
        assert_received {:mix_shell, :info, [":bar env is dev"]}

        Mix.Task.clear()
        Mix.Task.run("app.start", ["--no-compile"])
      end)
    end
  end

  test "compiles umbrella with protocol consolidation" do
    in_fixture "umbrella_dep/deps/umbrella", fn ->
      Mix.Project.in_project(:umbrella, ".", fn _ ->
        Mix.Task.run("compile", ["--verbose"])
        assert_received {:mix_shell, :info, ["Generated bar app"]}
        assert_received {:mix_shell, :info, ["Generated foo app"]}
        assert File.regular?("_build/dev/consolidated/Elixir.Enumerable.beam")
        purge([Enumerable])

        assert Mix.Tasks.App.Start.run([])
        assert Protocol.consolidated?(Enumerable)
      end)
    end
  end

  test "recursively compiles umbrella with protocol consolidation" do
    in_fixture "umbrella_dep/deps/umbrella", fn ->
      Mix.Project.in_project(:umbrella, ".", fn _ ->
        defmodule Elixir.Mix.Tasks.Umbrella.Recur do
          use Mix.Task
          @recursive true
          def run(_), do: Mix.Task.run("compile", ["--verbose"])
        end

        Mix.Task.run("umbrella.recur")
        assert_received {:mix_shell, :info, ["Generated bar app"]}
        assert_received {:mix_shell, :info, ["Generated foo app"]}
        assert File.regular?("_build/dev/consolidated/Elixir.Enumerable.beam")
        purge([Enumerable])

        assert Mix.Tasks.App.Start.run([])
        assert Protocol.consolidated?(Enumerable)
      end)
    end
  end

  defmodule UmbrellaDeps do
    def project do
      [apps_path: "apps", deps: [{:some_dep, path: "deps/some_dep"}]]
    end
  end

  test "loads umbrella dependencies" do
    Mix.Project.push(UmbrellaDeps)

    in_fixture "umbrella_dep/deps/umbrella", fn ->
      File.mkdir_p!("deps/some_dep/ebin")
      File.mkdir_p!("_build/dev/lib/some_dep/ebin")
      File.mkdir_p!("_build/dev/lib/foo/ebin")
      File.mkdir_p!("_build/dev/lib/bar/ebin")

      Mix.Task.run("loadpaths", ["--no-deps-check", "--no-elixir-version-check"])
      assert to_charlist(Path.expand("_build/dev/lib/some_dep/ebin")) in :code.get_path()
      assert to_charlist(Path.expand("_build/dev/lib/foo/ebin")) in :code.get_path()
      assert to_charlist(Path.expand("_build/dev/lib/bar/ebin")) in :code.get_path()
    end
  end

  test "loads umbrella child dependencies in all environments" do
    in_fixture "umbrella_dep/deps/umbrella", fn ->
      Mix.Project.in_project(:umbrella, ".", fn _ ->
        File.write!("apps/bar/mix.exs", """
        defmodule Bar.MixProject do
          use Mix.Project

          def project do
            [app: :bar,
             version: "0.1.0",
             deps: [{:git_repo, git: MixTest.Case.fixture_path("git_repo"), only: :other}]]
          end
        end
        """)

        # Does not fetch when filtered
        Mix.Tasks.Deps.Get.run(["--only", "dev"])
        refute_received {:mix_shell, :info, ["* Getting git_repo" <> _]}

        # But works across all environments
        Mix.Tasks.Deps.Get.run([])
        assert_received {:mix_shell, :info, ["* Getting git_repo" <> _]}

        # Does not show by default
        Mix.Tasks.Deps.run([])
        refute_received {:mix_shell, :info, ["* git_repo" <> _]}

        # But shows on proper environment
        Mix.env(:other)
        Mix.Tasks.Deps.run([])
        assert_received {:mix_shell, :info, ["* git_repo " <> _]}
      end)
    end
  after
    Mix.env(:test)
  end

  test "loads umbrella child optional dependencies" do
    in_fixture "umbrella_dep/deps/umbrella", fn ->
      Mix.Project.in_project(:umbrella, ".", fn _ ->
        File.write!("apps/bar/mix.exs", """
        defmodule Bar.MixProject do
          use Mix.Project

          def project do
            [app: :bar,
             version: "0.1.0",
             deps: [{:git_repo, git: MixTest.Case.fixture_path("git_repo"), optional: true}]]
          end
        end
        """)

        Mix.Tasks.Deps.run([])
        assert_received {:mix_shell, :info, ["* git_repo " <> _]}
      end)
    end
  end

  test "loads umbrella sibling dependencies with :in_umbrella" do
    in_fixture "umbrella_dep/deps/umbrella", fn ->
      Mix.Project.in_project(:umbrella, ".", fn _ ->
        File.write!("apps/bar/mix.exs", """
        defmodule Bar.MixProject do
          use Mix.Project

          def project do
            [app: :bar,
             version: "0.1.0",
             deps: [{:foo, in_umbrella: true}]]
          end
        end
        """)

        # Running from umbrella should not cause conflicts
        Mix.Tasks.Deps.Get.run([])
        Mix.Tasks.Run.run([])
      end)
    end
  end

  test "finds umbrella sibling dependencies conflicts with :in_umbrella" do
    in_fixture "umbrella_dep/deps/umbrella", fn ->
      Mix.Project.in_project(:umbrella, ".", fn _ ->
        File.write!("apps/bar/mix.exs", """
        defmodule Bar.MixProject do
          use Mix.Project

          def project do
            [app: :bar,
             version: "0.1.0",
             deps: [{:foo, in_umbrella: true, env: :unknown}]]
          end
        end
        """)

        assert_raise Mix.Error, fn ->
          Mix.Tasks.Deps.Get.run([])
        end

        assert_received {:mix_shell, :error, ["Dependencies have diverged:"]}

        assert_received {
          :mix_shell,
          :error,
          ["  the dependency foo in mix.exs is overriding a child dependency" <> _]
        }
      end)
    end
  end

  ## Umbrellas as a dependency

  test "list deps for umbrella as dependency" do
    in_fixture("umbrella_dep", fn ->
      Mix.Project.in_project(:umbrella_dep, ".", fn _ ->
        Mix.Task.run("deps")
        assert_received {:mix_shell, :info, ["* umbrella (deps/umbrella) (mix)"]}
        assert_received {:mix_shell, :info, ["* foo (apps/foo) (mix)"]}
      end)
    end)
  end

  test "compile for umbrella as dependency" do
    in_fixture "umbrella_dep", fn ->
      Mix.Project.in_project(:umbrella_dep, ".", fn _ ->
        Mix.Task.run("deps.compile")
        assert Bar.bar() == "hello world"
      end)
    end
  end

  defmodule CycleDeps do
    def project do
      [
        app: :umbrella_dep,
        deps: [
          {:bar, path: "deps/umbrella/apps/bar"},
          {:umbrella, path: "deps/umbrella"}
        ]
      ]
    end
  end

  test "handles dependencies with cycles" do
    Mix.Project.push(CycleDeps)

    in_fixture "umbrella_dep", fn ->
      assert Enum.map(Mix.Dep.loaded([]), & &1.app) == [:foo, :bar, :umbrella]
    end
  end

  test "handles dependencies with cycles and overridden deps" do
    in_fixture "umbrella_dep/deps/umbrella", fn ->
      Mix.Project.in_project(:umbrella, ".", fn _ ->
        File.write!("apps/foo/mix.exs", """
        defmodule Foo.MixProject do
          use Mix.Project

          def project do
            # Ensure we have the proper environment
            :dev = Mix.env

            [app: :foo,
             version: "0.1.0",
             deps: [{:bar, in_umbrella: true}]]
          end
        end
        """)

        File.write!("apps/bar/mix.exs", """
        defmodule Bar.MixProject do
          use Mix.Project

          def project do
            # Ensure we have the proper environment
            :dev = Mix.env

            [app: :bar,
             version: "0.1.0",
             deps: [{:a, path: "deps/a"},
                    {:b, path: "deps/b"}]]
          end
        end
        """)

        assert Enum.map(Mix.Dep.loaded([]), & &1.app) == [:a, :b, :bar, :foo]
      end)
    end
  end

  test "uses dependency aliases" do
    in_fixture "umbrella_dep/deps/umbrella", fn ->
      Mix.Project.in_project(:umbrella, ".", fn _ ->
        File.write!("apps/bar/mix.exs", """
        defmodule Bar.MixProject do
          use Mix.Project

          def project do
            [app: :bar,
             version: "0.1.0",
             aliases: ["compile.all": fn _ -> Mix.shell.info "no compile bar" end]]
          end
        end
        """)

        Mix.Task.run("compile", ["--verbose"])
        assert_receive {:mix_shell, :info, ["no compile bar"]}
        refute_receive {:mix_shell, :info, ["Compiled lib/bar.ex"]}
      end)
    end
  end

  test "recompiles after path dependency changes" do
    in_fixture("umbrella_dep/deps/umbrella/apps", fn ->
      Mix.Project.in_project(:bar, "bar", fn _ ->
        Mix.Task.run("compile", ["--verbose"])
        assert_received {:mix_shell, :info, ["Generated foo app"]}
        assert_received {:mix_shell, :info, ["Generated bar app"]}
        assert File.regular?("_build/dev/lib/foo/ebin/Elixir.Foo.beam")
        assert File.regular?("_build/dev/lib/bar/ebin/Elixir.Bar.beam")

        # Noop by default
        assert Mix.Tasks.Compile.Elixir.run(["--verbose"]) == {:noop, []}

        # Noop when there is no runtime dependency
        mtime = File.stat!("_build/dev/lib/bar/.compile.elixir").mtime
        ensure_touched("_build/dev/lib/foo/ebin/Elixir.Foo.beam", mtime)

        mtime = File.stat!("_build/dev/lib/bar/.compile.elixir").mtime
        ensure_touched("_build/dev/lib/foo/.compile.elixir", mtime)

        assert Mix.Tasks.Compile.Elixir.run(["--verbose"]) == {:noop, []}

        # Add runtime dependency
        File.write!("lib/bar.ex", """
        defmodule Bar do
          def bar, do: Foo.foo
        end
        """)

        assert Mix.Tasks.Compile.Elixir.run(["--verbose"]) == {:ok, []}
        assert_receive {:mix_shell, :info, ["Compiled lib/bar.ex"]}

        # Noop for runtime dependencies
        mtime = File.stat!("_build/dev/lib/bar/.compile.elixir").mtime
        ensure_touched("_build/dev/lib/foo/ebin/Elixir.Foo.beam", mtime)

        mtime = File.stat!("_build/dev/lib/bar/.compile.elixir").mtime
        ensure_touched("_build/dev/lib/foo/.compile.elixir", mtime)

        assert Mix.Tasks.Compile.Elixir.run(["--verbose"]) == {:noop, []}

        # Add compile time dependency
        File.write!("lib/bar.ex", "defmodule Bar, do: Foo.foo")
        assert Mix.Tasks.Compile.Elixir.run(["--verbose"]) == {:ok, []}
        assert_receive {:mix_shell, :info, ["Compiled lib/bar.ex"]}

        # Recompiles for compile time dependencies
        mtime = File.stat!("_build/dev/lib/bar/.compile.elixir").mtime
        ensure_touched("_build/dev/lib/foo/ebin/Elixir.Foo.beam", mtime)

        mtime = File.stat!("_build/dev/lib/bar/.compile.elixir").mtime
        ensure_touched("_build/dev/lib/foo/.compile.elixir", mtime)

        assert Mix.Tasks.Compile.Elixir.run(["--verbose"]) == {:ok, []}
        assert_receive {:mix_shell, :info, ["Compiled lib/bar.ex"]}
      end)
    end)
  end

  test "reconsolidates after path dependency changes" do
    in_fixture("umbrella_dep/deps/umbrella/apps", fn ->
      Mix.Project.in_project(:bar, "bar", fn _ ->
        # Add a protocol dependency
        File.write!("../foo/lib/foo.ex", """
        defprotocol Foo do
          def foo(arg)
        end
        defimpl Foo, for: List do
          def foo(list), do: list
        end
        """)

        Mix.Task.run("compile")
        assert File.regular?("_build/dev/lib/bar/consolidated/Elixir.Foo.beam")
        assert Mix.Tasks.Compile.Protocols.run([]) == :noop

        # Mark protocol as outdated
        File.touch!("_build/dev/lib/bar/consolidated/Elixir.Foo.beam", {{2010, 1, 1}, {0, 0, 0}})

        mtime = File.stat!("_build/dev/lib/bar/consolidated/.compile.protocols").mtime
        ensure_touched("_build/dev/lib/foo/ebin/Elixir.Foo.beam", mtime)

        assert Mix.Tasks.Compile.Protocols.run([]) == :ok

        # Check new timestamp
        mtime = File.stat!("_build/dev/lib/bar/consolidated/Elixir.Foo.beam").mtime
        assert mtime > {{2010, 1, 1}, {0, 0, 0}}
      end)
    end)
  end

  test "reconsolidates using umbrella parent information on shared _build" do
    in_fixture("umbrella_dep/deps/umbrella", fn ->
      File.write!("apps/bar/lib/bar.ex", """
      defprotocol Bar do
        def bar(arg)
      end
      defimpl Bar, for: List do
        def bar(list), do: list
      end
      """)

      Mix.Project.in_project(:foo, "apps/foo", [build_path: "../../_build"], fn _ ->
        Mix.Task.run("compile.protocols")
        refute Code.ensure_loaded?(Bar)
      end)

      Mix.Project.in_project(:umbrella, ".", fn _ ->
        Mix.Task.run("compile.protocols")
        Mix.Task.run("app.start")
        assert Protocol.consolidated?(Bar)
      end)
    end)
  end

  test "reconsolidates using umbrella child information on shared _build" do
    in_fixture("umbrella_dep/deps/umbrella", fn ->
      File.write!("apps/bar/lib/bar.ex", """
      defprotocol Bar do
        def foo(arg)
      end
      defimpl Bar, for: List do
        def foo(list), do: list
      end
      """)

      Mix.Project.in_project(:umbrella, ".", fn _ ->
        Mix.Task.run("compile.protocols")
      end)

      # Emulate the dependency being removed
      Mix.Project.in_project(:foo, "apps/foo", [build_path: "../../_build", deps: []], fn _ ->
        File.rm_rf("../../_build/dev/lib/bar")
        Mix.Task.run("compile.protocols")
      end)
    end)
  end

  test "apps cannot refer to themselves as a dep" do
    in_fixture("umbrella_dep/deps/umbrella", fn ->
      Mix.Project.in_project(:umbrella, ".", fn _ ->
        File.write!("apps/bar/mix.exs", """
        defmodule Bar.MixProject do
          use Mix.Project

          def project do
            [app: :bar,
             version: "0.1.0",
             deps: [{:bar, in_umbrella: true}]]
          end
        end
        """)

        assert_raise Mix.Error, "App bar lists itself as a dependency", fn ->
          Mix.Task.run("deps.get", ["--verbose"]) == [:ok, :ok]
        end
      end)
    end)
  end
end

Code.require_file "../test_helper.exs", __DIR__

defmodule Mix.UmbrellaTest do
  use MixTest.Case

  @moduletag apps: [:foo, :bar]

  test "apps_paths" do
    in_fixture "umbrella_dep/deps/umbrella", fn ->
      assert Mix.Project.apps_paths == nil
      Mix.Project.in_project(:umbrella, ".", fn _ ->
        assert Mix.Project.apps_paths ==
               %{bar: "apps/bar", foo: "apps/foo"}
        assert_received {:mix_shell, :error, ["warning: path \"apps/dont_error_on_missing_mixfile\"" <> _]}
        refute_received {:mix_shell, :error, ["warning: path \"apps/dont_error_on_files\"" <> _]}
      end)
    end
  end

  test "apps_paths with selection" do
    in_fixture "umbrella_dep/deps/umbrella", fn ->
      Mix.Project.in_project(:umbrella, ".", [apps: [:foo, :bar]], fn _ ->
        File.mkdir_p! "apps/errors"
        File.write! "apps/errors/mix.exs", "raise :oops"
        assert Mix.Project.apps_paths ==
               %{bar: "apps/bar", foo: "apps/foo"}
      end)
    end
  end

  test "compiles umbrella" do
    in_fixture "umbrella_dep/deps/umbrella", fn ->
      Mix.Project.in_project(:umbrella, ".", fn _ ->
        Mix.Task.run "deps"
        assert_received {:mix_shell, :info, ["* bar (apps/bar) (mix)"]}
        assert_received {:mix_shell, :info, ["* foo (apps/foo) (mix)"]}

        # Ensure we can compile and run checks
        Mix.Task.run "deps.compile"
        Mix.Task.run "deps.loadpaths"
        Mix.Task.run "compile", ["--verbose"]

        assert_received {:mix_shell, :info, ["==> bar"]}
        assert_received {:mix_shell, :info, ["Generated bar app"]}
        assert File.regular?("_build/dev/lib/bar/ebin/Elixir.Bar.beam")
        assert_received {:mix_shell, :info, ["==> foo"]}
        assert_received {:mix_shell, :info, ["Generated foo app"]}
        assert File.regular?("_build/dev/lib/foo/ebin/Elixir.Foo.beam")

        # Ensure foo was loaded and in the same env as Mix.env
        assert_received {:mix_shell, :info, [":foo env is dev"]}
        assert_received {:mix_shell, :info, [":bar env is dev"]}

        Mix.Task.clear
        Mix.Task.run "app.start", ["--no-compile"]
      end)
    end
  end

  test "compiles umbrella with protocol consolidation" do
    in_fixture "umbrella_dep/deps/umbrella", fn ->
      Mix.Project.in_project(:umbrella, ".", fn _ ->
        Mix.Task.run "compile", ["--verbose"]
        assert_received {:mix_shell, :info, ["Generated bar app"]}
        assert_received {:mix_shell, :info, ["Generated foo app"]}
        assert File.regular? "_build/dev/consolidated/Elixir.Enumerable.beam"
        purge [Enumerable]

        assert Mix.Tasks.App.Start.run []
        assert Protocol.consolidated?(Enumerable)
      end)
    end
  end

  test "recursively compiles umbrella with protocol consolidation" do
    in_fixture "umbrella_dep/deps/umbrella", fn ->
      Mix.Project.in_project(:umbrella, ".", fn _ ->
        defmodule Elixir.Mix.Tasks.Umbrella.Recur do
          use Mix.Task
          @recursive true
          def run(_), do: Mix.Task.run "compile", ["--verbose"]
        end

        Mix.Task.run "umbrella.recur"
        assert_received {:mix_shell, :info, ["Generated bar app"]}
        assert_received {:mix_shell, :info, ["Generated foo app"]}
        assert File.regular? "_build/dev/consolidated/Elixir.Enumerable.beam"
        purge [Enumerable]

        assert Mix.Tasks.App.Start.run []
        assert Protocol.consolidated?(Enumerable)
      end)
    end
  end

  defmodule UmbrellaDeps do
    def project do
      [apps_path: "apps",
       deps: [{:some_dep, path: "deps/some_dep"}]]
    end
  end

  test "loads umbrella dependencies" do
    Mix.Project.push UmbrellaDeps

    in_fixture "umbrella_dep/deps/umbrella", fn ->
      File.mkdir_p!("deps/some_dep/ebin")
      File.mkdir_p!("_build/dev/lib/some_dep/ebin")
      File.mkdir_p!("_build/dev/lib/foo/ebin")
      File.mkdir_p!("_build/dev/lib/bar/ebin")

      Mix.Task.run "loadpaths", ["--no-deps-check", "--no-elixir-version-check"]
      assert to_charlist(Path.expand("_build/dev/lib/some_dep/ebin")) in :code.get_path
      assert to_charlist(Path.expand("_build/dev/lib/foo/ebin")) in :code.get_path
      assert to_charlist(Path.expand("_build/dev/lib/bar/ebin")) in :code.get_path
    end
  end

  test "loads umbrella child dependencies in all environments" do
    in_fixture "umbrella_dep/deps/umbrella", fn ->
      Mix.Project.in_project :umbrella, ".", fn _ ->
        File.write! "apps/bar/mix.exs", """
        defmodule Bar.MixProject do
          use Mix.Project

          def project do
            [app: :bar,
             version: "0.1.0",
             deps: [{:git_repo, git: MixTest.Case.fixture_path("git_repo"), only: :other}]]
          end
        end
        """

        # Does not fetch when filtered
        Mix.Tasks.Deps.Get.run ["--only", "dev"]
        refute_received {:mix_shell, :info, ["* Getting git_repo" <> _]}

        # But works across all environments
        Mix.Tasks.Deps.Get.run []
        assert_received {:mix_shell, :info, ["* Getting git_repo" <> _]}

        # Does not show by default
        Mix.Tasks.Deps.run []
        refute_received {:mix_shell, :info, ["* git_repo" <> _]}

        # But shows on proper environment
        Mix.env(:other)
        Mix.Tasks.Deps.run []
        assert_received {:mix_shell, :info, ["* git_repo " <> _]}
      end
    end
  after
    Mix.env(:test)
  end

  test "loads umbrella child optional dependencies" do
    in_fixture "umbrella_dep/deps/umbrella", fn ->
      Mix.Project.in_project :umbrella, ".", fn _ ->
        File.write! "apps/bar/mix.exs", """
        defmodule Bar.MixProject do
          use Mix.Project

          def project do
            [app: :bar,
             version: "0.1.0",
             deps: [{:git_repo, git: MixTest.Case.fixture_path("git_repo"), optional: true}]]
          end
        end
        """

        Mix.Tasks.Deps.run []
        assert_received {:mix_shell, :info, ["* git_repo " <> _]}
      end
    end
  end

  test "loads umbrella sibling dependencies with :in_umbrella" do
    in_fixture "umbrella_dep/deps/umbrella", fn ->
      Mix.Project.in_project :umbrella, ".", fn _ ->
        File.write! "apps/bar/mix.exs", """
        defmodule Bar.MixProject do
          use Mix.Project

          def project do
            [app: :bar,
             version: "0.1.0",
             deps: [{:foo, in_umbrella: true}]]
          end
        end
        """

        # Running from umbrella should not cause conflicts
        Mix.Tasks.Deps.Get.run []
        Mix.Tasks.Run.run []
      end
    end
  end

  test "finds umbrella sibling dependencies conflicts with :in_umbrella" do
    in_fixture "umbrella_dep/deps/umbrella", fn ->
      Mix.Project.in_project :umbrella, ".", fn _ ->
        File.write! "apps/bar/mix.exs", """
        defmodule Bar.MixProject do
          use Mix.Project

          def project do
            [app: :bar,
             version: "0.1.0",
             deps: [{:foo, in_umbrella: true, env: :unknown}]]
          end
        end
        """

        assert_raise Mix.Error, fn ->
          Mix.Tasks.Deps.Get.run []
        end

        assert_received {:mix_shell, :error, ["Dependencies have diverged:"]}
        assert_received {:mix_shell, :error, ["  the dependency foo in mix.exs is overriding a child dependency" <> _]}
      end
    end
  end

  ## Umbrellas as a dependency

  test "list deps for umbrella as dependency" do
    in_fixture("umbrella_dep", fn ->
      Mix.Project.in_project(:umbrella_dep, ".", fn _ ->
        Mix.Task.run "deps"
        assert_received {:mix_shell, :info, ["* umbrella (deps/umbrella) (mix)"]}
        assert_received {:mix_shell, :info, ["* foo (apps/foo) (mix)"]}
      end)
    end)
  end

  test "compile for umbrella as dependency" do
    in_fixture "umbrella_dep", fn ->
      Mix.Project.in_project(:umbrella_dep, ".", fn _ ->
        Mix.Task.run "deps.compile"
        assert Bar.bar == "hello world"
      end)
    end
  end

  defmodule CycleDeps do
    def project do
      [app: :umbrella_dep,
       deps: [
         {:bar, path: "deps/umbrella/apps/bar"},
         {:umbrella, path: "deps/umbrella"}
       ]]
    end
  end

  test "handles dependencies with cycles" do
    Mix.Project.push CycleDeps

    in_fixture "umbrella_dep", fn ->
      assert Enum.map(Mix.Dep.loaded([]), & &1.app) == [:foo, :bar, :umbrella]
    end
  end

  test "handles dependencies with cycles and overridden deps" do
    in_fixture "umbrella_dep/deps/umbrella", fn ->
      Mix.Project.in_project :umbrella, ".", fn _ ->
        File.write! "apps/foo/mix.exs", """
        defmodule Foo.MixProject do
          use Mix.Project

          def project do
            # Ensure we have the proper environment
            :dev = Mix.env

            [app: :foo,
             version: "0.1.0",
             deps: [{:bar, in_umbrella: true}]]
          end
        end
        """

        File.write! "apps/bar/mix.exs", """
        defmodule Bar.MixProject do
          use Mix.Project

          def project do
            # Ensure we have the proper environment
            :dev = Mix.env

            [app: :bar,
             version: "0.1.0",
             deps: [{:a, path: "deps/a"},
                    {:b, path: "deps/b"}]]
          end
        end
        """

        assert Enum.map(Mix.Dep.loaded([]), & &1.app) == [:a, :b, :bar, :foo]
      end
    end
  end

  test "uses dependency aliases" do
    in_fixture "umbrella_dep/deps/umbrella", fn ->
      Mix.Project.in_project :umbrella, ".", fn _ ->
        File.write! "apps/bar/mix.exs", """
        defmodule Bar.MixProject do
          use Mix.Project

          def project do
            [app: :bar,
             version: "0.1.0",
             aliases: ["compile.all": fn _ -> Mix.shell.info "no compile bar" end]]
          end
        end
        """

        Mix.Task.run "compile", ["--verbose"]
        assert_receive {:mix_shell, :info, ["no compile bar"]}
        refute_receive {:mix_shell, :info, ["Compiled lib/bar.ex"]}
      end
    end
  end

  test "recompiles after path dependency changes" do
    in_fixture("umbrella_dep/deps/umbrella/apps", fn ->
      Mix.Project.in_project(:bar, "bar", fn _ ->
        Mix.Task.run "compile", ["--verbose"]
        assert_received {:mix_shell, :info, ["Generated foo app"]}
        assert_received {:mix_shell, :info, ["Generated bar app"]}
        assert File.regular?("_build/dev/lib/foo/ebin/Elixir.Foo.beam")
        assert File.regular?("_build/dev/lib/bar/ebin/Elixir.Bar.beam")

        # Noop by default
        assert Mix.Tasks.Compile.Elixir.run(["--verbose"]) == {:noop, []}

        # Noop when there is no runtime dependency
        ensure_touched("_build/dev/lib/foo/ebin/Elixir.Foo.beam",
                       File.stat!("_build/dev/lib/bar/.compile.elixir").mtime)
        ensure_touched("_build/dev/lib/foo/.compile.elixir",
                       File.stat!("_build/dev/lib/bar/.compile.elixir").mtime)
        assert Mix.Tasks.Compile.Elixir.run(["--verbose"]) == {:noop, []}

        # Add runtime dependency
        File.write!("lib/bar.ex", """
        defmodule Bar do
          def bar, do: Foo.foo
        end
        """)
        assert Mix.Tasks.Compile.Elixir.run(["--verbose"]) == {:ok, []}
        assert_receive {:mix_shell, :info, ["Compiled lib/bar.ex"]}

        # Noop for runtime dependencies
        ensure_touched("_build/dev/lib/foo/ebin/Elixir.Foo.beam",
                       File.stat!("_build/dev/lib/bar/.compile.elixir").mtime)
        ensure_touched("_build/dev/lib/foo/.compile.elixir",
                       File.stat!("_build/dev/lib/bar/.compile.elixir").mtime)
        assert Mix.Tasks.Compile.Elixir.run(["--verbose"]) == {:noop, []}

        # Add compile time dependency
        File.write!("lib/bar.ex", "defmodule Bar, do: Foo.foo")
        assert Mix.Tasks.Compile.Elixir.run(["--verbose"]) == {:ok, []}
        assert_receive {:mix_shell, :info, ["Compiled lib/bar.ex"]}

        # Recompiles for compile time dependencies
        ensure_touched("_build/dev/lib/foo/ebin/Elixir.Foo.beam",
                       File.stat!("_build/dev/lib/bar/.compile.elixir").mtime)
        ensure_touched("_build/dev/lib/foo/.compile.elixir",
                       File.stat!("_build/dev/lib/bar/.compile.elixir").mtime)
        assert Mix.Tasks.Compile.Elixir.run(["--verbose"]) == {:ok, []}
        assert_receive {:mix_shell, :info, ["Compiled lib/bar.ex"]}
      end)
    end)
  end

  test "reconsolidates after path dependency changes" do
    in_fixture("umbrella_dep/deps/umbrella/apps", fn ->
      Mix.Project.in_project(:bar, "bar", fn _ ->
        # Add a protocol dependency
        File.write!("../foo/lib/foo.ex", """
        defprotocol Foo do
          def foo(arg)
        end
        defimpl Foo, for: List do
          def foo(list), do: list
        end
        """)
        Mix.Task.run("compile")
        assert File.regular?("_build/dev/lib/bar/consolidated/Elixir.Foo.beam")
        assert Mix.Tasks.Compile.Protocols.run([]) == :noop

        # Mark protocol as outdated
        File.touch!("_build/dev/lib/bar/consolidated/Elixir.Foo.beam",
                    {{2010, 1, 1}, {0, 0, 0}})

        ensure_touched("_build/dev/lib/foo/ebin/Elixir.Foo.beam",
                       File.stat!("_build/dev/lib/bar/consolidated/.compile.protocols").mtime)
        assert Mix.Tasks.Compile.Protocols.run([]) == :ok

        # Check new timestamp
        assert File.stat!("_build/dev/lib/bar/consolidated/Elixir.Foo.beam").mtime >
                          {{2010, 1, 1}, {0, 0, 0}}
      end)
    end)
  end

  test "reconsolidates using umbrella parent information on shared _build" do
    in_fixture("umbrella_dep/deps/umbrella", fn ->
      File.write!("apps/bar/lib/bar.ex", """
      defprotocol Bar do
        def bar(arg)
      end
      defimpl Bar, for: List do
        def bar(list), do: list
      end
      """)

      Mix.Project.in_project(:foo, "apps/foo", [build_path: "../../_build"], fn _ ->
        Mix.Task.run("compile.protocols")
        refute Code.ensure_loaded?(Bar)
      end)

      Mix.Project.in_project(:umbrella, ".", fn _ ->
        Mix.Task.run("compile.protocols")
        Mix.Task.run("app.start")
        assert Protocol.consolidated?(Bar)
      end)
    end)
  end

  test "reconsolidates using umbrella child information on shared _build" do
    in_fixture("umbrella_dep/deps/umbrella", fn ->
      File.write!("apps/bar/lib/bar.ex", """
      defprotocol Bar do
        def foo(arg)
      end
      defimpl Bar, for: List do
        def foo(list), do: list
      end
      """)

      Mix.Project.in_project(:umbrella, ".", fn _ ->
        Mix.Task.run("compile.protocols")
      end)

      # Emulate the dependency being removed
      Mix.Project.in_project(:foo, "apps/foo", [build_path: "../../_build", deps: []], fn _ ->
        File.rm_rf "../../_build/dev/lib/bar"
        Mix.Task.run("compile.protocols")
      end)
    end)
  end

  test "apps cannot refer to themselves as a dep" do
    in_fixture("umbrella_dep/deps/umbrella", fn ->
      Mix.Project.in_project :umbrella, ".", fn _ ->
        File.write! "apps/bar/mix.exs", """
        defmodule Bar.MixProject do
          use Mix.Project

          def project do
            [app: :bar,
             version: "0.1.0",
             deps: [{:bar, in_umbrella: true}]]
          end
        end
        """

        assert_raise Mix.Error, "App bar lists itself as a dependency", fn ->
          Mix.Task.run("deps.get", ["--verbose"]) == [:ok, :ok]
        end
      end
    end)
  end
end


Improve defoverridable test suite (#8802)

Code.require_file("../test_helper.exs", __DIR__)

defmodule Kernel.Overridable do
  def sample do
    1
  end

  def with_super do
    1
  end

  def without_super do
    1
  end

  def super_with_multiple_args(x, y) do
    x + y
  end

  def many_clauses(0) do
    11
  end

  def many_clauses(1) do
    13
  end

  def locals do
    undefined_function()
  end

  def multiple_overrides do
    [1]
  end

  def public_to_private do
    :public
  end

  defoverridable sample: 0,
                 with_super: 0,
                 without_super: 0,
                 super_with_multiple_args: 2,
                 many_clauses: 1,
                 locals: 0,
                 multiple_overrides: 0,
                 public_to_private: 0

  true = Module.overridable?(__MODULE__, {:without_super, 0})
  true = Module.overridable?(__MODULE__, {:with_super, 0})

  def without_super do
    :without_super
  end

  def with_super do
    super() + 2
  end

  def super_with_multiple_args(x, y) do
    super(x, y * 2)
  end

  def many_clauses(2) do
    17
  end

  def many_clauses(3) do
    super(0) + super(1)
  end

  def many_clauses(x) do
    super(x)
  end

  def locals do
    :ok
  end

  def multiple_overrides do
    [2 | super()]
  end

  defp public_to_private do
    :private
  end

  def test_public_to_private do
    public_to_private()
  end

  defoverridable multiple_overrides: 0

  def multiple_overrides do
    [3 | super()]
  end

  ## Macros

  defmacro overridable_macro(x) do
    quote do
      unquote(x) + 100
    end
  end

  defoverridable overridable_macro: 1

  defmacro overridable_macro(x) do
    quote do
      unquote(super(x)) + 1000
    end
  end

  defmacrop private_macro(x \\ raise("never called"))

  defmacrop private_macro(x) do
    quote do
      unquote(x) + 100
    end
  end

  defoverridable private_macro: 1

  defmacrop private_macro(x) do
    quote do
      unquote(super(x)) + 1000
    end
  end

  def private_macro_call(val \\ 11) do
    private_macro(val)
  end
end

defmodule Kernel.OverridableExampleBehaviour do
  @callback required_callback :: any
  @callback optional_callback :: any
  @macrocallback required_macro_callback(arg :: any) :: Macro.t()
  @macrocallback optional_macro_callback(arg :: any, arg2 :: any) :: Macro.t()
  @optional_callbacks optional_callback: 0, optional_macro_callback: 2
end

defmodule Kernel.OverridableTest do
  require Kernel.Overridable, as: Overridable
  use ExUnit.Case

  defp purge(module) do
    :code.purge(module)
    :code.delete(module)
  end

  test "overridable keeps function ordering" do
    defmodule OverridableOrder do
      def not_private(str) do
        process_url(str)
      end

      def process_url(_str) do
        :first
      end

      # There was a bug where the order in which we removed
      # overridable expressions lead to errors. This module
      # aims to guarantee removing process_url/1 before we
      # remove the function that depends on it does not cause
      # errors. If it compiles, it works!
      defoverridable process_url: 1, not_private: 1

      def process_url(_str) do
        :second
      end
    end
  end

  test "overridable works with defaults" do
    defmodule OverridableDefault do
      def fun(value, opt \\ :from_parent) do
        {value, opt}
      end

      defmacro macro(value, opt \\ :from_parent) do
        {{value, opt}, Macro.escape(__CALLER__)}
      end

      # There was a bug where the default function would
      # attempt to call its overridable name instead of
      # func/1. If it compiles, it works!
      defoverridable fun: 1, fun: 2, macro: 1, macro: 2

      def fun(value) do
        {value, super(value)}
      end

      defmacro macro(value) do
        {{value, super(value)}, Macro.escape(__CALLER__)}
      end
    end

    defmodule OverridableCall do
      require OverridableDefault
      OverridableDefault.fun(:foo)
      OverridableDefault.macro(:bar)
    end
  end

  test "overridable is made concrete if no other is defined" do
    assert Overridable.sample() == 1
  end

  test "overridable overridden with super" do
    assert Overridable.with_super() == 3
  end

  test "overridable overridden without super" do
    assert Overridable.without_super() == :without_super
  end

  test "public overridable overridden as private function" do
    assert Overridable.test_public_to_private() == :private
    refute {:public_to_private, 0} in Overridable.module_info(:exports)
  end

  test "overridable locals are ignored without super" do
    assert Overridable.locals() == :ok
  end

  test "calling super with multiple args" do
    assert Overridable.super_with_multiple_args(1, 2) == 5
  end

  test "overridable with many clauses" do
    assert Overridable.many_clauses(0) == 11
    assert Overridable.many_clauses(1) == 13
    assert Overridable.many_clauses(2) == 17
    assert Overridable.many_clauses(3) == 24
  end

  test "overridable definitions are private" do
    refute {:"with_super (overridable 0)", 0} in Overridable.module_info(:exports)
    refute {:"with_super (overridable 1)", 0} in Overridable.module_info(:exports)
  end

  test "multiple overrides" do
    assert Overridable.multiple_overrides() == [3, 2, 1]
  end

  test "overridable macros" do
    a = 11
    assert Overridable.overridable_macro(a) == 1111
    assert Overridable.private_macro_call() == 1111
  end

  test "invalid super call" do
    message =
      "nofile:4: no super defined for foo/0 in module Kernel.OverridableOrder.Forwarding. " <>
        "Overridable functions available are: bar/0"

    assert_raise CompileError, message, fn ->
      Code.eval_string("""
      defmodule Kernel.OverridableOrder.Forwarding do
        def bar(), do: 1
        defoverridable bar: 0
        def foo(), do: super()
      end
      """)
    end

    purge(Kernel.OverridableOrder.Forwarding)
  end

  test "undefined functions can't be marked as overridable" do
    message = "cannot make function foo/2 overridable because it was not defined"

    assert_raise ArgumentError, message, fn ->
      Code.eval_string("""
      defmodule Kernel.OverridableOrder.Foo do
        defoverridable foo: 2
      end
      """)
    end

    purge(Kernel.OverridableOrder.Foo)
  end

  test "overrides with behaviour" do
    defmodule OverridableWithBehaviour do
      @behaviour Elixir.Kernel.OverridableExampleBehaviour

      def required_callback(), do: "original"

      def optional_callback(), do: "original"

      def not_a_behaviour_callback(), do: "original"

      defmacro required_macro_callback(boolean) do
        quote do
          if unquote(boolean) do
            "original"
          end
        end
      end

      defoverridable Elixir.Kernel.OverridableExampleBehaviour

      defmacro optional_macro_callback(arg1, arg2), do: {arg1, arg2}

      assert Module.overridable?(__MODULE__, {:required_callback, 0})
      assert Module.overridable?(__MODULE__, {:optional_callback, 0})
      assert Module.overridable?(__MODULE__, {:required_macro_callback, 1})
      refute Module.overridable?(__MODULE__, {:optional_macro_callback, 1})
      refute Module.overridable?(__MODULE__, {:not_a_behaviour_callback, 1})
    end
  end

  test "undefined module can't be passed as argument to defoverridable" do
    message =
      "cannot pass module Kernel.OverridableTest.Bar as argument to defoverridable/1 because it was not defined"

    assert_raise ArgumentError, message, fn ->
      Code.eval_string("""
      defmodule Kernel.OverridableTest.Foo do
        defoverridable Kernel.OverridableTest.Bar
      end
      """)
    end

    purge(Kernel.OverridableTest.Foo)
  end

  test "module without @behaviour can't be passed as argument to defoverridable" do
    message =
      "cannot pass module Kernel.OverridableExampleBehaviour as argument to defoverridable/1" <>
        " because its corresponding behaviour is missing. Did you forget to add " <>
        "@behaviour Kernel.OverridableExampleBehaviour?"

    assert_raise ArgumentError, message, fn ->
      Code.eval_string("""
      defmodule Kernel.OverridableTest.Foo do
        defoverridable Kernel.OverridableExampleBehaviour
      end
      """)
    end

    purge(Kernel.OverridableTest.Foo)
  end

  test "module with no callbacks can't be passed as argument to defoverridable" do
    message =
      "cannot pass module Kernel.OverridableTest.Bar as argument to defoverridable/1 because it does not define any callbacks"

    assert_raise ArgumentError, message, fn ->
      Code.eval_string("""
      defmodule Kernel.OverridableTest.Bar do
      end
      defmodule Kernel.OverridableTest.Foo do
        @behaviour Kernel.OverridableTest.Bar
        defoverridable Kernel.OverridableTest.Bar
      end
      """)
    end

    purge(Kernel.OverridableTest.Bar)
    purge(Kernel.OverridableTest.Foo)
  end

  test "atom which is not a module can't be passed as argument to defoverridable" do
    message = "cannot pass module :abc as argument to defoverridable/1 because it was not defined"

    assert_raise ArgumentError, message, fn ->
      Code.eval_string("""
      defmodule Kernel.OverridableTest.Foo do
        defoverridable :abc
      end
      """)
    end

    purge(Kernel.OverridableTest.Foo)
  end
end

Code.require_file("../test_helper.exs", __DIR__)

defmodule Kernel.Overridable do
  def sample do
    1
  end

  def with_super do
    1
  end

  def without_super do
    1
  end

  def super_with_multiple_args(x, y) do
    x + y
  end

  def many_clauses(0) do
    11
  end

  def many_clauses(1) do
    13
  end

  defoverridable sample: 0,
                 with_super: 0,
                 without_super: 0,
                 super_with_multiple_args: 2,
                 many_clauses: 1

  true = Module.overridable?(__MODULE__, {:without_super, 0})
  true = Module.overridable?(__MODULE__, {:with_super, 0})

  def without_super do
    :without_super
  end

  def with_super do
    super() + 2
  end

  def super_with_multiple_args(x, y) do
    super(x, y * 2)
  end

  def many_clauses(2) do
    17
  end

  def many_clauses(3) do
    super(0) + super(1)
  end

  def many_clauses(x) do
    super(x)
  end

  ## Macros

  defmacro overridable_macro(x) do
    quote do
      unquote(x) + 100
    end
  end

  defoverridable overridable_macro: 1

  defmacro overridable_macro(x) do
    quote do
      unquote(super(x)) + 1000
    end
  end

  defmacrop private_macro(x \\ raise("never called"))

  defmacrop private_macro(x) do
    quote do
      unquote(x) + 100
    end
  end

  defoverridable private_macro: 1

  defmacrop private_macro(x) do
    quote do
      unquote(super(x)) + 1000
    end
  end

  def private_macro_call(val \\ 11) do
    private_macro(val)
  end
end

defmodule Kernel.OverridableExampleBehaviour do
  @callback required_callback :: any
  @callback optional_callback :: any
  @macrocallback required_macro_callback(arg :: any) :: Macro.t()
  @macrocallback optional_macro_callback(arg :: any, arg2 :: any) :: Macro.t()
  @optional_callbacks optional_callback: 0, optional_macro_callback: 2
end

defmodule Kernel.OverridableTest do
  require Kernel.Overridable, as: Overridable
  use ExUnit.Case

  defp purge(module) do
    :code.purge(module)
    :code.delete(module)
  end

  test "overridable keeps function ordering" do
    defmodule OverridableOrder do
      def not_private(str) do
        process_url(str)
      end

      def process_url(_str) do
        :first
      end

      # There was a bug where the order in which we removed
      # overridable expressions lead to errors. This module
      # aims to guarantee removing process_url/1 before we
      # remove the function that depends on it does not cause
      # errors. If it compiles, it works!
      defoverridable process_url: 1, not_private: 1

      def process_url(_str) do
        :second
      end
    end
  end

  test "overridable works with defaults" do
    defmodule OverridableDefault do
      def fun(value, opt \\ :from_parent) do
        {value, opt}
      end

      defmacro macro(value, opt \\ :from_parent) do
        {{value, opt}, Macro.escape(__CALLER__)}
      end

      # There was a bug where the default function would
      # attempt to call its overridable name instead of
      # func/1. If it compiles, it works!
      defoverridable fun: 1, fun: 2, macro: 1, macro: 2

      def fun(value) do
        {value, super(value)}
      end

      defmacro macro(value) do
        {{value, super(value)}, Macro.escape(__CALLER__)}
      end
    end

    defmodule OverridableCall do
      require OverridableDefault
      OverridableDefault.fun(:foo)
      OverridableDefault.macro(:bar)
    end
  end

  test "overridable is made concrete if no other is defined" do
    assert Overridable.sample() == 1
  end

  test "overridable overridden with super" do
    assert Overridable.with_super() == 3
  end

  test "overridable overridden without super" do
    assert Overridable.without_super() == :without_super
  end

  test "calling super with multiple args" do
    assert Overridable.super_with_multiple_args(1, 2) == 5
  end

  test "overridable with many clauses" do
    assert Overridable.many_clauses(0) == 11
    assert Overridable.many_clauses(1) == 13
    assert Overridable.many_clauses(2) == 17
    assert Overridable.many_clauses(3) == 24
  end

  test "overridable definitions are private" do
    refute {:"with_super (overridable 0)", 0} in Overridable.module_info(:exports)
    refute {:"with_super (overridable 1)", 0} in Overridable.module_info(:exports)
  end

  test "overridable macros" do
    a = 11
    assert Overridable.overridable_macro(a) == 1111
    assert Overridable.private_macro_call() == 1111
  end

  test "invalid super call" do
    message =
      "nofile:4: no super defined for foo/0 in module Kernel.OverridableOrder.Forwarding. " <>
        "Overridable functions available are: bar/0"

    assert_raise CompileError, message, fn ->
      Code.eval_string("""
      defmodule Kernel.OverridableOrder.Forwarding do
        def bar(), do: 1
        defoverridable bar: 0
        def foo(), do: super()
      end
      """)
    end

    purge(Kernel.OverridableOrder.Forwarding)
  end

  test "undefined functions can't be marked as overridable" do
    message = "cannot make function foo/2 overridable because it was not defined"

    assert_raise ArgumentError, message, fn ->
      Code.eval_string("""
      defmodule Kernel.OverridableOrder.Foo do
        defoverridable foo: 2
      end
      """)
    end

    purge(Kernel.OverridableOrder.Foo)
  end

  test "overrides with behaviour" do
    defmodule OverridableWithBehaviour do
      @behaviour Elixir.Kernel.OverridableExampleBehaviour

      def required_callback(), do: "original"

      def optional_callback(), do: "original"

      def not_a_behaviour_callback(), do: "original"

      defmacro required_macro_callback(boolean) do
        quote do
          if unquote(boolean) do
            "original"
          end
        end
      end

      defoverridable Elixir.Kernel.OverridableExampleBehaviour

      defmacro optional_macro_callback(arg1, arg2), do: {arg1, arg2}

      assert Module.overridable?(__MODULE__, {:required_callback, 0})
      assert Module.overridable?(__MODULE__, {:optional_callback, 0})
      assert Module.overridable?(__MODULE__, {:required_macro_callback, 1})
      refute Module.overridable?(__MODULE__, {:optional_macro_callback, 1})
      refute Module.overridable?(__MODULE__, {:not_a_behaviour_callback, 1})
    end
  end

  test "undefined module can't be passed as argument to defoverridable" do
    message =
      "cannot pass module Kernel.OverridableTest.Bar as argument to defoverridable/1 because it was not defined"

    assert_raise ArgumentError, message, fn ->
      Code.eval_string("""
      defmodule Kernel.OverridableTest.Foo do
        defoverridable Kernel.OverridableTest.Bar
      end
      """)
    end

    purge(Kernel.OverridableTest.Foo)
  end

  test "module without @behaviour can't be passed as argument to defoverridable" do
    message =
      "cannot pass module Kernel.OverridableExampleBehaviour as argument to defoverridable/1" <>
        " because its corresponding behaviour is missing. Did you forget to add " <>
        "@behaviour Kernel.OverridableExampleBehaviour?"

    assert_raise ArgumentError, message, fn ->
      Code.eval_string("""
      defmodule Kernel.OverridableTest.Foo do
        defoverridable Kernel.OverridableExampleBehaviour
      end
      """)
    end

    purge(Kernel.OverridableTest.Foo)
  end

  test "module with no callbacks can't be passed as argument to defoverridable" do
    message =
      "cannot pass module Kernel.OverridableTest.Bar as argument to defoverridable/1 because it does not define any callbacks"

    assert_raise ArgumentError, message, fn ->
      Code.eval_string("""
      defmodule Kernel.OverridableTest.Bar do
      end
      defmodule Kernel.OverridableTest.Foo do
        @behaviour Kernel.OverridableTest.Bar
        defoverridable Kernel.OverridableTest.Bar
      end
      """)
    end

    purge(Kernel.OverridableTest.Bar)
    purge(Kernel.OverridableTest.Foo)
  end

  test "atom which is not a module can't be passed as argument to defoverridable" do
    message = "cannot pass module :abc as argument to defoverridable/1 because it was not defined"

    assert_raise ArgumentError, message, fn ->
      Code.eval_string("""
      defmodule Kernel.OverridableTest.Foo do
        defoverridable :abc
      end
      """)
    end

    purge(Kernel.OverridableTest.Foo)
  end
end


Use variables for expected column widths

defmodule Benchee.Formatters.ConsoleTest do
  use ExUnit.Case
  doctest Benchee.Formatters.Console

  alias Benchee.Formatters.Console

  test "sorts the the given stats fastest to slowest" do
    second = {"Second", %{average: 200.0, ips: 5_000.0,
                          std_dev_ratio: 0.1, median: 195.5}}
    third  = {"Third",  %{average: 400.0, ips: 2_500.0,
                          std_dev_ratio: 0.1, median: 375.0}}
    first  = {"First",  %{average: 100.0, ips: 10_000.0,
                          std_dev_ratio: 0.1, median: 90.0}}
    jobs = [second, third, first]

    [_header, result_1, result_2, result_3 | _dont_care ] =
      Console.format(jobs)

    assert Regex.match?(~r/First/,  result_1)
    assert Regex.match?(~r/Second/, result_2)
    assert Regex.match?(~r/Third/,  result_3)
  end

  test "adjusts the label width to longest name" do
    third_name = String.duplicate("a", 40)
    first  = {"First",  %{average: 100.0, ips: 10_000.0,
                          std_dev_ratio: 0.1, median: 90.0}}
    second = {"Second", %{average: 200.0, ips: 5_000.0,
                          std_dev_ratio: 0.1, median: 195.5}}
    third  = {third_name,  %{average: 400.0, ips: 2_500.0,
                             std_dev_ratio: 0.1, median: 375.0}}

    expected_width = 43 # Normally long names, expect default width of 30 + 13
    [header, result_1, result_2 | _dont_care ] = Console.format([first, second])

    assert_column_width "Name", header, expected_width
    assert_column_width "First", result_1, expected_width
    assert_column_width "Second", result_2, expected_width

    expected_width_wide = 54 # Include extra long name, expect width of 41 + 13
    [header, result_1, result_2, result_3 | _dont_care ] = Console.format([first, second, third])

    assert_column_width "Name", header, expected_width_wide
    assert_column_width "First", result_1, expected_width_wide
    assert_column_width "Second", result_2, expected_width_wide
    assert_column_width third_name, result_3, expected_width_wide
  end

  test "creates comparisons" do
    first  = {"First",  %{average: 100.0, ips: 10_000.0,
                          std_dev_ratio: 0.1, median: 90.0}}
    second = {"Second", %{average: 200.0, ips: 5_000.0,
                          std_dev_ratio: 0.1, median: 195.5}}

    jobs = [second, first]

    [_, _, _, comp_header, reference, slower] = Console.format(jobs)

    assert Regex.match? ~r/Comparison/, comp_header
    assert Regex.match? ~r/^First\s+10000.00$/m, reference
    assert Regex.match? ~r/^Second\s+5000.00\s+- 2.00x slower/, slower
  end

  test "adjusts the label width to longest name for comparisons" do
    second_name = String.duplicate("a", 40)
    first  = {"First",  %{average: 100.0, ips: 10_000.0,
                          std_dev_ratio: 0.1, median: 90.0}}
    second  = {second_name,  %{average: 400.0, ips: 2_500.0,
                               std_dev_ratio: 0.1, median: 375.0}}

    expected_width = 54 # With extra long name, expect width of 41 + 13
    [_, _, _, _comp_header, reference, slower] = Console.format([first, second])

    assert_column_width "First", reference, expected_width
    assert_column_width second_name, slower, expected_width
  end

  test "it doesn't create comparisons with only one benchmark run" do
    first  = {"First",  %{average: 100.0, ips: 10_000.0,
                          std_dev_ratio: 0.1, median: 90.0}}

    assert [header, result] = Console.format [first]
    refute Regex.match? ~r/(Comparison|x slower)/, Enum.join([header, result])
  end

  test "It formats small averages and medians more precisely" do
    fast  = {"First",  %{average: 0.15, ips: 10_000.0,
                         std_dev_ratio: 0.1, median: 0.0125}}

    assert [_, result] = Console.format [fast]
    assert Regex.match? ~r/0.150μs/, result
    assert Regex.match? ~r/0.0125μs/, result
  end

  test "it doesn't end in an empty line when there's only on result" do
    fast  = {"First",  %{average: 0.15, ips: 10_000.0,
                         std_dev_ratio: 0.1, median: 0.0125}}

    assert [_, result] = Console.format [fast]

    assert String.last(result) != "\n"
  end

  test "it doesn't end in an empty line with multiple results" do
    first  = {"First",  %{average: 100.0, ips: 10_000.0,
                          std_dev_ratio: 0.1, median: 90.0}}
    second = {"Second", %{average: 200.0, ips: 5_000.0,
                          std_dev_ratio: 0.1, median: 195.5}}

    jobs = [second, first]

    [_, _, _, _comp_header, _reference, slower] = Console.format(jobs)
    assert String.last(slower) != "\n"
  end

  defp assert_column_width(name, string, expected_width) do
    n = Regex.escape name
    regex = Regex.compile! "(#{n} +([0-9\.]+|ips))( |$)"
    assert Regex.match? regex, string
    assert expected_width == Regex.run(regex, string, capture: :all_but_first)
      |> hd() |> String.length()
  end
end

defmodule Benchee.Formatters.ConsoleTest do
  use ExUnit.Case
  doctest Benchee.Formatters.Console

  alias Benchee.Formatters.Console

  test "sorts the the given stats fastest to slowest" do
    second = {"Second", %{average: 200.0, ips: 5_000.0,
                          std_dev_ratio: 0.1, median: 195.5}}
    third  = {"Third",  %{average: 400.0, ips: 2_500.0,
                          std_dev_ratio: 0.1, median: 375.0}}
    first  = {"First",  %{average: 100.0, ips: 10_000.0,
                          std_dev_ratio: 0.1, median: 90.0}}
    jobs = [second, third, first]

    [_header, result_1, result_2, result_3 | _dont_care ] =
      Console.format(jobs)

    assert Regex.match?(~r/First/,  result_1)
    assert Regex.match?(~r/Second/, result_2)
    assert Regex.match?(~r/Third/,  result_3)
  end

  test "adjusts the label width to longest name" do
    third_name = String.duplicate("a", 40)
    first  = {"First",  %{average: 100.0, ips: 10_000.0,
                          std_dev_ratio: 0.1, median: 90.0}}
    second = {"Second", %{average: 200.0, ips: 5_000.0,
                          std_dev_ratio: 0.1, median: 195.5}}
    third  = {third_name,  %{average: 400.0, ips: 2_500.0,
                             std_dev_ratio: 0.1, median: 375.0}}

    # Just normally long names, expect default width of 30 + 13
    [header, result_1, result_2 | _dont_care ] = Console.format([first, second])

    assert_column_width "Name", header, 43
    assert_column_width "First", result_1, 43
    assert_column_width "Second", result_2, 43

    # Include extra long name, expect width of 41 + 13 == 54
    [header, result_1, result_2, result_3 | _dont_care ] = Console.format([first, second, third])

    assert_column_width "Name", header, 54
    assert_column_width "First", result_1, 54
    assert_column_width "Second", result_2, 54
    assert_column_width third_name, result_3, 54
  end

  test "creates comparisons" do
    first  = {"First",  %{average: 100.0, ips: 10_000.0,
                          std_dev_ratio: 0.1, median: 90.0}}
    second = {"Second", %{average: 200.0, ips: 5_000.0,
                          std_dev_ratio: 0.1, median: 195.5}}

    jobs = [second, first]

    [_, _, _, comp_header, reference, slower] = Console.format(jobs)

    assert Regex.match? ~r/Comparison/, comp_header
    assert Regex.match? ~r/^First\s+10000.00$/m, reference
    assert Regex.match? ~r/^Second\s+5000.00\s+- 2.00x slower/, slower
  end

  test "adjusts the label width to longest name for comparisons" do
    second_name = String.duplicate("a", 40)
    first  = {"First",  %{average: 100.0, ips: 10_000.0,
                          std_dev_ratio: 0.1, median: 90.0}}
    second  = {second_name,  %{average: 400.0, ips: 2_500.0,
                               std_dev_ratio: 0.1, median: 375.0}}

    # Include extra long name, expect width of 41 + 13 == 54
    [_, _, _, _comp_header, reference, slower] = Console.format([first, second])

    assert_column_width "First", reference, 54
    assert_column_width second_name, slower, 54
  end

  test "it doesn't create comparisons with only one benchmark run" do
    first  = {"First",  %{average: 100.0, ips: 10_000.0,
                          std_dev_ratio: 0.1, median: 90.0}}

    assert [header, result] = Console.format [first]
    refute Regex.match? ~r/(Comparison|x slower)/, Enum.join([header, result])
  end

  test "It formats small averages and medians more precisely" do
    fast  = {"First",  %{average: 0.15, ips: 10_000.0,
                         std_dev_ratio: 0.1, median: 0.0125}}

    assert [_, result] = Console.format [fast]
    assert Regex.match? ~r/0.150μs/, result
    assert Regex.match? ~r/0.0125μs/, result
  end

  test "it doesn't end in an empty line when there's only on result" do
    fast  = {"First",  %{average: 0.15, ips: 10_000.0,
                         std_dev_ratio: 0.1, median: 0.0125}}

    assert [_, result] = Console.format [fast]

    assert String.last(result) != "\n"
  end

  test "it doesn't end in an empty line with multiple results" do
    first  = {"First",  %{average: 100.0, ips: 10_000.0,
                          std_dev_ratio: 0.1, median: 90.0}}
    second = {"Second", %{average: 200.0, ips: 5_000.0,
                          std_dev_ratio: 0.1, median: 195.5}}

    jobs = [second, first]

    [_, _, _, _comp_header, _reference, slower] = Console.format(jobs)
    assert String.last(slower) != "\n"
  end

  defp assert_column_width(name, string, expected_width) do
    n = Regex.escape name
    regex = Regex.compile! "(#{n} +([0-9\.]+|ips))( |$)"
    assert Regex.match? regex, string
    assert expected_width == Regex.run(regex, string, capture: :all_but_first)
      |> hd() |> String.length()
  end
end


Add test for get_parents

Add test for get_parents

defmodule Glayu.Build.Jobs.BuildSiteTreeTest do

  use ExUnit.Case
  use Timex

  setup_all do
    Glayu.Config.load_config_file("./test/fixtures/build_site_tree/_config.yml", "./test/fixtures/build_site_tree")
    Glayu.Build.Supervisor.start_link()
    SiteHelper.gen_test_site("build_site_tree")

    on_exit fn ->
      File.rm_rf!("./test/fixtures/build_site_tree/source/")
    end

  end

  test "site tree generation" do

    nodes = [
      "./test/fixtures/build_site_tree/source/",
      "./test/fixtures/build_site_tree/source/_posts/business/markets/2017/01/02",
      "./test/fixtures/build_site_tree/source/_posts/business/markets/2017/01/03",
      "./test/fixtures/build_site_tree/source/_posts/business/markets/2017/01/05",
      "./test/fixtures/build_site_tree/source/_posts/business/media/2017/01/01",
      "./test/fixtures/build_site_tree/source/_posts/business/media/2017/01/21",
      "./test/fixtures/build_site_tree/source/_posts/world/americas/2017/01/01",
      "./test/fixtures/build_site_tree/source/_posts/world/americas/2017/01/06",
      "./test/fixtures/build_site_tree/source/_posts/world/europe/2017/01/01",
      "./test/fixtures/build_site_tree/source/_posts/world/europe/2017/01/21"
    ]

    # sort by date desc
    sort_fn = fn doc_context1, doc_context2 ->
      comp = DateTime.compare(doc_context1[:date], doc_context2[:date])
      comp == :gt || comp == :eq
    end

    Glayu.Build.TaskSpawner.spawn(nodes, Glayu.Build.Jobs.BuildSiteTree, [sort_fn: sort_fn, num_posts: 3])

    # categories
    assert Glayu.Build.SiteTree.keys() == [["business"], ["business", "markets"], ["business", "media"], ["world"], ["world", "americas"], ["world", "europe"]]

    pages = Enum.sort(Glayu.Build.SiteTree.pages(), &(&1[:title] <= &2[:title]))
    assert length(pages) == 5
    assert Enum.at(pages,0).title == "Contact"
    assert Enum.at(pages,1).title == "Help"
    assert Enum.at(pages,2).title == "Privacy Policy"
    assert Enum.at(pages,3).title == "Site Map"
    assert Enum.at(pages,4).title == "Terms of Service"

    # tags
    assert Glayu.Build.SiteTree.tags() == ["21 Century Fox", "BIFFEX", "Barcelona", "Barranquilla", "Berlin", "Bertelsmann", "Bilbao", "Bordeaux", "Colombia", "Dow Jones", "Dublin", "France", "Germany", "Grupo Globo", "IBEX 35", "Ireland", "Italy", "Japan", "L.A.", "Lima", "London", "Madrid", "NY", "NYSE Arca", "Nasdaq", "National Amusements", "Nikkey 225", "PRISA", "Paris", "Peru", "Rome", "Sony", "Spain", "Televisa", "US", "Walt Disney"]

    # Node info extraction

    # root node
    root_node = Glayu.Build.SiteTree.get_node(["root"])
    assert root_node.name == "Home"
    assert root_node.path == "/index.html"
    root_children = Glayu.Build.SiteTree.get_children(["root"])
    assert length(root_children) == 2
    assert Enum.at(root_children,0).name == "Business"
    assert Enum.at(root_children,0).path == "/business/index.html"
    assert Enum.at(root_children,1).name == "World"
    assert Enum.at(root_children,1).path == "/world/index.html"

    # business node
    business_node = Glayu.Build.SiteTree.get_node(["business"])
    assert business_node.name == "Business"
    assert business_node.path == "/business/index.html"
    business_parent = Glayu.Build.SiteTree.get_parent(["business"])
    assert business_parent.name == "Home"
    assert business_parent.path == "/index.html"
    business_children = Glayu.Build.SiteTree.get_children(["business"])
    assert length(business_children) == 2
    assert Enum.at(business_children,0).name == "Markets"
    assert Enum.at(business_children,0).path == "/business/markets/index.html"
    assert Enum.at(business_children,1).name == "Media"
    assert Enum.at(business_children,1).path == "/business/media/index.html"

    # media node
    media_node = Glayu.Build.SiteTree.get_node(["business", "media"])
    assert media_node.name == "Media"
    assert media_node.path == "/business/media/index.html"
    media_parent = Glayu.Build.SiteTree.get_parent(["business", "media"])
    assert media_parent.name == "Business"
    assert media_parent.path == "/business/index.html"
    media_parents = Glayu.Build.SiteTree.get_parents(["business", "media"])
    assert length(media_parents) == 2
    assert Enum.at(media_parents, 0).name == "Home"
    assert Enum.at(media_parents, 0).path == "/index.html"
    assert Enum.at(media_parents, 1).name == "Business"
    assert Enum.at(media_parents, 1).path == "/business/index.html"

    media_children = Glayu.Build.SiteTree.get_children(["business", "media"])
    assert length(media_children) == 0

    # Latest posts

    # on root node
    root_posts = Glayu.Build.SiteTree.get(["root", :posts])
    assert length(root_posts) == 3
    assert Enum.at(root_posts,0).title == "Post 25"
    assert Enum.at(root_posts,1).title == "Post 24"
    assert Enum.at(root_posts,2).title == "Post 23"

    # on business node
    business_posts = Glayu.Build.SiteTree.get(["business", :posts])
    assert length(business_posts) == 3
    assert Enum.at(business_posts,0).title == "Post 25"
    assert Enum.at(business_posts,1).title == "Post 21"
    assert Enum.at(business_posts,2).title == "Post 20"

    # on media node
    media_posts = Glayu.Build.SiteTree.get(["business", "media", :posts])
    assert length(media_posts) == 3
    assert Enum.at(media_posts,0).title == "Post 25"
    assert Enum.at(media_posts,1).title == "Post 21"
    assert Enum.at(media_posts,2).title == "Post 20"

  end

end

defmodule Glayu.Build.Jobs.BuildSiteTreeTest do

  use ExUnit.Case
  use Timex

  setup_all do
    Glayu.Config.load_config_file("./test/fixtures/build_site_tree/_config.yml", "./test/fixtures/build_site_tree")
    Glayu.Build.Supervisor.start_link()
    SiteHelper.gen_test_site("build_site_tree")

    on_exit fn ->
      File.rm_rf!("./test/fixtures/build_site_tree/source/")
    end

  end

  test "site tree generation" do

    nodes = [
      "./test/fixtures/build_site_tree/source/",
      "./test/fixtures/build_site_tree/source/_posts/business/markets/2017/01/02",
      "./test/fixtures/build_site_tree/source/_posts/business/markets/2017/01/03",
      "./test/fixtures/build_site_tree/source/_posts/business/markets/2017/01/05",
      "./test/fixtures/build_site_tree/source/_posts/business/media/2017/01/01",
      "./test/fixtures/build_site_tree/source/_posts/business/media/2017/01/21",
      "./test/fixtures/build_site_tree/source/_posts/world/americas/2017/01/01",
      "./test/fixtures/build_site_tree/source/_posts/world/americas/2017/01/06",
      "./test/fixtures/build_site_tree/source/_posts/world/europe/2017/01/01",
      "./test/fixtures/build_site_tree/source/_posts/world/europe/2017/01/21"
    ]

    # sort by date desc
    sort_fn = fn doc_context1, doc_context2 ->
      comp = DateTime.compare(doc_context1[:date], doc_context2[:date])
      comp == :gt || comp == :eq
    end

    Glayu.Build.TaskSpawner.spawn(nodes, Glayu.Build.Jobs.BuildSiteTree, [sort_fn: sort_fn, num_posts: 3])

    # categories
    assert Glayu.Build.SiteTree.keys() == [["business"], ["business", "markets"], ["business", "media"], ["world"], ["world", "americas"], ["world", "europe"]]

    pages = Enum.sort(Glayu.Build.SiteTree.pages(), &(&1[:title] <= &2[:title]))
    assert length(pages) == 5
    assert Enum.at(pages,0).title == "Contact"
    assert Enum.at(pages,1).title == "Help"
    assert Enum.at(pages,2).title == "Privacy Policy"
    assert Enum.at(pages,3).title == "Site Map"
    assert Enum.at(pages,4).title == "Terms of Service"

    # tags
    assert Glayu.Build.SiteTree.tags() == ["21 Century Fox", "BIFFEX", "Barcelona", "Barranquilla", "Berlin", "Bertelsmann", "Bilbao", "Bordeaux", "Colombia", "Dow Jones", "Dublin", "France", "Germany", "Grupo Globo", "IBEX 35", "Ireland", "Italy", "Japan", "L.A.", "Lima", "London", "Madrid", "NY", "NYSE Arca", "Nasdaq", "National Amusements", "Nikkey 225", "PRISA", "Paris", "Peru", "Rome", "Sony", "Spain", "Televisa", "US", "Walt Disney"]

    # Node info extraction

    # root node
    root_node = Glayu.Build.SiteTree.get_node(["root"])
    assert root_node.name == "Home"
    assert root_node.path == "/index.html"
    root_children = Glayu.Build.SiteTree.get_children(["root"])
    assert length(root_children) == 2
    assert Enum.at(root_children,0).name == "Business"
    assert Enum.at(root_children,0).path == "/business/index.html"
    assert Enum.at(root_children,1).name == "World"
    assert Enum.at(root_children,1).path == "/world/index.html"

    # business node
    business_node = Glayu.Build.SiteTree.get_node(["business"])
    assert business_node.name == "Business"
    assert business_node.path == "/business/index.html"
    business_parent = Glayu.Build.SiteTree.get_parent(["business"])
    assert business_parent.name == "Home"
    assert business_parent.path == "/index.html"
    business_children = Glayu.Build.SiteTree.get_children(["business"])
    assert length(business_children) == 2
    assert Enum.at(business_children,0).name == "Markets"
    assert Enum.at(business_children,0).path == "/business/markets/index.html"
    assert Enum.at(business_children,1).name == "Media"
    assert Enum.at(business_children,1).path == "/business/media/index.html"

    # media node
    media_node = Glayu.Build.SiteTree.get_node(["business", "media"])
    assert media_node.name == "Media"
    assert media_node.path == "/business/media/index.html"
    media_parent = Glayu.Build.SiteTree.get_parent(["business", "media"])
    assert media_parent.name == "Business"
    assert media_parent.path == "/business/index.html"
    media_children = Glayu.Build.SiteTree.get_children(["business", "media"])
    assert length(media_children) == 0

    # Latest posts

    # on root node
    root_posts = Glayu.Build.SiteTree.get(["root", :posts])
    assert length(root_posts) == 3
    assert Enum.at(root_posts,0).title == "Post 25"
    assert Enum.at(root_posts,1).title == "Post 24"
    assert Enum.at(root_posts,2).title == "Post 23"

    # on business node
    business_posts = Glayu.Build.SiteTree.get(["business", :posts])
    assert length(business_posts) == 3
    assert Enum.at(business_posts,0).title == "Post 25"
    assert Enum.at(business_posts,1).title == "Post 21"
    assert Enum.at(business_posts,2).title == "Post 20"

    # on media node
    media_posts = Glayu.Build.SiteTree.get(["business", "media", :posts])
    assert length(media_posts) == 3
    assert Enum.at(media_posts,0).title == "Post 25"
    assert Enum.at(media_posts,1).title == "Post 21"
    assert Enum.at(media_posts,2).title == "Post 20"

  end

end


Support of json response from AdminController.index action. #109

defmodule ExAdmin.AdminController do
  @moduledoc false
  use ExAdmin.Web, :controller
  require Logger
  import ExAdmin
  import ExAdmin.Utils
  import ExAdmin.ParamsToAtoms
  alias ExAdmin.Schema
  require IEx

  plug :handle_root_req
  plug :set_theme
  plug :set_layout

  # A temporary fix to handle a get "/" request.
  # TODO: Refactor the way we assume path_info has the "/admin" prefix
  def handle_root_req(conn, _params) do
    case conn.path_info do
      [] ->
        case ExAdmin.Utils.get_route_path(:dashboard, :index) |> String.split("/") do
          [_, prefix | _] ->
            struct(conn, path_info: [prefix])
          _ -> conn
        end
      _ -> conn
    end
  end

  def set_theme(conn, _) do
    assign(conn, :theme, ExAdmin.theme)
  end

  def action(%{private: %{phoenix_action: action}} = conn, _options) do
    conn = conn |> assign(:xhr, get_req_header(conn, "x-requested-with") == ["XMLHttpRequest"])
    handle_action(conn, action, conn.params["resource"])
  end

  defp handle_action(conn, action, nil) do
    ExAdmin.get_all_registered
    |> Enum.sort(&(elem(&1,1).menu[:priority] < elem(&2,1).menu[:priority]))
    |> hd
    |> case do
      {_, %{controller_route: resource}} ->
        conn = scrub_params(conn, resource, action)
        params = filter_params(conn.params)
        conn
        |> struct(path_info: conn.path_info ++ [resource])
        |> struct(params: Map.put(conn.params, "resource", resource))
        |> handle_action(action, resource)
      _other ->
        throw :invalid_route
    end
  end
  defp handle_action(conn, action, resource) do
    conn = scrub_params(conn, resource, action)
    params = filter_params(conn.params)
    case get_registered_by_controller_route!(resource, conn) do
      %{__struct__: _} = defn ->
        conn
        |> handle_plugs(action, defn)
        |> handle_before_filter(action, defn, params)
        |> handle_custom_actions(action, defn, params)
      _ ->
        apply(__MODULE__, action, [conn, params])
    end
  end

  defp scrub_params(conn, required_key, action) when action in [:create, :update] do
    if conn.params[required_key] do
      Phoenix.Controller.scrub_params conn, required_key
    else
      conn
    end
  end
  defp scrub_params(conn, _required_key, _action), do: conn

  def handle_custom_actions({conn, params}, action, defn, _) do
    handle_custom_actions(conn, action, defn, params)
  end
  def handle_custom_actions(conn, action, defn, params) do
    %{member_actions: member_actions, collection_actions: collection_actions} = defn
    cond do
      member_action = Keyword.get(member_actions, action) ->
        member_action.(conn, params)
      collection_action = Keyword.get(collection_actions, action) ->
        collection_action.(conn, params)
      true ->
        apply(__MODULE__, action, [conn, params])
    end
  end

  def handle_before_filter(conn, action, defn, params) do
    case defn.controller_filters[:before_filter] do
      nil ->
        conn
      {name, opts} ->
        filter = cond do
          opts[:only] ->
            if action in opts[:only], do: true, else: false
          opts[:except] ->
            if not action in opts[:except], do: true, else: false
          true -> true
        end
        if filter, do: apply(defn.__struct__, name, [conn, params]), else: conn
    end
  end

  defp handle_plugs(conn, :nested, _defn), do: conn
  defp handle_plugs(conn, _action, defn) do
    case Application.get_env(:ex_admin, :plug, []) do
      list when is_list(list) -> list
      item -> [{item, []}]
    end
    |> Keyword.merge(defn.plugs)
    |> Enum.reduce(conn, fn({name, opts}, conn) ->
      apply(name, :call, [conn, opts])
    end)
    |> authorized?
  end

  defp authorized?(%{assigns: %{authorized: true}} = conn), do: conn
  defp authorized?(%{assigns: %{authorized: false}}) do
    throw :unauthorized
  end
  defp authorized?(conn), do: conn

  defp set_layout(conn, _) do
    put_layout(conn, "#{conn.assigns.theme.name}.html")
  end

  def index(%{assigns: %{xhr: false}} = conn, params) do
    defn = get_registered_by_controller_route!(params[:resource], conn)
    {contents, page, scope_counts} = case defn do
      %{type: :page} = defn ->
        {defn.__struct__ |> apply(:page_view, [conn]), nil, []}
      defn ->
        model = defn.__struct__

        page = case conn.assigns[:page] do
          nil ->
            model.run_query(repo, defn, :index, params |> Map.to_list)
          page ->
            page
        end
        counts = model.run_query_counts repo, defn, :index, params |> Map.to_list
        if function_exported? model, :index_view, 3 do
          {apply(model, :index_view, [conn, page, counts]), page, counts}
        else
          {ExAdmin.Index.default_index_view(conn, page, counts), page, counts}
        end
    end
    assign(conn, :scope_counts, scope_counts)
    |> render("admin.html", html: contents, defn: defn, resource: page,
      filters: (if false in defn.index_filters, do: false, else: defn.index_filters))
  end

  def index(%{assigns: %{xhr: true}} = conn, params) do
    defn = get_registered_by_controller_route!(params[:resource], conn)
    model = defn.__struct__
    page = defn.resource_model.admin_search_query(params[:keywords]) |> repo.paginate(params)

    results = page.entries
    |> Enum.map(fn(r) -> %{id: r.id, pretty_name: r.name} end)

    resp = %{results: results, more: page.page_number < page.total_pages}
    conn |> json(resp)
  end

  def show(conn, params) do
    registered = get_registered_by_controller_route!(params[:resource], conn)
    {contents, resource, defn} = case registered do
      defn ->
        model = defn.__struct__

        resource = unless Application.get_all_env(:auth_ex) == [] do
          resource_name = AuthEx.Utils.resource_name conn, model: defn.resource_model
          case conn.assigns[resource_name] do
            nil ->
              model.run_query(repo, defn, :show, params[:id])
            res ->
              res
          end
        else
          model.run_query(repo, defn, :show, params[:id])
        end

        if resource == nil do
          raise Phoenix.Router.NoRouteError, conn: conn, router: __MODULE__
        end

        if function_exported? model, :show_view, 2 do
          {apply(model, :show_view, [conn, resource]), resource, defn}
        else
          {ExAdmin.Show.default_show_view(conn, resource), resource, defn}
        end
    end
    render conn, "admin.html", html: contents, resource: resource, filters: nil, defn: defn
  end

  def edit(conn, params) do
    registered = get_registered_by_controller_route!(params[:resource], conn)
    {contents, resource, defn} = case registered do
      defn ->
        model = defn.__struct__
        resource = model.run_query(repo, defn, :edit, params[:id])

        if resource == nil do
          raise Phoenix.Router.NoRouteError, conn: conn, router: __MODULE__
        end

        if function_exported? model, :form_view, 3 do
          {apply(model, :form_view, [conn, resource, params]), resource, defn}
        else
          {ExAdmin.Form.default_form_view(conn, resource, params), resource, defn}
        end
    end
    render conn, "admin.html", html: contents, resource: resource, filters: nil, defn: defn
  end

  def new(conn, params) do
    registered = get_registered_by_controller_route!(params[:resource], conn)
    {contents, resource, defn} = case registered do
      defn ->
        model = defn.__struct__
        resource = model.__struct__.resource_model.__struct__
        {do_form_view(model, conn, resource, params), resource, defn}
    end
    render conn, "admin.html", html: contents, resource: resource, filters: nil, defn: defn
  end

  defp do_form_view(model, conn, resource, params) do
    if function_exported? model, :form_view, 3 do
      apply(model, :form_view, [conn, resource, params])
    else
      ExAdmin.Form.default_form_view conn, resource, params
    end
  end

  def create(conn, params) do
    registered = get_registered_by_controller_route!(params[:resource], conn)
    case registered do
      defn ->
        model = defn.__struct__
        resource = model.__struct__.resource_model.__struct__
        resource_model = model.__struct__.resource_model
        |> base_name |> String.downcase |> String.to_atom
        changeset_fn = Keyword.get(defn.changesets, :create, &resource.__struct__.changeset/2)
        changeset = ExAdmin.Repo.changeset(changeset_fn, resource, params[resource_model])

        case ExAdmin.Repo.insert(changeset) do
          {:error, changeset} ->
            conn = put_flash(conn, :inline_error, changeset.errors)
            contents = do_form_view model, conn, changeset.model, params
            conn |> render("admin.html", html: contents, resource: resource, filters: nil, defn: defn)
          resource ->
            put_flash(conn, :notice, "#{base_name model} was successfully created.")
            |> redirect(to: get_route_path(resource, :show, Schema.get_id(resource)))
        end
    end
  end

  def update(conn, params) do
    registered = get_registered_by_controller_route!(params[:resource], conn)
    case registered do
      defn ->
        model = defn.__struct__
        resource_model = model.__struct__.resource_model
        |> base_name |> String.downcase |> String.to_atom
        resource = model.run_query(repo, defn, :edit, params[:id])

        if resource == nil do
          raise Phoenix.Router.NoRouteError, conn: conn, router: __MODULE__
        end

        changeset_fn = Keyword.get(defn.changesets, :update, &resource.__struct__.changeset/2)
        changeset = ExAdmin.Repo.changeset(changeset_fn, resource, params[resource_model])
        case ExAdmin.Repo.update(changeset) do
          {:error, changeset} ->
            conn = put_flash(conn, :inline_error, changeset.errors)
            contents = do_form_view model, conn, changeset.model, params
            conn |> render("admin.html", html: contents, resource: resource, filters: nil, defn: defn)
          resource ->
            put_flash(conn, :notice, "#{base_name model} was successfully updated")
            |> redirect(to: get_route_path(resource, :show, Schema.get_id(resource)))
        end
    end
  end

  def destroy(conn, params) do
    registered = get_registered_by_controller_route!(params[:resource], conn)

    resource_model =
      case registered do
        defn ->
          model = defn.__struct__
          resource_model = model.__struct__.resource_model
          |> base_name |> String.downcase |> String.to_atom

          resource = model.run_query(repo, defn, :edit, params[:id])

          if resource == nil do
            raise Phoenix.Router.NoRouteError, conn: conn, router: __MODULE__
          end

          ExAdmin.Repo.delete(resource, params[resource_model])
          base_name model
      end

    case get_req_header(conn, "x-requested-with") do
      ["XMLHttpRequest"] ->
        render conn, "destroy.js", tr_id: String.downcase("#{resource_model}_#{params[:id]}")
      _ ->
        put_flash(conn, :notice, "#{resource_model} was successfully destroyed.")
        |> redirect(to: get_route_path(conn, :index))
    end
  end

  def batch_action(conn, %{batch_action: "destroy"} = params) do
    defn = get_registered_by_controller_route!(params[:resource], conn)

    model = defn.__struct__
    resource_model = model.__struct__.resource_model

    type = case ExAdmin.Schema.primary_key(resource_model) do
      nil -> :integer
      key -> resource_model.__schema__(:type, key)
    end

    ids = params[:collection_selection]
    count = Enum.count ids
    ids
    |> Enum.map(&(to_integer(type, &1)))
    |> Enum.each(fn(id) ->
      repo.delete repo.get(resource_model, id)
    end)

    put_flash(conn, :notice, "Successfully destroyed #{count} #{pluralize params[:resource], count}")
    |> redirect(to: get_route_path(conn, :index))
  end

  defp to_integer(:id, string), do: string
  defp to_integer(:string, string), do: string
  defp to_integer(:integer, string) do
    case Integer.parse string do
      {int, ""} -> int
      _ -> string
    end
  end

  def csv(conn, params) do
    registered = get_registered_by_controller_route!(params[:resource], conn)
    case registered do
      defn ->
        model = defn.__struct__

        csv = case model.run_query(repo, defn, :csv) do
          [] -> []
          [resource | resources] ->
            ExAdmin.View.Adapter.build_csv(resource, resources)
        end

        conn
        |> put_resp_content_type("text/csv")
        |> put_resp_header("Content-Disposition", "inline; filename=\"#{params[:resource]}.csv\"")
        |> send_resp(conn.status || 200, csv)
    end
  end

  @nested_key_list for i <- 1..5, do: {String.to_atom("nested#{i}"), String.to_atom("id#{i}")}

  def nested(conn, params) do
    registered = get_registered_by_controller_route!(params[:resource], conn)
    contents =
      case registered do
        defn ->
          model = defn.__struct__

          items = apply(model, :get_blocks, [conn, defn.resource_model.__struct__, params])
          block = deep_find(items, String.to_atom(params[:field_name]))

          resources = block[:opts][:collection].(conn, defn.resource_model.__struct__)

          contents = apply(model, :ajax_view, [conn, params, resources, block])
          contents
      end
    send_resp(conn, conn.status || 200, "text/javascript", contents)
  end

  def dashboard(conn, _) do
    redirect conn, to: "/admin/contacts"
  end

  def select_theme(conn, %{id: id} = params) do
    {_, theme} = Application.get_env(:ex_admin, :theme_selector, [])
    |> Enum.at(id)
    loc = Map.get(params, :loc, "/admin") |> URI.parse |> Map.get(:path)
    Application.put_env :ex_admin, :theme, theme
    redirect conn, to: loc
  end

  def deep_find(items, name) do
    Enum.reduce items, nil, fn(item, acc) ->
      case item do
        %{inputs: inputs} ->
          case Enum.find inputs, &(&1[:name] == name) do
            nil -> acc
            found -> found
          end
        _ -> acc
      end
    end
  end
  defp send_resp(conn, default_status, default_content_type, body) do
    conn
    |> ensure_resp_content_type(default_content_type)
    |> Plug.Conn.send_resp(conn.status || default_status, body)
  end

  defp ensure_resp_content_type(%{resp_headers: resp_headers} = conn, content_type) do
    if List.keyfind(resp_headers, "content-type", 0) do
      conn
    else
      content_type = content_type <> "; charset=utf-8"
      %{conn | resp_headers: [{"content-type", content_type}|resp_headers]}
    end
  end

  def repo, do: Application.get_env(:ex_admin, :repo)
end

defmodule ExAdmin.AdminController do
  @moduledoc false
  use ExAdmin.Web, :controller
  require Logger
  import ExAdmin
  import ExAdmin.Utils
  import ExAdmin.ParamsToAtoms
  alias ExAdmin.Schema
  require IEx

  plug :handle_root_req
  plug :set_theme
  plug :set_layout

  # A temporary fix to handle a get "/" request.
  # TODO: Refactor the way we assume path_info has the "/admin" prefix
  def handle_root_req(conn, _params) do
    case conn.path_info do
      [] ->
        case ExAdmin.Utils.get_route_path(:dashboard, :index) |> String.split("/") do
          [_, prefix | _] ->
            struct(conn, path_info: [prefix])
          _ -> conn
        end
      _ -> conn
    end
  end

  def set_theme(conn, _) do
    assign(conn, :theme, ExAdmin.theme)
  end

  def action(%{private: %{phoenix_action: action}} = conn, _options) do
    handle_action(conn, action, conn.params["resource"])
  end

  defp handle_action(conn, action, nil) do
    ExAdmin.get_all_registered
    |> Enum.sort(&(elem(&1,1).menu[:priority] < elem(&2,1).menu[:priority]))
    |> hd
    |> case do
      {_, %{controller_route: resource}} ->
        conn = scrub_params(conn, resource, action)
        params = filter_params(conn.params)
        conn
        |> struct(path_info: conn.path_info ++ [resource])
        |> struct(params: Map.put(conn.params, "resource", resource))
        |> handle_action(action, resource)
      _other ->
        throw :invalid_route
    end
  end
  defp handle_action(conn, action, resource) do
    conn = scrub_params(conn, resource, action)
    params = filter_params(conn.params)
    case get_registered_by_controller_route!(resource, conn) do
      %{__struct__: _} = defn ->
        conn
        |> handle_plugs(action, defn)
        |> handle_before_filter(action, defn, params)
        |> handle_custom_actions(action, defn, params)
      _ ->
        apply(__MODULE__, action, [conn, params])
    end
  end

  defp scrub_params(conn, required_key, action) when action in [:create, :update] do
    if conn.params[required_key] do
      Phoenix.Controller.scrub_params conn, required_key
    else
      conn
    end
  end
  defp scrub_params(conn, _required_key, _action), do: conn

  def handle_custom_actions({conn, params}, action, defn, _) do
    handle_custom_actions(conn, action, defn, params)
  end
  def handle_custom_actions(conn, action, defn, params) do
    %{member_actions: member_actions, collection_actions: collection_actions} = defn
    cond do
      member_action = Keyword.get(member_actions, action) ->
        member_action.(conn, params)
      collection_action = Keyword.get(collection_actions, action) ->
        collection_action.(conn, params)
      true ->
        apply(__MODULE__, action, [conn, params])
    end
  end

  def handle_before_filter(conn, action, defn, params) do
    case defn.controller_filters[:before_filter] do
      nil ->
        conn
      {name, opts} ->
        filter = cond do
          opts[:only] ->
            if action in opts[:only], do: true, else: false
          opts[:except] ->
            if not action in opts[:except], do: true, else: false
          true -> true
        end
        if filter, do: apply(defn.__struct__, name, [conn, params]), else: conn
    end
  end

  defp handle_plugs(conn, :nested, _defn), do: conn
  defp handle_plugs(conn, _action, defn) do
    case Application.get_env(:ex_admin, :plug, []) do
      list when is_list(list) -> list
      item -> [{item, []}]
    end
    |> Keyword.merge(defn.plugs)
    |> Enum.reduce(conn, fn({name, opts}, conn) ->
      apply(name, :call, [conn, opts])
    end)
    |> authorized?
  end

  defp authorized?(%{assigns: %{authorized: true}} = conn), do: conn
  defp authorized?(%{assigns: %{authorized: false}}) do
    throw :unauthorized
  end
  defp authorized?(conn), do: conn

  defp set_layout(conn, _) do
    put_layout(conn, "#{conn.assigns.theme.name}.html")
  end

  def index(conn, params) do
    defn = get_registered_by_controller_route!(params[:resource], conn)
    {contents, page, scope_counts} = case defn do
      %{type: :page} = defn ->
        {defn.__struct__ |> apply(:page_view, [conn]), nil, []}
      defn ->
        model = defn.__struct__

        page = case conn.assigns[:page] do
          nil ->
            model.run_query(repo, defn, :index, params |> Map.to_list)
          page ->
            page
        end
        counts = model.run_query_counts repo, defn, :index, params |> Map.to_list
        if function_exported? model, :index_view, 3 do
          {apply(model, :index_view, [conn, page, counts]), page, counts}
        else
          {ExAdmin.Index.default_index_view(conn, page, counts), page, counts}
        end
    end
    assign(conn, :scope_counts, scope_counts)
    |> render("admin.html", html: contents, defn: defn, resource: page,
      filters: (if false in defn.index_filters, do: false, else: defn.index_filters))
  end

  def show(conn, params) do
    registered = get_registered_by_controller_route!(params[:resource], conn)
    {contents, resource, defn} = case registered do
      defn ->
        model = defn.__struct__

        resource = unless Application.get_all_env(:auth_ex) == [] do
          resource_name = AuthEx.Utils.resource_name conn, model: defn.resource_model
          case conn.assigns[resource_name] do
            nil ->
              model.run_query(repo, defn, :show, params[:id])
            res ->
              res
          end
        else
          model.run_query(repo, defn, :show, params[:id])
        end

        if resource == nil do
          raise Phoenix.Router.NoRouteError, conn: conn, router: __MODULE__
        end

        if function_exported? model, :show_view, 2 do
          {apply(model, :show_view, [conn, resource]), resource, defn}
        else
          {ExAdmin.Show.default_show_view(conn, resource), resource, defn}
        end
    end
    render conn, "admin.html", html: contents, resource: resource, filters: nil, defn: defn
  end

  def edit(conn, params) do
    registered = get_registered_by_controller_route!(params[:resource], conn)
    {contents, resource, defn} = case registered do
      defn ->
        model = defn.__struct__
        resource = model.run_query(repo, defn, :edit, params[:id])

        if resource == nil do
          raise Phoenix.Router.NoRouteError, conn: conn, router: __MODULE__
        end

        if function_exported? model, :form_view, 3 do
          {apply(model, :form_view, [conn, resource, params]), resource, defn}
        else
          {ExAdmin.Form.default_form_view(conn, resource, params), resource, defn}
        end
    end
    render conn, "admin.html", html: contents, resource: resource, filters: nil, defn: defn
  end

  def new(conn, params) do
    registered = get_registered_by_controller_route!(params[:resource], conn)
    {contents, resource, defn} = case registered do
      defn ->
        model = defn.__struct__
        resource = model.__struct__.resource_model.__struct__
        {do_form_view(model, conn, resource, params), resource, defn}
    end
    render conn, "admin.html", html: contents, resource: resource, filters: nil, defn: defn
  end

  defp do_form_view(model, conn, resource, params) do
    if function_exported? model, :form_view, 3 do
      apply(model, :form_view, [conn, resource, params])
    else
      ExAdmin.Form.default_form_view conn, resource, params
    end
  end

  def create(conn, params) do
    registered = get_registered_by_controller_route!(params[:resource], conn)
    case registered do
      defn ->
        model = defn.__struct__
        resource = model.__struct__.resource_model.__struct__
        resource_model = model.__struct__.resource_model
        |> base_name |> String.downcase |> String.to_atom
        changeset_fn = Keyword.get(defn.changesets, :create, &resource.__struct__.changeset/2)
        changeset = ExAdmin.Repo.changeset(changeset_fn, resource, params[resource_model])

        case ExAdmin.Repo.insert(changeset) do
          {:error, changeset} ->
            conn = put_flash(conn, :inline_error, changeset.errors)
            contents = do_form_view model, conn, changeset.model, params
            conn |> render("admin.html", html: contents, resource: resource, filters: nil, defn: defn)
          resource ->
            put_flash(conn, :notice, "#{base_name model} was successfully created.")
            |> redirect(to: get_route_path(resource, :show, Schema.get_id(resource)))
        end
    end
  end

  def update(conn, params) do
    registered = get_registered_by_controller_route!(params[:resource], conn)
    case registered do
      defn ->
        model = defn.__struct__
        resource_model = model.__struct__.resource_model
        |> base_name |> String.downcase |> String.to_atom
        resource = model.run_query(repo, defn, :edit, params[:id])

        if resource == nil do
          raise Phoenix.Router.NoRouteError, conn: conn, router: __MODULE__
        end

        changeset_fn = Keyword.get(defn.changesets, :update, &resource.__struct__.changeset/2)
        changeset = ExAdmin.Repo.changeset(changeset_fn, resource, params[resource_model])
        case ExAdmin.Repo.update(changeset) do
          {:error, changeset} ->
            conn = put_flash(conn, :inline_error, changeset.errors)
            contents = do_form_view model, conn, changeset.model, params
            conn |> render("admin.html", html: contents, resource: resource, filters: nil, defn: defn)
          resource ->
            put_flash(conn, :notice, "#{base_name model} was successfully updated")
            |> redirect(to: get_route_path(resource, :show, Schema.get_id(resource)))
        end
    end
  end

  def destroy(conn, params) do
    registered = get_registered_by_controller_route!(params[:resource], conn)

    resource_model =
      case registered do
        defn ->
          model = defn.__struct__
          resource_model = model.__struct__.resource_model
          |> base_name |> String.downcase |> String.to_atom

          resource = model.run_query(repo, defn, :edit, params[:id])

          if resource == nil do
            raise Phoenix.Router.NoRouteError, conn: conn, router: __MODULE__
          end

          ExAdmin.Repo.delete(resource, params[resource_model])
          base_name model
      end

    case get_req_header(conn, "x-requested-with") do
      ["XMLHttpRequest"] ->
        render conn, "destroy.js", tr_id: String.downcase("#{resource_model}_#{params[:id]}")
      _ ->
        put_flash(conn, :notice, "#{resource_model} was successfully destroyed.")
        |> redirect(to: get_route_path(conn, :index))
    end
  end

  def batch_action(conn, %{batch_action: "destroy"} = params) do
    defn = get_registered_by_controller_route!(params[:resource], conn)

    model = defn.__struct__
    resource_model = model.__struct__.resource_model

    type = case ExAdmin.Schema.primary_key(resource_model) do
      nil -> :integer
      key -> resource_model.__schema__(:type, key)
    end

    ids = params[:collection_selection]
    count = Enum.count ids
    ids
    |> Enum.map(&(to_integer(type, &1)))
    |> Enum.each(fn(id) ->
      repo.delete repo.get(resource_model, id)
    end)

    put_flash(conn, :notice, "Successfully destroyed #{count} #{pluralize params[:resource], count}")
    |> redirect(to: get_route_path(conn, :index))
  end

  defp to_integer(:id, string), do: string
  defp to_integer(:string, string), do: string
  defp to_integer(:integer, string) do
    case Integer.parse string do
      {int, ""} -> int
      _ -> string
    end
  end

  def csv(conn, params) do
    registered = get_registered_by_controller_route!(params[:resource], conn)
    case registered do
      defn ->
        model = defn.__struct__

        csv = case model.run_query(repo, defn, :csv) do
          [] -> []
          [resource | resources] ->
            ExAdmin.View.Adapter.build_csv(resource, resources)
        end

        conn
        |> put_resp_content_type("text/csv")
        |> put_resp_header("Content-Disposition", "inline; filename=\"#{params[:resource]}.csv\"")
        |> send_resp(conn.status || 200, csv)
    end
  end

  @nested_key_list for i <- 1..5, do: {String.to_atom("nested#{i}"), String.to_atom("id#{i}")}

  def nested(conn, params) do
    registered = get_registered_by_controller_route!(params[:resource], conn)
    contents =
      case registered do
        defn ->
          model = defn.__struct__

          items = apply(model, :get_blocks, [conn, defn.resource_model.__struct__, params])
          block = deep_find(items, String.to_atom(params[:field_name]))

          resources = block[:opts][:collection].(conn, defn.resource_model.__struct__)

          contents = apply(model, :ajax_view, [conn, params, resources, block])
          contents
      end
    send_resp(conn, conn.status || 200, "text/javascript", contents)
  end

  def dashboard(conn, _) do
    redirect conn, to: "/admin/contacts"
  end

  def select_theme(conn, %{id: id} = params) do
    {_, theme} = Application.get_env(:ex_admin, :theme_selector, [])
    |> Enum.at(id)
    loc = Map.get(params, :loc, "/admin") |> URI.parse |> Map.get(:path)
    Application.put_env :ex_admin, :theme, theme
    redirect conn, to: loc
  end

  def deep_find(items, name) do
    Enum.reduce items, nil, fn(item, acc) ->
      case item do
        %{inputs: inputs} ->
          case Enum.find inputs, &(&1[:name] == name) do
            nil -> acc
            found -> found
          end
        _ -> acc
      end
    end
  end
  defp send_resp(conn, default_status, default_content_type, body) do
    conn
    |> ensure_resp_content_type(default_content_type)
    |> Plug.Conn.send_resp(conn.status || default_status, body)
  end

  defp ensure_resp_content_type(%{resp_headers: resp_headers} = conn, content_type) do
    if List.keyfind(resp_headers, "content-type", 0) do
      conn
    else
      content_type = content_type <> "; charset=utf-8"
      %{conn | resp_headers: [{"content-type", content_type}|resp_headers]}
    end
  end

  def repo, do: Application.get_env(:ex_admin, :repo)
end


Use imported function without unnecessary module prefix