Repository: phoenixframework/phoenix
Pull request #4168 (2021-01-25 03:10:31)

Copy Editing: Guides - Directory structure


Patch:
diff --git a/guides/controllers.md b/guides/controllers.md
index e1d166531..6f27eb06f 100644
--- a/guides/controllers.md
+++ b/guides/controllers.md
@@ -416,7 +416,7 @@ Fortunately, our application layout, `lib/hello_web/templates/layout/app.html.ee
 
 When we reload the [Welcome Page](http://localhost:4000/), our messages should appear just above "Welcome to Phoenix!"
 
-The flash functionality is handy when mixed with redirects. Perhaps you want to redirect to a page with some extra information. If we re-use the redirect action from the previous section, we can do:
+The flash functionality is handy when mixed with redirects. Perhaps you want to redirect to a page with some extra information. If we reuse the redirect action from the previous section, we can do:
 
 ```elixir
   def index(conn, _params) do
diff --git a/guides/directory_structure.md b/guides/directory_structure.md
index 51690c150..b93b1fda5 100644
--- a/guides/directory_structure.md
+++ b/guides/directory_structure.md
@@ -20,7 +20,7 @@ When we use `mix phx.new` to generate a new Phoenix application, it builds a top
 
 We will go over those directories one by one:
 
-  * `_build` - a directory created by the `mix` command line tool that ships as part of Elixir that holds all compilation artifacts. As we have seen in "Up and Running", `mix` is the main interface to your application. We use Mix to compile our code, create databases, run our server, and more. This directory must not be checked into version control and it can be removed at any time. Removing it will force Mix to rebuild your application from scratch
+  * `_build` - a directory created by the `mix` command line tool that ships as part of Elixir that holds all compilation artifacts. As we have seen in "[Up and Running](up_and_running.html)", `mix` is the main interface to your application. We use Mix to compile our code, create databases, run our server, and more. This directory must not be checked into version control and it can be removed at any time. Removing it will force Mix to rebuild your application from scratch
 
   * `assets` - a directory that keeps everything related to front-end assets, such as JavaScript, CSS, static images and more. It is typically handled by the `npm` tool. Phoenix developers typically only need to run `npm install` inside the assets directory. Everything else is managed by Phoenix
 
