jswny

jswny

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 :slight_smile: 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

Marked As Solved

jswny

jswny OP

Wow, I’m dumb :stuck_out_tongue: Turns out I forgot to change bypass_through(Rumbl.Router, :browser) to bypass_through(PhoenixBlog.Router, :browser) after I pasted the code from the book. Thanks Jose for your help and to @luke on Slack for talking the problem out with me and helping me fix it :smiley:

Also Liked

josevalim

josevalim

Creator of Elixir

I believe you need to call conn = recycle(conn) after using the connection if you want to do another request with it.

jswny

jswny OP

I’m not sure exactly where I would put that function, but I put it in the setup callback like so:

setup %{conn: conn} do
  conn =
    conn
    |> recycle()
    |> bypass_through(Rumbl.Router, :browser)
    |> get("/")

  {:ok, %{conn: conn}}
end

However that still did not fix my problem :confused: I also tried putting the recycle call in each of the failing tests before I used conn but that did not work either. I’m not sure what to do.

josevalim

josevalim

Creator of Elixir

It needs to be after get/2.

Last Post!

chriseyre

chriseyre

I am getting the same problem working through the >= 1.4 version of the book.

It starts failing on test “requires user authentication on all actions”

Where Next? Top

Trending in Questions Top

RSP87
I’m working on a project that simulates the bumbl example in the programming phoenix book. It acts almost like an email client. We have a...
New
nseaSeb
Hello, I know there is an approach for handling lists that allows for optimized traversal, but I can’t recall the specific method (somet...
New
RemyXRenard
I’m seeing that a list inside a Kino.DataTable will be interpreted as a charlist, even if the Kino.configure() is set to charlists: :as_l...
New
velrest
So my question is quite simple and i have found no conclusive answer on forum, google or AI. Should we use :erlang.float for Integer to ...
New
brecabral
Documentation While reading the Scoped Routes section, I noticed that the documentation currently refers to a problem without explainin...
New
samoloth
Hi, I’ve just set up an application with ash_authentication. There is only magic link strategy for now, so there is no confirmation add o...
New
FlyingNoodle
If a change or preparation module uses Ash.Changeset.get_argument/2 or Ash.Query.get_argument/2 (or any of the other get_argument functio...
New

Other Trending Topics Top

mudasobwa
I am happy to introduce the very α version of the new programming language compiled to BEAM. Welcome Cure. It has literally three kille...
New
marciok
Hi there! We created Gust: A task orchestrator inspired by Airflow. For those who have never heard about Aiflow, it’s a Python-based wor...
New
jimsynz
Beam Bots (or just BB for short) is a framework for building fault-tolerant robotics applications in Elixir using familiar OTP patterns. ...
New
Dmk
Xamal is a deployment tool for Elixir apps that deploys native releases to bare metal servers over SSH. It’s a port of GitHub - basecamp/...
New
netoum
Corex is an accessible, unstyled UI component library for Phoenix that integrates Zag.js state machines using Vanilla JavaScript and Live...
New
webofbits
With AI doing more of the implementation work, I’ve been wondering how much coding I should deliberately keep doing myself. My main conc...
#ai
New

We're in Beta

About us Mission Statement

Options

Thread Display Mode




Thread Preview

Skip Thread Previews