Phoenix controller tests with subdomain are failing when reusing conn

A section of an app I’m working on is hosted on a subdomain: foo.app.com. I use setup to add the host to the conn. I’m new to Phoenix and Elixir and I don’t know if this is right:

  setup do
    host = "foo." <> MyAppWeb.Endpoint.config(:url)[:host]
    conn = Phoenix.ConnTest.build_conn(:get, "http://#{host}/", nil)
    {:ok, conn: conn}
  end

It works unless I want to reuse the conn to make a new request. Then the route for the follow up request is not recognized. For example

  describe "delete role" do
    test "deletes chosen role", %{conn: conn, role: role} do
      conn = delete conn, telesales_admin_role_path(conn, :delete, role)
      assert redirected_to(conn) == telesales_admin_role_path(conn, :index)

      conn = get conn, telesales_admin_role_path(conn, :index)
      refute html_response(conn, 200) =~ role.user.name
    end
  end

When the test gets to conn = get conn, telesales_admin_role_path(conn, :index), I get this error:

  2) test delete role deletes chosen role (MyAppWeb.Telesales.Admin.RoleControllerTest)
     test/my_app_web/controllers/telesales/admin/role_controller_test.exs:41
     ** (Phoenix.Router.NoRouteError) no route found for DELETE /admin/roles/230 (MyAppWeb.Router)
     code: conn = delete conn, telesales_admin_role_path(conn, :delete, role)
     stacktrace:
       (my_app) lib/my_app_web/router.ex:1: MyAppWeb.Router.__match_route__/4
       (my_app) lib/phoenix/router.ex:303: MyAppWeb.Router.call/2
       (my_app) lib/my_app_web/endpoint.ex:1: MyAppWeb.Endpoint.plug_builder_call/2
       (my_app) lib/my_app_web/endpoint.ex:1: MyAppWeb.Endpoint.call/2
       (phoenix) lib/phoenix/test/conn_test.ex:224: Phoenix.ConnTest.dispatch/5
       test/my_app_web/controllers/telesales/admin/role_controller_test.exs:43: (test)

I have passing tests for the route telesales_admin_role_path(conn, :index). So something is wrong.

Can someone please explain to me what is happening?

I dumped the conn before and after the first request to find clues, but I can’t see anything.

Before the first request (delete conn, telesales_admin_role_path(conn, :delete, role)):

%Plug.Conn{adapter: {Plug.Adapters.Test.Conn, :...}, assigns: %{},
 before_send: [], body_params: %Plug.Conn.Unfetched{aspect: :body_params},
 cookies: %Plug.Conn.Unfetched{aspect: :cookies}, halted: false,
 host: "foo.localhost", method: "GET", owner: #PID<0.617.0>,
 params: %Plug.Conn.Unfetched{aspect: :params}, path_info: [], path_params: %{},
 peer: {{127, 0, 0, 1}, 111317}, port: 80,
 private: %{guardian_default_claims: %{"aud" => "my_app", "exp" => 1530883668,
     "iat" => 1528464468, "iss" => "my_app",
     "jti" => "a6a55bdf-d35a-4b52-948c-128dbc286ef6", "nbf" => 1528464467,
     "sub" => "1453", "typ" => "access"},
   guardian_default_resource: %MyApp.Accounts.User{__meta__: #Ecto.Schema.Metadata<:loaded, "users">,
    email: "email_8866@example.com", id: 1453, image: "avatar.jpg",
    inserted_at: ~N[2018-06-08 13:27:48.545323], name: "Elmer J. Fudd 8866",
    uid: "8866", updated_at: ~N[2018-06-08 13:27:48.545340]},
   guardian_default_token: "eyJhbGciOiJIUzUxMiIsInR5cCI6IkpXVCJ9.eyJhdWQiOiJvYnJhbWF4IiwiZXhwIjoxNTMwODgzNjY4LCJpYXQiOjE1Mjg0NjQ0NjgsImlzcyI6Im9icmFtYXgiLCJqdGkiOiJhNmE1NWJkZi1kMzVhLTRiNTItOTQ4Yy0xMjhkYmMyODZlZjYiLCJuYmYiOjE1Mjg0NjQ0NjcsInN1YiI6IjE0NTMiLCJ0eXAiOiJhY2Nlc3MifQ.ZW0vcNyRTiM_tgM27njihrLWLx8Qqqh_vGyJZdnsnzKu99865YaN03VttTznzuybaBAg8NqrGeDsVsg3WZTsPQ",
   phoenix_recycled: true, plug_skip_csrf_protection: true},
 query_params: %Plug.Conn.Unfetched{aspect: :query_params}, query_string: "",
 remote_ip: {127, 0, 0, 1}, req_cookies: %Plug.Conn.Unfetched{aspect: :cookies},
 req_headers: [], request_path: "/", resp_body: nil, resp_cookies: %{},
 resp_headers: [{"cache-control", "max-age=0, private, must-revalidate"}],
 scheme: :http, script_name: [], secret_key_base: nil, state: :unset,
 status: nil}