@@ -65,7 +65,7 @@ If it is your first time with Phoenix, you don't need to worry about the details
 
 You can learn more about applications in [Elixir's official docs for Application](https://hexdocs.pm/elixir/Application.html).
 
-In the same `lib/hello` directory, we will find a `lib/hello/repo.ex`. It defines a `Hello.Repo` module which is our main interface to the database. If you are using Postgres (the default), you will see something like this:
+In the same `lib/hello` directory, we will find a `lib/hello/repo.ex`. It defines a `Hello.Repo` module which is our main interface to the database. If you are using Postgres (the default database), you will see something like this:
 
 ```elixir
 defmodule Hello.Repo do
@@ -103,13 +103,13 @@ lib/hello_web
 └── telemetry.ex
 ```
 
-All of the files which are currently in the `controllers`, `templates`, and `views` directories are there to create the "Welcome to Phoenix!" page we saw in the "Up and running" guide.
+All of the files which are currently in the `controllers`, `templates`, and `views` directories are there to create the "Welcome to Phoenix!" page we saw in the "[Up and running](up_and_running.html)" guide.
 The `channels` directory is where we will add code related to building real-time Phoenix applications.
 
 By looking at `templates` and `views` directories, we can see Phoenix provides features for handling layouts and error pages out of the box.
 
-Besides the directories mentioned, `lib/hello_web` has four files at its root. `lib/hello_web/endpoint.ex` is the entry-point for HTTP requests. Once the browser accesses `http://localhost:4000`, the endpoint starts processing the data, eventually leading to the router, which is defined in `lib/hello_web/router.ex`. The router defines the rules to dispatch requests to "controllers", which then uses "views" and "templates" to render HTML pages back to clients. We explore these layers in length in other guides, starting with the "Request life-cycle" guide coming next.
+Besides the directories mentioned, `lib/hello_web` has four files at its root. `lib/hello_web/endpoint.ex` is the entry-point for HTTP requests. Once the browser accesses [http://localhost:4000](http://localhost:4000), the endpoint starts processing the data, eventually leading to the router, which is defined in `lib/hello_web/router.ex`. The router defines the rules to dispatch requests to "controllers", which then uses "views" and "templates" to render HTML pages back to clients. We explore these layers in length in other guides, starting with the "[Request life-cycle](request_lifecycle.html)" guide coming next.
 
-Through _telemetry_, Phoenix is able to collect metrics and send monitoring events of your application. The `lib/hello_web/telemetry.ex` file defines the supervisor responsible for managing the telemetry processes. You can find more information on this topic in the [Telemetry guide](telemetry.html).
+Through _Telemetry_, Phoenix is able to collect metrics and send monitoring events of your application. The `lib/hello_web/telemetry.ex` file defines the supervisor responsible for managing the telemetry processes. You can find more information on this topic in the [Telemetry guide](telemetry.html).
 
 Finally, there is a `lib/hello_web/gettext.ex` file which provides internationalization through [Gettext](https://hexdocs.pm/gettext/Gettext.html). If you are not worried about internationalization, you can safely skip this file and its contents.
diff --git a/guides/plug.md b/guides/plug.md
index 8a8dd6ec9..23d1e80b4 100644
--- a/guides/plug.md
+++ b/guides/plug.md
@@ -72,7 +72,7 @@ Module plugs are another type of Plug that let us define a connection transforma
 - `init/1` which initializes any arguments or options to be passed to `call/2`
 - `call/2` which carries out the connection transformation. `call/2` is just a function plug that we saw earlier
 
-To see this in action, let's write a module plug that puts the `:locale` key and value into the connection assign for downstream use in other plugs, controller actions, and our views. Put the contents above to a file named "lib/hello_web/plugs/locale.ex":
+To see this in action, let's write a module plug that puts the `:locale` key and value into the connection assign for downstream use in other plugs, controller actions, and our views. Put the contents above to a file named `lib/hello_web/plugs/locale.ex`:
 
 ```elixir
 defmodule HelloWeb.Plugs.Locale do
@@ -111,7 +111,7 @@ defmodule HelloWeb.Router do
 
 We are able to add this module plug to our browser pipeline via `plug HelloWeb.Plugs.Locale, "en"`. In the `init/1` callback, we pass a default locale to use if none is present in the params. We also use pattern matching to define multiple `call/2` function heads to validate the locale in the params, and fall back to "en" if there is no match.
 
-To see the assign in action, go to the layout in "lib/hello_web/templates/layout/app.html.eex" and add the following close to the main container:
+To see the assign in action, go to the layout in `lib/hello_web/templates/layout/app.html.eex` and add the following close to the main container:
 
 ```html
 <main role="main" class="container">
diff --git a/guides/request_lifecycle.md b/guides/request_lifecycle.md
index a27ff073d..3504192a0 100644
--- a/guides/request_lifecycle.md
+++ b/guides/request_lifecycle.md
@@ -101,13 +101,13 @@ We'll save a discussion of `use HelloWeb, :controller` for the [Controllers Guid
 
 All controller actions take two arguments. The first is `conn`, a struct which holds a ton of data about the request. The second is `params`, which are the request parameters. Here, we are not using `params`, and we avoid compiler warnings by adding the leading `_`.
 
-The core of this action is `render(conn, "index.html")`. It tells Phoenix to render "index.html". The modules responsible for rendering are called views. By default, Phoenix views are named after the controller, so Phoenix is expecting a `HelloWeb.HelloView` to exist and handle "index.html" for us.
+The core of this action is `render(conn, "index.html")`. It tells Phoenix to render `"index.html"`. The modules responsible for rendering are called views. By default, Phoenix views are named after the controller, so Phoenix is expecting a `HelloWeb.HelloView` to exist and handle `"index.html"` for us.
 
 > Note: Using an atom as the template name also works `render(conn, :index)`. In these cases, the template will be chosen based off the Accept headers, e.g. `"index.html"` or `"index.json"`.
 
 ### A new View
 
-Phoenix views act as the presentation layer. For example, we expect the output of rendering "index.html" to be a complete HTML page. To make our lives easier, we often use templates for creating those HTML pages.
+Phoenix views act as the presentation layer. For example, we expect the output of rendering `"index.html"` to be a complete HTML page. To make our lives easier, we often use templates for creating those HTML pages.
 
 Let's create a new view. Create `lib/hello_web/views/hello_view.ex` and make it look like this:
 
@@ -119,7 +119,7 @@ end
 
 Now in order to add templates to this view, we simply need to add files to the `lib/hello_web/templates/hello` directory. Note the controller name (`HelloController`), the view name (`HelloView`), and the template directory (`hello`) all follow the same naming convention and are named after each other.
 
-A template file has the following structure: `NAME.FORMAT.TEMPLATING_LANGUAGE`. In our case, we will create a "index.html.eex" file at "lib/hello_web/templates/hello/index.html.eex". ".eex" stands for `EEx`, which is a library for embedding Elixir that ships as part of Elixir itself. Phoenix enhances EEx to include automatic escaping of values. This protects you from security vulnerabilities like Cross-Site-Scripting with no extra work on your part.
+A template file has the following structure: `NAME.FORMAT.TEMPLATING_LANGUAGE`. In our case, we will create a `index.html.eex` file at `lib/hello_web/templates/hello/index.html.eex`. ".eex" stands for `EEx`, which is a library for embedding Elixir that ships as part of Elixir itself. Phoenix enhances EEx to include automatic escaping of values. This protects you from security vulnerabilities like Cross-Site-Scripting with no extra work on your part.
 
 Create `lib/hello_web/templates/hello/index.html.eex` and make it look like this:
 
@@ -188,7 +188,7 @@ As we did last time, the first thing we'll do is create a new route.
 
 ### Another new Route
 
-For this exercise, we're going to re-use the `HelloController` we just created and just add a new `show` action. We'll add a line just below our last route, like this:
+For this exercise, we're going to reuse the `HelloController` we just created and just add a new `show` action. We'll add a line just below our last route, like this:
 
 ```elixir
 scope "/", HelloWeb do
diff --git a/guides/views.md b/guides/views.md
index 0339f5fc7..e774091f5 100644
--- a/guides/views.md
+++ b/guides/views.md
@@ -220,7 +220,7 @@ defmodule HelloWeb.PageView do
 end
 ```
 
-In the view we see our `render/2` function pattern matching on `"index.json"`, `"show.json"`, and `"page.json"`. The "index.json" and "show.json" are the ones requested directly from the controller. They also match on the assigns sent by the controller. `"index.json"` will respond with JSON like this:
+In the view we see our `render/2` function pattern matching on `"index.json"`, `"show.json"`, and `"page.json"`. The `"index.json"` and `"show.json"` are the ones requested directly from the controller. They also match on the assigns sent by the controller. `"index.json"` will respond with JSON like this:
 
 ```javascript
 {
@@ -245,7 +245,7 @@ And the `render/2` matching `"show.json"`:
 }
 ```
 
-This works because both "index.json" and "show.json" build themselves on top of an internal "page.json" clause.
+This works because both `index.json` and `show.json` build themselves on top of an internal `page.json` clause.
 
 The `render_many/3` function takes the data we want to respond with (`pages`), a view, and a string to pattern match on the `render/2` function defined on view. It will map over each item in `pages` and call `PageView.render("page.json", %{page: page})`. `render_one/3` follows the same signature, ultimately using the `render/2` matching `page.json` to specify what each `page` looks like.
 
diff --git a/lib/mix/tasks/phx.gen.auth.ex b/lib/mix/tasks/phx.gen.auth.ex
index a079c1672..26c580ad6 100644
--- a/lib/mix/tasks/phx.gen.auth.ex
+++ b/lib/mix/tasks/phx.gen.auth.ex
@@ -85,7 +85,7 @@ defmodule Mix.Tasks.Phx.Gen.Auth do
              table: :string, merge_with_existing_context: :boolean]
 
   @doc false
-  def run(args) do
+  def run(args, test_opts \\ []) do
     if Mix.Project.umbrella?() do
       Mix.raise("mix phx.gen.auth can only be run inside an application directory")
     end
@@ -99,16 +99,21 @@ defmodule Mix.Tasks.Phx.Gen.Auth do
     {context, schema} = Gen.Context.build(context_args, __MODULE__)
     Gen.Context.prompt_for_code_injection(context)
 
-    # Needed so we can get the ecto adapter and ensure other
-    # libraries are loaded.
-    #
-    # As far as I can tell, everything after this must be tested with an
-    # integration test.
-    Mix.Task.run("compile")
+    if Keyword.get(test_opts, :validate_dependencies?, true) do
+      # Needed so we can get the ecto adapter and ensure other
+      # libraries are loaded.
+      Mix.Task.run("compile")
 
-    validate_required_dependencies!()
+      validate_required_dependencies!()
+    end
+
+    ecto_adapter =
+      Keyword.get_lazy(
+        test_opts,
+        :ecto_adapter,
+        fn -> get_ecto_adapter!(schema) end
+      )
 
-    ecto_adapter = get_ecto_adapter!(schema)
     migration = Migration.build(ecto_adapter)
 
     binding = [
@@ -311,7 +316,7 @@ defmodule Mix.Tasks.Phx.Gen.Auth do
       {:error, :unable_to_inject} ->
         Mix.shell().info("""
 
-        Add your #{inspect(mix_dependency)} dependency to #{file_path}:
+        Add your #{mix_dependency} dependency to #{file_path}:
 
             defp deps do
               [
diff --git a/mix.exs b/mix.exs
index c513708e6..08c3f221b 100644
--- a/mix.exs
+++ b/mix.exs
@@ -79,6 +79,7 @@ defmodule Phoenix.MixProject do
       {:telemetry_metrics, "~> 0.4", only: :docs},
 
       # Test dependencies
+      {:phx_new, path: "./installer", only: :test},
       {:websocket_client, git: "https://github.com/jeremyong/websocket_client.git", only: :test}
     ]
   end


Test patch:
diff --git a/guides/testing/testing_channels.md b/guides/testing/testing_channels.md
index 7a0a398cb..2c8ebbd20 100644
--- a/guides/testing/testing_channels.md
+++ b/guides/testing/testing_channels.md
@@ -77,7 +77,7 @@ setup do
 end
 ```
 
-The `setup` block sets up a `Phoenix.Socket` based on the `UserSocket` module, which you can find at "lib/hello_web/channels/user_socket.ex". Then it says we want to subscribe and join the `RoomChannel`, accessible as "room:lobby" in the `UserSocket`. At the end of the test, we return the `%{socket: socket}` as metadata, so we can re-use it on every test.
+The `setup` block sets up a `Phoenix.Socket` based on the `UserSocket` module, which you can find at `lib/hello_web/channels/user_socket.ex`. Then it says we want to subscribe and join the `RoomChannel`, accessible as `"room:lobby"` in the `UserSocket`. At the end of the test, we return the `%{socket: socket}` as metadata, so we can reuse it on every test.
 
 In a nutshell, `subscribe_and_join/3` emulates the client joining a channel and subscribes the test process to the given topic. This is a necessary step since clients need to join a channel before they can send and receive events on that channel.
 
diff --git a/guides/testing/testing_controllers.md b/guides/testing/testing_controllers.md
index 798cf56bd..09ede44cc 100644
--- a/guides/testing/testing_controllers.md
+++ b/guides/testing/testing_controllers.md
@@ -101,7 +101,7 @@ describe "create post" do
 end
 ```
 
-The first test starts with a `post/2` request. That's because once the form in the "/posts/new" page is submitted, it becomes a POST request to the create action. Because we have supplied valid attributes, the post should have been successfully created and we should have redirected to the show action of the new post. This new page will have an address like "/posts/ID", where ID is the identifier of the post in the database.
+The first test starts with a `post/2` request. That's because once the form in the `/posts/new` page is submitted, it becomes a POST request to the create action. Because we have supplied valid attributes, the post should have been successfully created and we should have redirected to the show action of the new post. This new page will have an address like `/posts/ID`, where ID is the identifier of the post in the database.
 
 We then use `redirected_params(conn)` to get the ID of the post and then match that we indeed redirected to the show action. Finally, we do request a `get` request to the page we redirected to, allowing us to verify that the post was indeed created.
 
@@ -185,7 +185,7 @@ end
   1. An exception was raised
   2. The exception has a status code equivalent to 404 (which stands for Not Found)
 
-This pretty much mimics how Phoenix handles exceptions. For example, when we access "/posts/12345" where 12345 is an ID that does not exist, we will invoke our `show` action:
+This pretty much mimics how Phoenix handles exceptions. For example, when we access `/posts/12345` where `12345` is an ID that does not exist, we will invoke our `show` action:
 
 ```elixir
 def show(conn, %{"id" => id}) do
@@ -255,7 +255,7 @@ def index(conn, _params) do
 end
 ```
 
-The action gets all articles and renders "index.json". Since we are talking about JSON, we don't have a "index.json.eex" template. Instead, the code that converts "articles" into JSON can be found directly in the ArticleView module, defined at `lib/hello_web/views/article_view.ex` like this:
+The action gets all articles and renders `index.json`. Since we are talking about JSON, we don't have a `index.json.eex` template. Instead, the code that converts `articles` into JSON can be found directly in the ArticleView module, defined at `lib/hello_web/views/article_view.ex` like this:
 
 ```elixir
 defmodule HelloWeb.ArticleView do
diff --git a/installer/test/mix_helper.exs b/installer/test/mix_helper.exs
index fc06436b9..3513896d5 100644
--- a/installer/test/mix_helper.exs
+++ b/installer/test/mix_helper.exs
@@ -98,12 +98,27 @@ defmodule MixHelper do
     end
   end
 
-  def with_generator_env(new_env, fun) do
-    Application.put_env(:phoenix, :generators, new_env)
+  def modify_file(path, function) when is_binary(path) and is_function(function, 1) do
+    path
+    |> File.read!()
+    |> function.()
+    |> write_file!(path)
+  end
+
+  defp write_file!(content, path) do
+    File.write!(path, content)
+  end
+
+  def with_generator_env(app_name \\ :phoenix, new_env, fun) do
+    config_before = Application.fetch_env(app_name, :generators)
+    Application.put_env(app_name, :generators, new_env)
     try do
       fun.()
     after
-      Application.delete_env(:phoenix, :generators)
+      case config_before do
+        {:ok, config} -> Application.put_env(app_name, :generators, config)
+        :error -> Application.delete_env(app_name, :generators)
+      end
     end
   end
 
diff --git a/test/mix/tasks/phx.gen.auth_test.exs b/test/mix/tasks/phx.gen.auth_test.exs
index 8890c8e37..1eac38a1d 100644
--- a/test/mix/tasks/phx.gen.auth_test.exs
+++ b/test/mix/tasks/phx.gen.auth_test.exs
@@ -11,8 +11,26 @@ defmodule Mix.Tasks.Phx.Gen.AuthTest do
     :ok
   end
 
+  defp in_tmp_phx_project(test, additional_args \\ [], func) do
+    in_tmp(test, fn ->
+      Mix.Tasks.Phx.New.run(~w(my_app --no-install) ++ additional_args)
+      in_project(:my_app, "my_app", fn _module ->
+        func.()
+      end)
+    end)
+  end
+
+  defp in_tmp_phx_umbrella_project(test, func) do
+    in_tmp(test, fn ->
+      Mix.Tasks.Phx.New.run(~w(my_app --umbrella --no-install))
+      File.cd!("my_app_umbrella", fn ->
+        func.()
+      end)
+    end)
+  end
+
   test "invalid mix arguments", config do
-    in_tmp_project(config.test, fn ->
+    in_tmp_phx_project(config.test, fn ->
       assert_raise Mix.Error, ~r/Expected the context, "accounts", to be a valid module name.*phx\.gen\.auth/s, fn ->
         Gen.Auth.run(~w(accounts User users))
       end
@@ -25,12 +43,12 @@ defmodule Mix.Tasks.Phx.Gen.AuthTest do
         Gen.Auth.run(~w(User User users))
       end
 
-      assert_raise Mix.Error, ~r/Cannot generate context Phoenix because it has the same name as the application/, fn ->
-        Gen.Auth.run(~w(Phoenix User users))
+      assert_raise Mix.Error, ~r/Cannot generate context MyApp because it has the same name as the application/, fn ->
+        Gen.Auth.run(~w(MyApp User users))
       end
 
-      assert_raise Mix.Error, ~r/Cannot generate schema Phoenix because it has the same name as the application/, fn ->
-        Gen.Auth.run(~w(Accounts Phoenix users))
+      assert_raise Mix.Error, ~r/Cannot generate schema MyApp because it has the same name as the application/, fn ->
+        Gen.Auth.run(~w(Accounts MyApp users))
       end
 
       assert_raise Mix.Error, ~r/Invalid arguments/, fn ->
@@ -54,4 +72,804 @@ defmodule Mix.Tasks.Phx.Gen.AuthTest do
       end
     end)
   end
+
+  test "generates with defaults", config do
+    in_tmp_phx_project(config.test, fn ->
+      Gen.Auth.run(
+        ~w(Accounts User users),
+        [ecto_adapter: Ecto.Adapters.Postgres, validate_dependencies?: false]
+      )
+
+      assert_file "config/test.exs", fn file ->
+        assert file =~ "config :bcrypt_elixir, :log_rounds, 1"
+      end
+
+      assert_file "lib/my_app/accounts.ex"
+      assert_file "lib/my_app/accounts/user.ex"
+      assert_file "lib/my_app/accounts/user_token.ex"
+      assert_file "lib/my_app/accounts/user_notifier.ex"
+      assert_file "test/my_app/accounts_test.exs"
+      assert_file "test/support/fixtures/accounts_fixtures.ex"
+      assert_file "lib/my_app_web/controllers/user_auth.ex"
+      assert_file "test/my_app_web/controllers/user_auth_test.exs"
+      assert_file "lib/my_app_web/views/user_confirmation_view.ex"
+      assert_file "lib/my_app_web/templates/user_confirmation/new.html.eex"
+      assert_file "lib/my_app_web/controllers/user_confirmation_controller.ex"
+      assert_file "test/my_app_web/controllers/user_confirmation_controller_test.exs"
+      assert_file "lib/my_app_web/templates/layout/_user_menu.html.eex"
+      assert_file "lib/my_app_web/controllers/user_registration_controller.ex"
+      assert_file "lib/my_app_web/views/user_registration_view.ex"
+      assert_file "test/my_app_web/controllers/user_registration_controller_test.exs"
+      assert_file "lib/my_app_web/controllers/user_reset_password_controller.ex"
+      assert_file "lib/my_app_web/templates/user_reset_password/edit.html.eex"
+      assert_file "lib/my_app_web/templates/user_reset_password/new.html.eex"
+      assert_file "lib/my_app_web/views/user_reset_password_view.ex"
+      assert_file "test/my_app_web/controllers/user_reset_password_controller_test.exs"
+      assert_file "lib/my_app_web/controllers/user_session_controller.ex"
+      assert_file "lib/my_app_web/templates/user_session/new.html.eex"
+      assert_file "test/my_app_web/controllers/user_session_controller_test.exs"
+      assert_file "lib/my_app_web/views/user_session_view.ex"
+      assert_file "lib/my_app_web/controllers/user_settings_controller.ex"
+      assert_file "lib/my_app_web/templates/user_settings/edit.html.eex"
+      assert_file "lib/my_app_web/views/user_settings_view.ex"
+      assert_file "test/my_app_web/controllers/user_settings_controller_test.exs"
+
+      assert [migration] = Path.wildcard("priv/repo/migrations/*_create_users_auth_tables.exs")
+      assert_file migration, fn file ->
+        assert file =~ "create table(:users) do"
+        assert file =~ "create table(:users_tokens) do"
+      end
+
+      assert_file "mix.exs", fn file ->
+        assert file =~ ~s|{:bcrypt_elixir, "~> 2.0"},|
+      end
+
+      assert_file "lib/my_app_web/router.ex", fn file ->
+        assert file =~ "import MyAppWeb.UserAuth"
+        assert file =~ "plug :fetch_current_user"
+
+        assert file =~ """
+          ## Authentication routes
+
+          scope "/", MyAppWeb do
+            pipe_through [:browser, :redirect_if_user_is_authenticated]
+
+            get "/users/register", UserRegistrationController, :new
+            post "/users/register", UserRegistrationController, :create
+            get "/users/log_in", UserSessionController, :new
+            post "/users/log_in", UserSessionController, :create
+            get "/users/reset_password", UserResetPasswordController, :new
+            post "/users/reset_password", UserResetPasswordController, :create
+            get "/users/reset_password/:token", UserResetPasswordController, :edit
+            put "/users/reset_password/:token", UserResetPasswordController, :update
+          end
+
+          scope "/", MyAppWeb do
+            pipe_through [:browser, :require_authenticated_user]
+
+            get "/users/settings", UserSettingsController, :edit
+            put "/users/settings", UserSettingsController, :update
+            get "/users/settings/confirm_email/:token", UserSettingsController, :confirm_email
+          end
+
+          scope "/", MyAppWeb do
+            pipe_through [:browser]
+
+            delete "/users/log_out", UserSessionController, :delete
+            get "/users/confirm", UserConfirmationController, :new
+            post "/users/confirm", UserConfirmationController, :create
+            get "/users/confirm/:token", UserConfirmationController, :confirm
+          end
+        """
+      end
+
+      assert_file "lib/my_app_web/templates/layout/app.html.eex", fn file ->
+        assert file =~ ~s|<%= render "_user_menu.html", assigns %>|
+      end
+
+      assert_file "test/support/conn_case.ex", fn file ->
+        assert file =~ "def register_and_log_in_user(%{conn: conn})"
+        assert file =~ "def log_in_user(conn, user)"
+      end
+
+      assert_received {:mix_shell, :info, ["""
+
+      Please re-fetch your dependencies with the following command:
+
+          mix deps.get
+      """]}
+
+      assert_received {:mix_shell, :info, ["""
+
+      Remember to update your repository by running migrations:
+
+        $ mix ecto.migrate
+      """]}
+    end)
+  end
+
+  test "works with apps generated with --live", config do
+    in_tmp_phx_project(config.test, ~w(--live), fn ->
+      Gen.Auth.run(
+        ~w(Accounts User users),
+        [ecto_adapter: Ecto.Adapters.Postgres, validate_dependencies?: false]
+      )
+
+      assert_file "lib/my_app_web/templates/layout/root.html.leex", fn file ->
+        assert file =~ ~s|<%= render "_user_menu.html", assigns %>|
+      end
+
+      assert_file "lib/my_app_web/templates/layout/app.html.eex", fn file ->
+        refute file =~ ~s|<%= render "_user_menu.html", assigns %>|
+      end
+    end)
+  end
+
+  test "generates with --web option", config do
+    in_tmp_phx_project(config.test, fn ->
+      Gen.Auth.run(
+        ~w(Accounts User users --web warehouse),
+        [ecto_adapter: Ecto.Adapters.Postgres, validate_dependencies?: false]
+      )
+
+      assert_file "lib/my_app/accounts.ex"
+      assert_file "lib/my_app/accounts/user.ex"
+      assert_file "lib/my_app/accounts/user_token.ex"
+      assert_file "lib/my_app/accounts/user_notifier.ex"
+      assert_file "test/my_app/accounts_test.exs"
+      assert_file "test/support/fixtures/accounts_fixtures.ex"
+
+      assert_file "lib/my_app_web/controllers/warehouse/user_auth.ex", fn file ->
+        assert file =~ "defmodule MyAppWeb.Warehouse.UserAuth do"
+      end
+
+      assert_file "test/my_app_web/controllers/warehouse/user_auth_test.exs", fn file ->
+        assert file =~ "defmodule MyAppWeb.Warehouse.UserAuthTest do"
+      end
+
+      assert_file "lib/my_app_web/views/warehouse/user_confirmation_view.ex", fn file ->
+        assert file =~ "defmodule MyAppWeb.Warehouse.UserConfirmationView do"
+      end
+
+      assert_file "lib/my_app_web/templates/warehouse/user_confirmation/new.html.eex", fn file ->
+        assert file =~ ~S|<%= form_for :user, Routes.warehouse_user_confirmation_path(@conn, :create), fn f -> %>|
+        assert file =~ ~S|<%= link "Register", to: Routes.warehouse_user_registration_path(@conn, :new) %>|
+        assert file =~ ~S|<%= link "Log in", to: Routes.warehouse_user_session_path(@conn, :new) %>|
+      end
+
+      assert_file "lib/my_app_web/controllers/warehouse/user_confirmation_controller.ex", fn file ->
+        assert file =~ "defmodule MyAppWeb.Warehouse.UserConfirmationController do"
+      end
+
+      assert_file "test/my_app_web/controllers/warehouse/user_confirmation_controller_test.exs", fn file ->
+        assert file =~ "defmodule MyAppWeb.Warehouse.UserConfirmationControllerTest do"
+      end
+
+      assert_file "lib/my_app_web/templates/layout/_user_menu.html.eex", fn file ->
+        assert file =~ ~S|<%= link "Settings", to: Routes.warehouse_user_settings_path(@conn, :edit) %>|
+        assert file =~ ~S|<%= link "Log out", to: Routes.warehouse_user_session_path(@conn, :delete), method: :delete %>|
+        assert file =~ ~S|<%= link "Register", to: Routes.warehouse_user_registration_path(@conn, :new) %>|
+        assert file =~ ~S|<%= link "Log in", to: Routes.warehouse_user_session_path(@conn, :new) %>|
+      end
+
+      assert_file "lib/my_app_web/controllers/warehouse/user_registration_controller.ex", fn file ->
+        assert file =~ "defmodule MyAppWeb.Warehouse.UserRegistrationController do"
+      end
+
+      assert_file "lib/my_app_web/views/warehouse/user_registration_view.ex", fn file ->
+        assert file =~ "defmodule MyAppWeb.Warehouse.UserRegistrationView do"
+      end
+
+      assert_file "test/my_app_web/controllers/warehouse/user_registration_controller_test.exs", fn file ->
+        assert file =~ "defmodule MyAppWeb.Warehouse.UserRegistrationControllerTest do"
+      end
+
+      assert_file "lib/my_app_web/controllers/warehouse/user_reset_password_controller.ex", fn file ->
+        assert file =~ "defmodule MyAppWeb.Warehouse.UserResetPasswordController do"
+      end
+
+      assert_file "lib/my_app_web/templates/warehouse/user_reset_password/edit.html.eex", fn file ->
+        assert file =~ ~S|<%= form_for @changeset, Routes.warehouse_user_reset_password_path(@conn, :update, @token), fn f -> %>|
+        assert file =~ ~S|<%= link "Register", to: Routes.warehouse_user_registration_path(@conn, :new) %>|
+        assert file =~ ~S|<%= link "Log in", to: Routes.warehouse_user_session_path(@conn, :new) %>|
+      end
+
+      assert_file "lib/my_app_web/templates/warehouse/user_reset_password/new.html.eex", fn file ->
+        assert file =~ ~S|<%= form_for :user, Routes.warehouse_user_reset_password_path(@conn, :create), fn f -> %>|
+        assert file =~ ~S|<%= link "Register", to: Routes.warehouse_user_registration_path(@conn, :new) %>|
+        assert file =~ ~S|<%= link "Log in", to: Routes.warehouse_user_session_path(@conn, :new) %>|
+      end
+
+      assert_file "lib/my_app_web/views/warehouse/user_reset_password_view.ex", fn file ->
+        assert file =~ "defmodule MyAppWeb.Warehouse.UserResetPasswordView do"
+      end
+
+      assert_file "test/my_app_web/controllers/warehouse/user_reset_password_controller_test.exs", fn file ->
+        assert file =~ "defmodule MyAppWeb.Warehouse.UserResetPasswordControllerTest do"
+      end
+
+      assert_file "lib/my_app_web/controllers/warehouse/user_session_controller.ex", fn file ->
+        assert file =~ "defmodule MyAppWeb.Warehouse.UserSessionController do"
+      end
+
+      assert_file "lib/my_app_web/templates/warehouse/user_session/new.html.eex", fn file ->
+        assert file =~ ~S|<%= form_for @conn, Routes.warehouse_user_session_path(@conn, :create), [as: :user], fn f -> %>|
+        assert file =~ ~S|<%= link "Register", to: Routes.warehouse_user_registration_path(@conn, :new) %>|
+        assert file =~ ~S|<%= link "Forgot your password?", to: Routes.warehouse_user_reset_password_path(@conn, :new) %>|
+      end
+
+      assert_file "test/my_app_web/controllers/warehouse/user_session_controller_test.exs", fn file ->
+        assert file =~ "defmodule MyAppWeb.Warehouse.UserSessionControllerTest do"
+      end
+
+      assert_file "lib/my_app_web/views/warehouse/user_session_view.ex", fn file ->
+        assert file =~ "defmodule MyAppWeb.Warehouse.UserSessionView do"
+      end
+
+      assert_file "lib/my_app_web/controllers/warehouse/user_settings_controller.ex", fn file ->
+        assert file =~ "defmodule MyAppWeb.Warehouse.UserSettingsController do"
+      end
+
+      assert_file "lib/my_app_web/templates/warehouse/user_settings/edit.html.eex", fn file ->
+        assert file =~ ~S|<%= form_for @email_changeset, Routes.warehouse_user_settings_path(@conn, :update), fn f -> %>|
+        assert file =~ ~S|<%= form_for @password_changeset, Routes.warehouse_user_settings_path(@conn, :update), fn f -> %>|
+      end
+
+      assert_file "lib/my_app_web/views/warehouse/user_settings_view.ex", fn file ->
+        assert file =~ "defmodule MyAppWeb.Warehouse.UserSettingsView do"
+      end
+
+      assert_file "test/my_app_web/controllers/warehouse/user_settings_controller_test.exs", fn file ->
+        assert file =~ "defmodule MyAppWeb.Warehouse.UserSettingsControllerTest do"
+      end
+
+      assert [migration] = Path.wildcard("priv/repo/migrations/*_create_users_auth_tables.exs")
+      assert_file migration, fn file ->
+        assert file =~ "create table(:users) do"
+        assert file =~ "create table(:users_tokens) do"
+      end
+
+      assert_file "lib/my_app_web/router.ex", fn file ->
+        assert file =~ "import MyAppWeb.Warehouse.UserAuth"
+        assert file =~ "plug :fetch_current_user"
+
+        assert file =~ """
+          ## Authentication routes
+
+          scope "/warehouse", MyAppWeb.Warehouse, as: :warehouse do
+            pipe_through [:browser, :redirect_if_user_is_authenticated]
+
+            get "/users/register", UserRegistrationController, :new
+            post "/users/register", UserRegistrationController, :create
+            get "/users/log_in", UserSessionController, :new
+            post "/users/log_in", UserSessionController, :create
+            get "/users/reset_password", UserResetPasswordController, :new
+            post "/users/reset_password", UserResetPasswordController, :create
+            get "/users/reset_password/:token", UserResetPasswordController, :edit
+            put "/users/reset_password/:token", UserResetPasswordController, :update
+          end
+
+          scope "/warehouse", MyAppWeb.Warehouse, as: :warehouse do
+            pipe_through [:browser, :require_authenticated_user]
+
+            get "/users/settings", UserSettingsController, :edit
+            put "/users/settings", UserSettingsController, :update
+            get "/users/settings/confirm_email/:token", UserSettingsController, :confirm_email
+          end
+
+          scope "/warehouse", MyAppWeb.Warehouse, as: :warehouse do
+            pipe_through [:browser]
+
+            delete "/users/log_out", UserSessionController, :delete
+            get "/users/confirm", UserConfirmationController, :new
+            post "/users/confirm", UserConfirmationController, :create
+            get "/users/confirm/:token", UserConfirmationController, :confirm
+          end
+        """
+      end
+
+      assert_file "lib/my_app_web/templates/layout/app.html.eex", fn file ->
+        assert file =~ ~s|<%= render "_user_menu.html", assigns %>|
+      end
+
+      assert_file "test/support/conn_case.ex", fn file ->
+        assert file =~ "def register_and_log_in_user(%{conn: conn})"
+        assert file =~ "def log_in_user(conn, user)"
+      end
+    end)
+  end
+
+  describe "--database option" do
+    test "when the database is postgres", config do
+      in_tmp_phx_project(config.test, fn ->
+        Gen.Auth.run(
+          ~w(Accounts User users),
+          [ecto_adapter: Ecto.Adapters.Postgres, validate_dependencies?: false]
+        )
+
+        assert [migration] = Path.wildcard("priv/repo/migrations/*_create_users_auth_tables.exs")
+        assert_file migration, fn file ->
+          assert file =~ ~r/execute "CREATE EXTENSION IF NOT EXISTS citext", ""$/m
+          assert file =~ ~r/add :email, :citext, null: false$/m
+        end
+
+        assert_file "test/my_app_web/controllers/user_auth_test.exs", fn file ->
+          assert file =~ ~r/use MyAppWeb\.ConnCase, async: true$/m
+        end
+
+        assert_file "test/my_app_web/controllers/user_confirmation_controller_test.exs", fn file ->
+          assert file =~ ~r/use MyAppWeb\.ConnCase, async: true$/m
+        end
+
+        assert_file "test/my_app_web/controllers/user_registration_controller_test.exs", fn file ->
+          assert file =~ ~r/use MyAppWeb\.ConnCase, async: true$/m
+        end
+
+        assert_file "test/my_app_web/controllers/user_reset_password_controller_test.exs", fn file ->
+          assert file =~ ~r/use MyAppWeb\.ConnCase, async: true$/m
+        end
+
+        assert_file "test/my_app_web/controllers/user_session_controller_test.exs", fn file ->
+          assert file =~ ~r/use MyAppWeb\.ConnCase, async: true$/m
+        end
+
+        assert_file "test/my_app_web/controllers/user_settings_controller_test.exs", fn file ->
+          assert file =~ ~r/use MyAppWeb\.ConnCase, async: true$/m
+        end
+      end)
+    end
+
+    test "when the database is mysql", config do
+      in_tmp_phx_project(config.test, fn ->
+        Gen.Auth.run(
+          ~w(Accounts User users),
+          [ecto_adapter: Ecto.Adapters.MyXQL, validate_dependencies?: false]
+        )
+
+        assert [migration] = Path.wildcard("priv/repo/migrations/*_create_users_auth_tables.exs")
+        assert_file migration, fn file ->
+          refute file =~ ~r/execute "CREATE EXTENSION IF NOT EXISTS citext", ""$/m
+          assert file =~ ~r/add :email, :string, null: false, size: 160$/m
+        end
+
+        assert_file "test/my_app_web/controllers/user_auth_test.exs", fn file ->
+          assert file =~ ~r/use MyAppWeb\.ConnCase$/m
+        end
+
+        assert_file "test/my_app_web/controllers/user_confirmation_controller_test.exs", fn file ->
+          assert file =~ ~r/use MyAppWeb\.ConnCase$/m
+        end
+
+        assert_file "test/my_app_web/controllers/user_registration_controller_test.exs", fn file ->
+          assert file =~ ~r/use MyAppWeb\.ConnCase$/m
+        end
+
+        assert_file "test/my_app_web/controllers/user_reset_password_controller_test.exs", fn file ->
+          assert file =~ ~r/use MyAppWeb\.ConnCase$/m
+        end
+
+        assert_file "test/my_app_web/controllers/user_session_controller_test.exs", fn file ->
+          assert file =~ ~r/use MyAppWeb\.ConnCase$/m
+        end
+
+        assert_file "test/my_app_web/controllers/user_settings_controller_test.exs", fn file ->
+          assert file =~ ~r/use MyAppWeb\.ConnCase$/m
+        end
+      end)
+    end
+
+    test "when the database is mssql", config do
+      in_tmp_phx_project(config.test, fn ->
+        Gen.Auth.run(
+          ~w(Accounts User users),
+          [ecto_adapter: Ecto.Adapters.TDS, validate_dependencies?: false]
+        )
+
+        assert [migration] = Path.wildcard("priv/repo/migrations/*_create_users_auth_tables.exs")
+        assert_file migration, fn file ->
+          refute file =~ ~r/execute "CREATE EXTENSION IF NOT EXISTS citext", ""$/m
+          assert file =~ ~r/add :email, :string, null: false, size: 160$/m
+        end
+
+        assert_file "test/my_app_web/controllers/user_auth_test.exs", fn file ->
+          assert file =~ ~r/use MyAppWeb\.ConnCase$/m
+        end
+
+        assert_file "test/my_app_web/controllers/user_confirmation_controller_test.exs", fn file ->
+          assert file =~ ~r/use MyAppWeb\.ConnCase$/m
+        end
+
+        assert_file "test/my_app_web/controllers/user_registration_controller_test.exs", fn file ->
+          assert file =~ ~r/use MyAppWeb\.ConnCase$/m
+        end
+
+        assert_file "test/my_app_web/controllers/user_reset_password_controller_test.exs", fn file ->
+          assert file =~ ~r/use MyAppWeb\.ConnCase$/m
+        end
+
+        assert_file "test/my_app_web/controllers/user_session_controller_test.exs", fn file ->
+          assert file =~ ~r/use MyAppWeb\.ConnCase$/m
+        end
+
+        assert_file "test/my_app_web/controllers/user_settings_controller_test.exs", fn file ->
+          assert file =~ ~r/use MyAppWeb\.ConnCase$/m
+        end
+      end)
+    end
+  end
+
+  test "supports --binary_id option", config do
+    in_tmp_phx_project(config.test, fn ->
+      Gen.Auth.run(
+        ~w(Accounts User users --binary-id),
+        [ecto_adapter: Ecto.Adapters.Postgres, validate_dependencies?: false]
+      )
+
+      assert_file "lib/my_app/accounts/user.ex", fn file ->
+        assert file =~ "@primary_key {:id, :binary_id, autogenerate: true}"
+        assert file =~ "@foreign_key_type :binary_id"
+      end
+
+      assert_file "lib/my_app/accounts/user_token.ex", fn file ->
+        assert file =~ "@primary_key {:id, :binary_id, autogenerate: true}"
+        assert file =~ "@foreign_key_type :binary_id"
+      end
+
+      assert [migration] = Path.wildcard("priv/repo/migrations/*_create_users_auth_tables.exs")
+      assert_file migration, fn file ->
+        assert file =~ "create table(:users, primary_key: false)"
+        assert file =~ "create table(:users_tokens, primary_key: false)"
+        assert file =~ "add :id, :binary_id, primary_key: true"
+      end
+    end)
+  end
+
+  describe "--hashing-lib option" do
+    test "when bcrypt", config do
+      in_tmp_phx_project(config.test, fn ->
+        Gen.Auth.run(
+          ~w(Accounts User users --hashing-lib bcrypt),
+          [ecto_adapter: Ecto.Adapters.Postgres, validate_dependencies?: false]
+        )
+
+        assert_file "mix.exs", fn file ->
+          assert file =~ ~s|{:bcrypt_elixir, "~> 2.0"}|
+        end
+
+        assert_file "config/test.exs", fn file ->
+          assert file =~ "config :bcrypt_elixir, :log_rounds, 1"
+        end
+
+        assert_file "lib/my_app/accounts/user.ex", fn file ->
+          assert file =~ "Bcrypt.verify_pass(password, hashed_password)"
+        end
+      end)
+    end
+
+    test "when pbkdf2", config do
+      in_tmp_phx_project(config.test, fn ->
+        Gen.Auth.run(
+          ~w(Accounts User users --hashing-lib pbkdf2),
+          [ecto_adapter: Ecto.Adapters.Postgres, validate_dependencies?: false]
+        )
+
+        assert_file "mix.exs", fn file ->
+          assert file =~ ~s|{:pbkdf2_elixir, "~> 1.0"}|
+        end
+
+        assert_file "config/test.exs", fn file ->
+          assert file =~ "config :pbkdf2_elixir, :rounds, 1"
+        end
+
+        assert_file "lib/my_app/accounts/user.ex", fn file ->
+          assert file =~ "Pbkdf2.verify_pass(password, hashed_password)"
+        end
+      end)
+    end
+
+    test "when argon2", config do
+      in_tmp_phx_project(config.test, fn ->
+        Gen.Auth.run(
+          ~w(Accounts User users --hashing-lib argon2),
+          [ecto_adapter: Ecto.Adapters.Postgres, validate_dependencies?: false]
+        )
+
+        assert_file "mix.exs", fn file ->
+          assert file =~ ~s|{:argon2_elixir, "~> 2.0"}|
+        end
+
+        assert_file "config/test.exs", fn file ->
+          assert file =~ """
+          config :argon2_elixir,
+            t_cost: 1,
+            m_cost: 8
+          """
+        end
+
+        assert_file "lib/my_app/accounts/user.ex", fn file ->
+          assert file =~ "Argon2.verify_pass(password, hashed_password)"
+        end
+      end)
+    end
+  end
+
+  test "with --table option", config do
+    in_tmp_phx_project(config.test, fn ->
+      Gen.Auth.run(
+        ~w(Accounts User users --table my_users),
+        [ecto_adapter: Ecto.Adapters.Postgres, validate_dependencies?: false]
+      )
+
+      assert_file "lib/my_app/accounts/user.ex", fn file ->
+        assert file =~ ~S|schema "my_users" do|
+      end
+
+      assert_file "lib/my_app/accounts/user_token.ex", fn file ->
+        assert file =~ ~S|schema "my_users_tokens" do|
+      end
+
+      assert [migration] = Path.wildcard("priv/repo/migrations/*_create_my_users_auth_tables.exs")
+      assert_file migration, fn file ->
+        assert file =~ "create table(:my_users) do"
+        assert file =~ "create table(:my_users_tokens) do"
+      end
+    end)
+  end
+
+  describe "inside umbrella" do
+    test "without context_app generators config uses web dir", config do
+      in_tmp_phx_umbrella_project(config.test, fn ->
+        in_project(:my_app, "apps/my_app", fn _module ->
+          with_generator_env(:my_app_web, [context_app: nil], fn ->
+            Gen.Auth.run(
+              ~w(Accounts User users),
+              [ecto_adapter: Ecto.Adapters.Postgres, validate_dependencies?: false]
+            )
+          end)
+        end)
+
+        assert_file "apps/my_app/lib/my_app/accounts.ex"
+        assert_file "apps/my_app/lib/my_app/accounts/user.ex"
+        assert_file "apps/my_app/lib/my_app/accounts/user_token.ex"
+        assert_file "apps/my_app/lib/my_app/accounts/user_notifier.ex"
+        assert_file "apps/my_app/test/my_app/accounts_test.exs"
+        assert_file "apps/my_app/test/support/fixtures/accounts_fixtures.ex"
+        assert_file "apps/my_app/lib/my_app_web/controllers/user_auth.ex"
+        assert_file "apps/my_app/test/my_app_web/controllers/user_auth_test.exs"
+        assert_file "apps/my_app/lib/my_app_web/views/user_confirmation_view.ex"
+        assert_file "apps/my_app/lib/my_app_web/templates/user_confirmation/new.html.eex"
+        assert_file "apps/my_app/lib/my_app_web/controllers/user_confirmation_controller.ex"
+        assert_file "apps/my_app/test/my_app_web/controllers/user_confirmation_controller_test.exs"
+        assert_file "apps/my_app/lib/my_app_web/templates/layout/_user_menu.html.eex"
+        assert_file "apps/my_app/lib/my_app_web/controllers/user_registration_controller.ex"
+        assert_file "apps/my_app/lib/my_app_web/views/user_registration_view.ex"
+        assert_file "apps/my_app/test/my_app_web/controllers/user_registration_controller_test.exs"
+        assert_file "apps/my_app/lib/my_app_web/controllers/user_reset_password_controller.ex"
+        assert_file "apps/my_app/lib/my_app_web/templates/user_reset_password/edit.html.eex"
+        assert_file "apps/my_app/lib/my_app_web/templates/user_reset_password/new.html.eex"
+        assert_file "apps/my_app/lib/my_app_web/views/user_reset_password_view.ex"
+        assert_file "apps/my_app/test/my_app_web/controllers/user_reset_password_controller_test.exs"
+        assert_file "apps/my_app/lib/my_app_web/controllers/user_session_controller.ex"
+        assert_file "apps/my_app/lib/my_app_web/templates/user_session/new.html.eex"
+        assert_file "apps/my_app/test/my_app_web/controllers/user_session_controller_test.exs"
+        assert_file "apps/my_app/lib/my_app_web/views/user_session_view.ex"
+        assert_file "apps/my_app/lib/my_app_web/controllers/user_settings_controller.ex"
+        assert_file "apps/my_app/lib/my_app_web/templates/user_settings/edit.html.eex"
+        assert_file "apps/my_app/lib/my_app_web/views/user_settings_view.ex"
+        assert_file "apps/my_app/test/my_app_web/controllers/user_settings_controller_test.exs"
+      end)
+    end
+
+    test "with context_app generators config does not use web dir", config do
+      in_tmp_phx_umbrella_project(config.test, fn ->
+        in_project(:my_app_web, "apps/my_app_web", fn _module ->
+          with_generator_env(:my_app_web, [context_app: :my_app], fn ->
+            Gen.Auth.run(
+              ~w(Accounts User users),
+              [ecto_adapter: Ecto.Adapters.Postgres, validate_dependencies?: false]
+            )
+          end)
+        end)
+
+        assert_file "apps/my_app/lib/my_app/accounts.ex"
+        assert_file "apps/my_app/lib/my_app/accounts/user.ex"
+        assert_file "apps/my_app/lib/my_app/accounts/user_token.ex"
+        assert_file "apps/my_app/lib/my_app/accounts/user_notifier.ex"
+        assert_file "apps/my_app/test/my_app/accounts_test.exs"
+        assert_file "apps/my_app/test/support/fixtures/accounts_fixtures.ex"
+        assert_file "apps/my_app_web/lib/my_app_web/controllers/user_auth.ex"
+        assert_file "apps/my_app_web/test/my_app_web/controllers/user_auth_test.exs"
+        assert_file "apps/my_app_web/lib/my_app_web/views/user_confirmation_view.ex"
+        assert_file "apps/my_app_web/lib/my_app_web/templates/user_confirmation/new.html.eex"
+        assert_file "apps/my_app_web/lib/my_app_web/controllers/user_confirmation_controller.ex"
+        assert_file "apps/my_app_web/test/my_app_web/controllers/user_confirmation_controller_test.exs"
+        assert_file "apps/my_app_web/lib/my_app_web/templates/layout/_user_menu.html.eex"
+        assert_file "apps/my_app_web/lib/my_app_web/controllers/user_registration_controller.ex"
+        assert_file "apps/my_app_web/lib/my_app_web/views/user_registration_view.ex"
+        assert_file "apps/my_app_web/test/my_app_web/controllers/user_registration_controller_test.exs"
+        assert_file "apps/my_app_web/lib/my_app_web/controllers/user_reset_password_controller.ex"
+        assert_file "apps/my_app_web/lib/my_app_web/templates/user_reset_password/edit.html.eex"
+        assert_file "apps/my_app_web/lib/my_app_web/templates/user_reset_password/new.html.eex"
+        assert_file "apps/my_app_web/lib/my_app_web/views/user_reset_password_view.ex"
+        assert_file "apps/my_app_web/test/my_app_web/controllers/user_reset_password_controller_test.exs"
+        assert_file "apps/my_app_web/lib/my_app_web/controllers/user_session_controller.ex"
+        assert_file "apps/my_app_web/lib/my_app_web/templates/user_session/new.html.eex"
+        assert_file "apps/my_app_web/test/my_app_web/controllers/user_session_controller_test.exs"
+        assert_file "apps/my_app_web/lib/my_app_web/views/user_session_view.ex"
+        assert_file "apps/my_app_web/lib/my_app_web/controllers/user_settings_controller.ex"
+        assert_file "apps/my_app_web/lib/my_app_web/templates/user_settings/edit.html.eex"
+        assert_file "apps/my_app_web/lib/my_app_web/views/user_settings_view.ex"
+        assert_file "apps/my_app_web/test/my_app_web/controllers/user_settings_controller_test.exs"
+      end)
+    end
+
+    test "raises with false context_app", config do
+      in_tmp_phx_umbrella_project config.test, fn ->
+        in_project(:my_app_web, "apps/my_app_web", fn _module ->
+          with_generator_env(:my_app_web, [context_app: :false], fn ->
+            assert_raise Mix.Error, ~r/no context_app configured/, fn ->
+              Gen.Auth.run(
+                ~w(Accounts User users),
+                [ecto_adapter: Ecto.Adapters.Postgres, validate_dependencies?: false]
+              )
+              end
+          end)
+        end)
+      end
+    end
+  end
+
+  describe "user prompts" do
+    test "when unable to inject dependencies in mix.exs", config do
+      in_tmp_phx_project(config.test, fn ->
+        File.write!("mix.exs", "")
+
+        Gen.Auth.run(
+          ~w(Accounts User users),
+          [ecto_adapter: Ecto.Adapters.Postgres, validate_dependencies?: false]
+        )
+
+        assert_received {:mix_shell, :info, ["""
+
+        Add your {:bcrypt_elixir, "~> 2.0"} dependency to mix.exs:
+
+            defp deps do
+              [
+                {:bcrypt_elixir, "~> 2.0"},
+                ...
+              ]
+            end
+        """]}
+      end)
+    end
+
+    test "when unable to inject authentication import into router.ex", config do
+      in_tmp_phx_project(config.test, fn ->
+        modify_file("lib/my_app_web/router.ex", fn file ->
+          String.replace(file, "use MyAppWeb, :router", "")
+        end)
+
+        Gen.Auth.run(
+          ~w(Accounts User users),
+          [ecto_adapter: Ecto.Adapters.Postgres, validate_dependencies?: false]
+        )
+
+        assert_received {:mix_shell, :info, ["""
+
+        Add your MyAppWeb.UserAuth import to lib/my_app_web/router.ex:
+
+            defmodule MyAppWeb.Router do
+              use MyAppWeb, :router
+
+              # Import authentication plugs
+              import MyAppWeb.UserAuth
+
+              ...
+            end
+
+        """]}
+      end)
+    end
+
+    test "when unable to inject plugs into router.ex", config do
+      in_tmp_phx_project(config.test, fn ->
+        modify_file("lib/my_app_web/router.ex", fn file ->
+          String.replace(file, "plug :put_secure_browser_headers\n", "")
+        end)
+
+        Gen.Auth.run(
+          ~w(Accounts User users),
+          [ecto_adapter: Ecto.Adapters.Postgres, validate_dependencies?: false]
+        )
+
+        assert_received {:mix_shell, :info, ["""
+
+        Add the :fetch_current_user plug to the :browser pipeline in lib/my_app_web/router.ex:
+
+            pipeline :browser do
+              ...
+              plug :put_secure_browser_headers
+              plug :fetch_current_user
+            end
+
+        """]}
+      end)
+    end
+
+    test "when layout file is not found", config do
+      in_tmp_phx_project(config.test, fn ->
+        File.rm!("lib/my_app_web/templates/layout/app.html.eex")
+
+        Gen.Auth.run(
+          ~w(Accounts User users),
+          [ecto_adapter: Ecto.Adapters.Postgres, validate_dependencies?: false]
+        )
+
+        assert_received {:mix_shell, :error, ["""
+
+        Unable to find an application layout file to inject a render
+        call for "_user_menu.html".
+
+        Missing files:
+
+          * lib/my_app_web/templates/layout/root.html.leex
+          * lib/my_app_web/templates/layout/app.html.eex
+
+        Please ensure this phoenix app was not generated with
+        --no-html. If you have changed the name of your application
+        layout file, please add the following code to it where you'd
+        like "_user_menu.html" to be rendered.
+
+            <%= render "_user_menu.html", assigns %>
+        """]}
+      end)
+    end
+
+    test "when user menu can't be injected into layout", config do
+      in_tmp_phx_project(config.test, fn ->
+        modify_file("lib/my_app_web/templates/layout/app.html.eex", fn _file ->
+          ""
+        end)
+
+        Gen.Auth.run(
+          ~w(Accounts User users),
+          [ecto_adapter: Ecto.Adapters.Postgres, validate_dependencies?: false]
+        )
+
+        assert_received {:mix_shell, :info, ["""
+
+        Add a render call for "_user_menu.html" to lib/my_app_web/templates/layout/app.html.eex:
+
+          <nav role="navigation">
+            <%= render "_user_menu.html", assigns %>
+          </nav>
+
+        """]}
+      end)
+    end
+  end
+
+  test "allows templates to be overridden", config do
+    in_tmp_phx_project(config.test, fn ->
+      File.mkdir_p!("priv/templates/phx.gen.auth")
+      File.write!("priv/templates/phx.gen.auth/_menu.html.eex", """
+      <ul>
+        <%%= if @current_<%= schema.singular %> do %>
+          You're logged in
+        <%% end %>
+      </ul>
+      """)
+
+      Gen.Auth.run(
+        ~w(Accounts Admin admins),
+        [ecto_adapter: Ecto.Adapters.Postgres, validate_dependencies?: false]
+      )
+
+      assert_file "lib/my_app_web/templates/layout/_admin_menu.html.eex", fn file ->
+        assert file =~ ~S|<%= if @current_admin do %>|
+            assert file =~ ~S|You're logged in|
+          end
+      end)
+  end
+
 end