CSRF protection and testing controllers

I am a bit at a wits end with this one. I was following this blog post and the passwordless auth works really well. I am pretty happy with it.

Now, I want to have controller tests, where I can verify that actions are protected by the auth. This is where my troubles start.

    test "/create works when logged in", %{conn: conn, session: session} do
      create_params = %{
        url: "https://www.example.com",
        client: "test client"
      }

      conn = conn
      |> init_test_session(%{session_id: session.id})
      |> put_req_header("content-type", "text/html")
      |> post(Routes.link_path(conn, :create, create_params))

      assert html_response(conn, 200) =~ "Creating the seed 1"
    end

The assertion is nonsensical, for now. I am not even able to get to it, though. The test fails somewhere inside Plug middleware and I don’t know why.

  1) test create /create works when logged in (LinksWeb.LinkControllerTest)
     test/links_web/controllers/link_controller_test.exs:39
     ** (ArgumentError) cannot fetch key "_csrf_token" from conn.body_params because they were not fetched
     code: |> post(Routes.link_path(conn, :create, create_params))
     stacktrace:
       (plug 1.10.0) lib/plug/conn/unfetched.ex:35: Plug.Conn.Unfetched.raise_unfetched/3
       (elixir 1.10.3) lib/access.ex:286: Access.get/3
       (plug 1.10.0) lib/plug/csrf_protection.ex:322: Plug.CSRFProtection.verified_request?/3
       (plug 1.10.0) lib/plug/csrf_protection.ex:301: Plug.CSRFProtection.call/2
       (links 0.7.3) LinksWeb.Router.browser/2
       (links 0.7.3) lib/links_web/router.ex:1: LinksWeb.Router.__pipe_through0__/1
       (phoenix 1.5.1) lib/phoenix/router.ex:347: Phoenix.Router.__call__/2
       (links 0.7.3) lib/links_web/endpoint.ex:1: LinksWeb.Endpoint.plug_builder_call/2
       (links 0.7.3) lib/links_web/endpoint.ex:1: LinksWeb.Endpoint.call/2
       (phoenix 1.5.1) lib/phoenix/test/conn_test.ex:225: Phoenix.ConnTest.dispatch/5
       test/links_web/controllers/link_controller_test.exs:48: (test)

I am pulling out the error into its own block to make sure it’s seen:

** (ArgumentError) cannot fetch key "_csrf_token" from conn.body_params because they were not fetched

As far as I can tell, this is because body_params is “unfetched”. Every forum and blog post I came across indicates that init_test_session should fix this.

The stack is Elixir 1.10.3 and Phoenix 1.5.1. Any help is much appreciated.

Do you have something like use YourApp.ModelCase on the top of your test suite? If so, can you paste its code contents?

Additionally, do you have any setup_all or setup blocks in your file?

Hey @dimitarvp, I’m pasting the contents of the ConnCase file below.

defmodule LinksWeb.ConnCase do
    use ExUnit.CaseTemplate

  using do
    quote do
      # Import conveniences for testing with connections
      import Plug.Conn
      import Phoenix.ConnTest

      alias LinksWeb.Router.Helpers, as: Routes

      # The default endpoint for testing
      @endpoint LinksWeb.Endpoint

    end
  end

  setup _tags do
    :ok = Ecto.Adapters.SQL.Sandbox.checkout(Links.Repo)

    on_exit(fn ->
      Links.Repo.delete_all(Links.Link)
      Links.Repo.delete_all(Links.User)
      Links.Repo.delete_all(Links.Accounts.Session)
    end)

    {:ok, conn: Phoenix.ConnTest.build_conn()}
  end
end

The setup block looks like this:

  setup %{conn: conn } do
    {:ok, conn: conn, session: create_session() }
  end

  defp create_session() do
    {:ok, user} =
      Links.Accounts.User.create_for_session(%{
        email: "test@example.com",
        username: "tester"
      })

    {:ok, session} = Ecto.build_assoc(user, :sessions) |> Links.Repo.insert()
    session
  end

One thing I noted is that without init_test_session call, the post hits the controller action and if I remove the session check in that action, the whole call works fine. It seems to me that the init_test_session somehow changes conn such that the error in the original post is shown.