After the first request:

%Plug.Conn{adapter: {Plug.Adapters.Test.Conn, :...},
 assigns: %{current_user: %MyApp.Accounts.User{__meta__: #Ecto.Schema.Metadata<:loaded, "users">,
    email: "email_8866@example.com", id: 1453, image: "avatar.jpg",
    inserted_at: ~N[2018-06-08 13:27:48.545323], name: "Elmer J. Fudd 8866",
    uid: "8866", updated_at: ~N[2018-06-08 13:27:48.545340]}},
 before_send: [#Function<0.113748926/1 in Plug.CSRFProtection.call/2>,
  #Function<4.31088025/1 in Phoenix.Controller.fetch_flash/2>,
  #Function<0.112984571/1 in Plug.Session.before_send/2>,
  #Function<1.125774924/1 in Plug.Logger.call/2>], body_params: %{},
 cookies: %{"_my_app_key" => "SFMyNTY.g3QAAAABbQAAAA1waG9lbml4X2ZsYXNodAAAAAFtAAAABGluZm9tAAAAGlJvbGUgZGVsZXRlZCBzdWNjZXNzZnVsbHku.x1KW5_8UKbMmKGRtuWO4Htx5WV37QAI3nYqleFSdT80"},
 halted: false, host: "foo.localhost", method: "DELETE", owner: #PID<0.617.0>,
 params: %{"id" => "238"}, path_info: ["admin", "roles", "238"],
 path_params: %{"id" => "238"}, peer: {{127, 0, 0, 1}, 111317}, port: 80,
 private: %{MyAppWeb.Router => {[],
    %{MyAppWeb.Plug.API.Version => ["api"]}},
   :guardian_default_claims => %{"aud" => "my_app", "exp" => 1530883668,
     "iat" => 1528464468, "iss" => "my_app",
     "jti" => "a6a55bdf-d35a-4b52-948c-128dbc286ef6", "nbf" => 1528464467,
     "sub" => "1453", "typ" => "access"},
   :guardian_default_resource => %MyApp.Accounts.User{__meta__: #Ecto.Schema.Metadata<:loaded, "users">,
    email: "email_8866@example.com", id: 1453, image: "avatar.jpg",
    inserted_at: ~N[2018-06-08 13:27:48.545323], name: "Elmer J. Fudd 8866",
    uid: "8866", updated_at: ~N[2018-06-08 13:27:48.545340]},
   :guardian_default_token => "eyJhbGciOiJIUzUxMiIsInR5cCI6IkpXVCJ9.eyJhdWQiOiJvYnJhbWF4IiwiZXhwIjoxNTMwODgzNjY4LCJpYXQiOjE1Mjg0NjQ0NjgsImlzcyI6Im9icmFtYXgiLCJqdGkiOiJhNmE1NWJkZi1kMzVhLTRiNTItOTQ4Yy0xMjhkYmMyODZlZjYiLCJuYmYiOjE1Mjg0NjQ0NjcsInN1YiI6IjE0NTMiLCJ0eXAiOiJhY2Nlc3MifQ.ZW0vcNyRTiM_tgM27njihrLWLx8Qqqh_vGyJZdnsnzKu99865YaN03VttTznzuybaBAg8NqrGeDsVsg3WZTsPQ",
   :guardian_error_handler => MyAppWeb.Guardian.ErrorHandler,
   :guardian_module => MyApp.Guardian, :phoenix_action => :delete,
   :phoenix_controller => MyAppWeb.Telesales.Admin.RoleController,
   :phoenix_endpoint => MyAppWeb.Endpoint,
   :phoenix_flash => %{"info" => "Role deleted successfully."},
   :phoenix_format => "html", :phoenix_layout => {MyAppWeb.LayoutView, :app},
   :phoenix_pipelines => [:browser, :login_required],
   :phoenix_recycled => false, :phoenix_router => MyAppWeb.Router,
   :phoenix_view => MyAppWeb.Telesales.Admin.RoleView,
   :plug_session => %{"phoenix_flash" => %{"info" => "Role deleted successfully."}},
   :plug_session_fetch => :done, :plug_session_info => :write,
   :plug_skip_csrf_protection => true}, query_params: %{}, query_string: "",
 remote_ip: {127, 0, 0, 1}, req_cookies: %{}, req_headers: [],
 request_path: "/admin/roles/238",
 resp_body: "<html><body>You are being <a href=\"/admin/roles\">redirected</a>.</body></html>",
 resp_cookies: %{"_my_app_key" => %{value: "SFMyNTY.g3QAAAABbQAAAA1waG9lbml4X2ZsYXNodAAAAAFtAAAABGluZm9tAAAAGlJvbGUgZGVsZXRlZCBzdWNjZXNzZnVsbHku.x1KW5_8UKbMmKGRtuWO4Htx5WV37QAI3nYqleFSdT80"}},
 resp_headers: [{"set-cookie",
   "_my_app_key=SFMyNTY.g3QAAAABbQAAAA1waG9lbml4X2ZsYXNodAAAAAFtAAAABGluZm9tAAAAGlJvbGUgZGVsZXRlZCBzdWNjZXNzZnVsbHku.x1KW5_8UKbMmKGRtuWO4Htx5WV37QAI3nYqleFSdT80; path=/; HttpOnly"},
  {"content-type", "text/html; charset=utf-8"},
  {"cache-control", "max-age=0, private, must-revalidate"},
  {"x-frame-options", "SAMEORIGIN"}, {"x-xss-protection", "1; mode=block"},
  {"x-content-type-options", "nosniff"}, {"x-download-options", "noopen"},
  {"x-permitted-cross-domain-policies", "none"}, {"location", "/admin/roles"}],
 scheme: :http, script_name: [],
 secret_key_base: "CTLiPUrLFZE13qML+i7pPbZqkHJUgQC/+u/e96EdcqT4DlNOxMGCw4+vItUos3k2",
 state: :sent, status: 302}

Here is my router:

defmodule MyAppWeb.Router do
  use MyAppWeb, :router

  pipeline :browser do
    plug :accepts, ["html"]
    plug :fetch_session
    plug :fetch_flash
    plug :protect_from_forgery
    plug :put_secure_browser_headers
  end

  pipeline :login_required do
    plug MyAppWeb.Guardian.AuthPipeline
    plug MyAppWeb.Plug.CurrentUser
  end

  forward "/api", MyAppWeb.Plug.API.Version

  scope host: "foo.", alias: MyAppWeb.Telesales, as: :telesales do
    pipe_through :browser

    scope "/admin", Admin, as: :admin do
      pipe_through :login_required

      resources "/roles", RoleController, only: [:index, :delete]
    end

    scope "/" do
      pipe_through :login_required

      resources "/sessions", SessionController, only: [:show], singleton: true
    end

    delete "/auth/logout", SessionController, :delete

    get "/auth/login", SessionController, :new
    get "/auth/:provider/callback", SessionController, :callback
    get "/auth/:provider", SessionController, :request
  end

  scope "/", MyAppWeb do
    pipe_through :browser # Use the default browser stack

    get "/", PageController, :index
  end

  # Other scopes may use custom stacks.
  # scope "/api", MyAppWeb do
  #   pipe_through :api
  # end
end
1 Like

I think because role 230 doesnt exist or deleted already. Can you try another role you are sure it is exist ?

Sorry about that, but I pasted the wrong error. Here is the correct one:

3) test delete role deletes chosen role (MyAppWeb.Telesales.Admin.RoleControllerTest)
     test/my_app_web/controllers/telesales/admin/role_controller_test.exs:47
     ** (Phoenix.Router.NoRouteError) no route found for GET /admin/roles (MyAppWeb.Router)
     code: conn = get conn, telesales_admin_role_path(conn, :index)
     stacktrace:
       (my_app) lib/my_app_web/router.ex:1: MyAppWeb.Router.__match_route__/4
       (my_app) lib/phoenix/router.ex:303: MyAppWeb.Router.call/2
       (my_app) lib/my_app_web/endpoint.ex:1: MyAppWeb.Endpoint.plug_builder_call/2
       (my_app) lib/my_app_web/endpoint.ex:1: MyAppWeb.Endpoint.call/2
       (phoenix) lib/phoenix/test/conn_test.ex:224: Phoenix.ConnTest.dispatch/5
       test/my_app_web/controllers/telesales/admin/role_controller_test.exs:51: (test)

