how can i set session in setup when i test phoenix action which need user_id in session?

LcpMarvel picture LcpMarvel · Aug 13, 2015 · Viewed 7.6k times · Source

I have a test which need to set user_id to session before testing, because this action need to know the current_user.

setup do
  %User{
    id: 123456,
    username: "lcp",
    email: "[email protected]",
    password: Comeonin.Bcrypt.hashpwsalt("password")
  } |> Repo.insert

  {:ok, user: Repo.get(User, 123456) }
end

test "POST /posts", context do
  # conn = conn()
  #      |> put_session(:user_id, context[:user].id)
  #      |> post("/posts", %{ post: %{ title: "title", body: "body" } })

  # assert get_flash(conn, :info) == "Post created successfully."

  # updated to => 
  conn = conn()
        |> Map.put(:secret_key_base, String.duplicate("abcdefgh", 8))
        |> Plug.Session.call(@session)
        |> Plug.Conn.fetch_session
        |> put_session(:user_id, context[:user].id)
        |> post("/posts", %{ post: %{ title: "title", body: "body" } })

  assert get_flash(conn, :info) == "Post created successfully."
end

I have tried this code, but it says that session not fetched, call fetch_session/2.


web/controllers/controller_helper.ex

defmodule SimpleBlog.ControllerHelpers do    
  alias Phoenix.Controller
  alias Plug.Conn
  alias SimpleBlog.Router.Helpers

  def authenticate(conn, _) do
    case Conn.get_session(conn, :user_id) do
      nil ->
        unauthorized(conn)
      user_id ->
        case SimpleBlog.Repo.get(SimpleBlog.User, user_id) do
          {:ok, user} ->
            Conn.assign(conn, :current_user, user)
          nil ->
            unauthorized(conn)
        end
    end
  end

  def unauthorized(conn) do
    conn
      |> Controller.put_flash(:error, "You must be logged in")
      |> Controller.redirect(to: Helpers.session_path(conn, :new))
      |> Conn.halt
  end
end

Updated

I get nil when i get user_id from session through Conn.get_session(conn, :user_id).

Here is post controller web/controllers/post_controller.ex

defmodule SimpleBlog.PostController do
  use SimpleBlog.Web, :controller
  import SimpleBlog.ControllerHelpers

  alias SimpleBlog.Post

  plug :authenticate when not action in [:new]

  def create(conn, %{ "post" => post_params }) do
    changeset = Post.changeset(%Post{}, post_params)

    case Repo.insert(changeset) do
      {:ok, _post} ->
        conn
          |> put_flash(:info, "Post created successfully.")
          |> redirect(to: post_path(conn, :new))
      {:error, changeset} ->
        render(conn, "new.html", changeset: changeset)
    end
  end
end

It's my test file.

defmodule SimpleBlog.PostControllerTest do
  use SimpleBlog.ConnCase
  alias SimpleBlog.Repo
  alias SimpleBlog.User

  @session Plug.Session.init(
    store: :cookie,
    key: "_app",
    encryption_salt: "yadayada",
    signing_salt: "yadayada"
  )

  setup do
    %User{
      id: 123456,
      username: "lcp",
      email: "[email protected]",
      password: Comeonin.Bcrypt.hashpwsalt("password")
    } |> Repo.insert

    {:ok, user: Repo.get(User, 123456) }
  end


  @tag timeout: 900000
  test "POST /posts", context do
    conn = conn()
            |> Map.put(:secret_key_base, String.duplicate("abcdefgh", 8))
            |> Plug.Session.call(@session)
            |> Plug.Conn.fetch_session
            |> put_session(:user_id, context[:user].id)
            |> post("/posts", %{ post: %{ title: "title", body: "body" } })

    assert get_flash(conn, :info) == "Post created successfully."
  end
end


update ..


lib/simple_blog/plugs/authenticated.ex

I define a plug authenticated

defmodule SimpleBlog.Plugs.Authenticated do
  import Plug.Conn
  alias Phoenix.Controller
  alias SimpleBlog.Router.Helpers
  alias SimpleBlog.User

  def init(options) do
    options
  end

  def call(conn, _) do
    case conn |> current_user_id do
      nil ->
        conn
          |> Controller.put_flash(:error, "You must be logged in")
          |> Controller.redirect(to: Helpers.session_path(conn, :new))
          |> halt
      current_user_id ->
        conn |> assign(:current_user, SimpleBlog.Repo.get(User, current_user_id))
    end
  end

  defp current_user_id(conn) do
    case Mix.env do
      :test ->
        conn.private[:authenticated_current_user_id]
      _ ->
        conn |> fetch_session |> get_session(:current_user_id)
    end
  end
