I’m using Programming Phoenix by Chris McCord as a reference to build my personal blog. I’ve replicated the authentication implementation almost exactly, but now that I am working through the chapter about testing the authentication plug, I’m getting (Plug.Conn.AlreadySentError) the response was already sent
. However, the site works fine when navigating through it regularly, just not in the tests. I’ve looked the problem up and ensured I don’t have plug :action
anywhere and am calling halt()
after I redirect when an action isn’t authorized. If anyone could help I’d really appreciate it Relevant code:
1) test login puts the user into the session (PhoenixBlog.AuthTest)
test/plugs/auth_test.exs:28
** (Plug.Conn.AlreadySentError) the response was already sent
stacktrace:
(plug) lib/plug/conn.ex:862: Plug.Conn.put_session/3
(phoenix_blog) web/plugs/auth.ex:25: PhoenixBlog.Plugs.Auth.login/2
test/plugs/auth_test.exs:31: (test)
2) test authenticate_user halts when no current_user exists (PhoenixBlog.AuthTest)
test/plugs/auth_test.exs:14
** (Plug.Conn.AlreadySentError) the response was already sent
stacktrace:
(plug) lib/plug/conn.ex:607: Plug.Conn.put_resp_header/3
(phoenix) lib/phoenix/controller.ex:303: Phoenix.Controller.redirect/2
(phoenix_blog) web/plugs/auth.ex:57: PhoenixBlog.Plugs.Auth.authenticate_user/2
test/plugs/auth_test.exs:15: (test)
Notice that only the tests which hit the current_user
doesn’t exist clause in authenticate_user
fail.
defmodule PhoenixBlog.AuthTest do
use PhoenixBlog.ConnCase
alias PhoenixBlog.Plugs.Auth
setup %{conn: conn} do
conn =
conn
|> bypass_through(Rumbl.Router, :browser)
|> get("/")
{:ok, %{conn: conn}}
end
test "authenticate_user halts when no current_user exists", %{conn: conn} do
conn = Auth.authenticate_user(conn, [])
assert conn.halted
end
test "authenticate_user continues when the current_user exists", %{conn: conn} do
conn =
conn
|> assign(:current_user, %PhoenixBlog.User{})
|> Auth.authenticate_user([])
refute conn.halted
end
test "login puts the user into the session", %{conn: conn} do
login_conn =
conn
|> Auth.login(%PhoenixBlog.User{id: 123})
|> send_resp(:ok, "")
next_conn = get(login_conn, "/")
assert get_session(next_conn, :user_id) == 123
end
end
Auth plug:
defmodule PhoenixBlog.Plugs.Auth do
import Plug.Conn
import Comeonin.Bcrypt, only: [checkpw: 2, dummy_checkpw: 0]
def init(opts) do
Keyword.fetch!(opts, :repo)
end
def call(conn, repo) do
user_id = get_session(conn, :user_id)
cond do
user = conn.assigns[:current_user] ->
conn
user = user_id && repo.get(PhoenixBlog.User, user_id) ->
assign(conn, :current_user, user)
true ->
assign(conn, :current_user, nil)
end
end
def login(conn, user) do
conn
|> assign(:current_user, user)
|> put_session(:user_id, user.id)
|> configure_session(renew: true)
end
def logout(conn) do
configure_session(conn, drop: true)
end
def login_by_username_and_pass(conn, username, given_pass, opts) do
repo = Keyword.fetch!(opts, :repo)
user = repo.get_by(PhoenixBlog.User, username: username)
cond do
user && checkpw(given_pass, user.password_hash) ->
{:ok, login(conn, user)}
user ->
{:error, :unauthorized, conn}
true ->
dummy_checkpw()
{:error, :not_found, conn}
end
end
import Phoenix.Controller
alias PhoenixBlog.Router.Helpers
def authenticate_user(conn, _opts) do
if conn.assigns.current_user do
conn
else
conn
|> put_flash(:error, "You must be logged in to access that page")
|> redirect(to: Helpers.page_path(conn, :index))
|> halt()
end
end
end