Just for the record, here is the code:

    test "deletes chosen role", %{conn: conn, role: role} do
      conn = delete conn, telesales_admin_role_path(conn, :delete, role)
      assert redirected_to(conn, 302) =~ telesales_admin_role_path(conn, :index)

      conn = get conn, telesales_admin_role_path(conn, :index)
      refute html_response(conn, 200) =~ role.user.name
    end

The error is caused by this line: conn = get conn, telesales_admin_role_path(conn, :index), where I am reusing the conn to issue a new GET request.

so it tries that url foo.localhost:80/admin/roles is this right url ?

No, the port is 4000. foo.localhost:4000/admin/roles Does exist. I don’t know where port 80 is coming from. The only thing I change when building the connection is the host:

setup do
    host = "foo." <> MyAppWeb.Endpoint.config(:url)[:host]
    conn = Phoenix.ConnTest.build_conn(:get, "http://#{host}/", nil)
    {:ok, conn: conn}
end

It’s strange because it happens any time I try to reuse the conn. So, any request works fine. The subsequent request reusing the conn from the first request yields this error.

You have said the error is because you are “reusing” the conn. Are you sure? If you change the first two lines to bind a different name, does the test pass? e.g. change to:

 conn2 = delete conn, telesales_admin_role_path(conn, :delete, role)
 assert redirected_to(conn2) == telesales_admin_role_path(conn, :index)

conn = get conn, telesales_admin_role_path(conn, :index)
refute html_response(conn, 200) =~ role.user.name

If that passes, then I would guess the endpoint is resetting the URL to what it is configured for in the response.

If that passes, then I would guess the endpoint is resetting the URL to what it is configured for in the response.

@jeremyjh yes, that passes. I tried that before, and again now, to be sure. The tests pass.

You may have to do that as a workaround. I don’t know that this is an intended usage of path; its there so you can make a preset conn that includes a path and optionally params.

1 Like

OK. So is there no canonical way to add a subdomain when testing routes behind subdomain host?