end

in my test

conn = conn()
        |> put_private(:authenticated_current_user_id, context[:user].id)
        |> post("/posts", %{ post: %{ title: "title", body: "body" } })

assert get_flash(conn, :info) == "Post created successfully."

now, the test is passed.

Answer

Gazler picture Gazler · Aug 13, 2015

You actually can't do it like this due to the post action resetting the session. You have a couple of options.

First, you can do an integration test that visits your login path with valid credentials, and then make your request to create the post.

Secondly, you can create an authentication plug like this:

defmodule SimpleBlog.Plug.Authenticate do
  import Plug.Conn
  alias SimpleBlog.Router.Helpers, as: RouteHelpers
  import Phoenix.Controller

  alias SimpleBlog.Repo
  alias SimpleBlog.User

  def init(opts), do: opts

  def call(conn, _opts) do
    if user = get_user(conn) do
      assign(conn, :current_user, user)
    else
      auth_error!(conn)
    end
  end

  def get_user(conn) do
    case conn.assigns[:current_user] do
      nil      -> fetch_user(conn)
      user     -> user
    end
  end

  defp fetch_user(conn) do
    case get_session(conn, :current_user) |> find_user
      {:ok, user} -> user
      _           -> nil
    end
  end

  defp find_user(id) when do
    Repo.get(User, id)
  end

  defp auth_error!(conn) do
    conn
    |> put_flash(:error, "You need to be signed in to view this page")
    |> redirect(to: RouteHelpers.session_path(conn, :new))
    |> halt
  end
end

You can test this by doing something like the following taken from the Phoenix tests:

defmodule SimpleBlog.Plug.AuthenticationTest do
  use ExUnit.Case
  use Plug.Test

  alias Plug.Conn
  alias SimpleBlog.Plug.Authenticate
  alias SimpleBlog.Repo
  alias SimpleBlog.User
  import SimpleBlog.Router.Helpers

  @session Plug.Session.init(
    store: :cookie,
    key: "_app",
    encryption_salt: "yadayada",
    signing_salt: "yadayada"
  )

  setup do
    user = %User{
      id: 123456,
      username: "lcp",
      email: "[email protected]",
      password: Comeonin.Bcrypt.hashpwsalt("password")
    } |> Repo.insert!

    session_data = %{id: user.id}
    conn =
      conn(:get, "/")
      |> Map.put(:secret_key_base, String.duplicate("abcdefgh", 8))
      |> Plug.Session.call(@session)
      |> Conn.fetch_session()
    {:ok, conn: conn, user: user, session_data: session_data}
  end

  test "get_user returns the user if it is set in conn.assigns", %{conn: conn, user: user} do
    conn = Conn.assign(conn, :current_user, user)
    assert Authenticate.get_user(conn) == user
  end

  test "get_user returns the user if it is set in a session", %{conn: conn, user: user, session_data: session_data} do
    conn = Conn.put_session(conn, :current_user, session_data)
    assert Authenticate.get_user(conn) == user
  end

  test "get_user returns nil if the user is not in assigns or session", %{conn: conn} do
    assert Authenticate.get_user(conn) == nil
  end

  test "when there is not user stored", %{conn: conn} do
    conn =
      |> Phoenix.Controller.fetch_flash
      |> Authenticate.call([])
    assert Phoenix.Controller.get_flash(new_conn, :error) == "You need to be signed in to view this page"
    assert Phoenix.ConnTest.redirected_to(new_conn) == session_path(new_conn, :new)
  end
end

You can now test your controller by doing:

setup do
  %User{
    id: 123456,
    username: "lcp",
    email: "[email protected]",
    password: Comeonin.Bcrypt.hashpwsalt("password")
  } |> Repo.insert

  {:ok, user: Repo.get(User, 123456) }
end

test "POST /posts", %{user: user} do
   conn = conn()
        |> assign(:current_user, user)
        |> post("/posts", %{ post: %{ title: "title", body: "body" } })

   assert get_flash(conn, :info) == "Post created successfully."
end