1

I have a basic application and tried to use plugs on my controller. It should basically return 400 if the request body doesn't have "url" parameter. It works fine when I send a request from Postman but it seems it doesn't work on my tests.

Do I have to do anything for it to work also in test environment?

  plug :check_url_existence when action in [:create]

  def create(conn, link_params) do
    with {:ok, %Link{} = link} <- Links.create_link(link_params) do
      conn
      |> put_status(:created)
      |> put_resp_header("location", Routes.link_path(conn, :show, link))
      |> render("show.json", link: link)
    end
  end

  defp check_url_existence(conn, _) do
    if is_nil(conn.params["url"]) do
      conn
      |> put_status(:bad_request)
      |> put_view(ShortyWeb.ErrorView)
      |> render("400.json")
    end
    conn
  end

Test:

    test "returns an error without a url", %{ conn: conn } do
      response =
        conn
        |> post("/shorten", %{})
        |> json_response(400)

      assert response["errors"] != %{}
    end

Result:

1) test shortcode generation returns an error without a url (ShortyWeb.LinkControllerTest)
     test/shorty_web/controllers/link_controller_test.exs:58
     ** (RuntimeError) expected response with status 400, got: 422, with body:
     {"errors":{"url":["can't be blank"]}}
     code: |> json_response(400)
     stacktrace:
       (phoenix) lib/phoenix/test/conn_test.ex:373: Phoenix.ConnTest.response/2
       (phoenix) lib/phoenix/test/conn_test.ex:419: Phoenix.ConnTest.json_response/2
       test/shorty_web/controllers/link_controller_test.exs:62: (test)

Postman Result: https://prnt.sc/qdy2bo

Adam Millerchip
  • 20,844
  • 5
  • 51
  • 74
htunc
  • 455
  • 5
  • 19
  • the error is handled before it gets to your controller, check `fallback_controller.ex` and see if you receive the error there. – Daniel Dec 21 '19 at 12:38
  • Thanks for your answer but I just started to make something in Elixir couple days ago and didn't get the point here. Do i have to create plugs also on fallback_controller? Why it doesn't go directly to my controller? It works as i expected when i make a request from postman on development environment. – htunc Dec 21 '19 at 12:42
  • Can you show your router code? I also want to mention that there are other ways to enforce the presence of a certain url parameter. You can handle it in the `changeset` function of the `Link` module so validation will fail and you can display the error message. Or you can do pattern matching on the link_params in the `create` controller function (`def create(conn, %{"url" => url} = link_params)`) and add a fallback function (`def create(conn, _link_params), do: ..render error ...`). – zwippie Dec 21 '19 at 15:24
  • https://prnt.sc/qe00ka here's my router.ex. I guess its better to do it with pattern matching as you said. But the point is my function needs two parameters instead of one. Url and shortcode (shortcode is not required, if not sent it generates a shortcode). – htunc Dec 21 '19 at 15:47

1 Answers1

0

Your plug function is written in such a way that the unchanged conn will be returned no matter what.

You need to move the last line into an else block. Additionally, the if block likely needs to call halt/1.

Otherwise, it will continue with the create function and attempt to create the link anyway, where it will fail with a 422 as it does now, due to validation issues (url is blank).

You should rewrite your plug to something like

defp check_url_existence(conn, _) do
  if is_nil(conn.params["url"]) do
    conn
    |> put_status(:bad_request)
    |> put_view(ShortyWeb.ErrorView)
    |> render("400.json")
    |> halt()
  else
    conn
  end 
end

You should also consider doing as others suggest and not worry about parsing params. The 422 seems like a valid response code.

If you want to stick with the 400 handler as you're trying to achieve here, here's an alternative way to write it.

defp check_url_existence(%{params: %{"url" => url}} = conn) when not is_nil(url), do: conn
defp check_url_existence(conn) do
  conn
  |> put_status(:bad_request)
  |> put_view(ShortyWeb.ErrorView)
  |> render("400.json")
  |> halt()
end

To answer your question directly, the conn test helpers go through the router code, so plugs should get applied.

There are some edge cases where you need to call certain plug functions manually when you're implementing helpers of your own, but those are probably beyond the scope of the question.

begedin
  • 188
  • 8