Repository: phoenixframework/phoenix
Pull request #6128 (2025-03-20 10:21:42)

add test for new verified routes behavior for mix phx.routes
@zachdaniel I needed to make a little adjustment. While the type for formatted_route defines verb, path and label

```elixir
@type formatted_route :: %{
          required(:verb) => String.t(),
          required(:path) => String.t(),
          required(:label) => String.t()
        }
```

The code expected plug_opts instead and didn't handle the label. I believe this was just an oversight, but please double check.


Patch:
diff --git a/installer/lib/phx_new/single.ex b/installer/lib/phx_new/single.ex
index 9326ad1e1..8a988bdc9 100644
--- a/installer/lib/phx_new/single.ex
+++ b/installer/lib/phx_new/single.ex
@@ -88,7 +88,7 @@ defmodule Phx.New.Single do
       :text,
       :web,
       # the default.css file can be re-created by using the recreate_default_css.exs file
-      # in the project root: `elixir recreate_default_css.exs`
+      # in the installer folder: `elixir installer/recreate_default_css.exs`
       "phx_static/app.css": "priv/static/assets/app.css",
       "phx_static/default.css": "priv/static/assets/default.css"
     }
diff --git a/recreate_default_css.exs b/installer/recreate_default_css.exs
similarity index 100%
rename from recreate_default_css.exs
rename to installer/recreate_default_css.exs
diff --git a/lib/phoenix/router.ex b/lib/phoenix/router.ex
index 4d440fb4c..c1ad97ee0 100644
--- a/lib/phoenix/router.ex
+++ b/lib/phoenix/router.ex
@@ -1308,12 +1308,13 @@ defmodule Phoenix.Router do
         route.plug_opts
         |> route.plug.formatted_routes()
         |> Enum.map(fn nested_route ->
-          %{
+          route = %{
             route
             | path: Path.join(route.path, nested_route.path),
-              verb: nested_route.verb,
-              plug_opts: nested_route.plug_opts
+              verb: nested_route.verb
           }
+
+          Map.put(route, :label, nested_route.label)
         end)
       else
         plug =


Test patch:
diff --git a/test/mix/tasks/phx.routes_test.exs b/test/mix/tasks/phx.routes_test.exs
index e044fc868..19bb90c27 100644
--- a/test/mix/tasks/phx.routes_test.exs
+++ b/test/mix/tasks/phx.routes_test.exs
@@ -24,6 +24,34 @@ defmodule PhoenixTestLiveWeb.Router do
   get "/", PageController, :index, metadata: %{mfa: {PageController.Live, :init, 1}}
 end
 
+defmodule PhoenixTestWeb.ForwardedRouter do
+  use Phoenix.Router
+
+  forward "/", PhoenixTestWeb.PlugRouterWithVerifiedRoutes
+end
+
+defmodule PhoenixTestWeb.PlugRouterWithVerifiedRoutes do
+  use Plug.Router
+
+  @behaviour Phoenix.VerifiedRoutes
+
+  get "/foo" do
+    send_resp(conn, 200, "ok")
+  end
+
+  @impl Phoenix.VerifiedRoutes
+  def formatted_routes(_plug_opts) do
+    [
+      %{verb: "GET", path: "/foo", label: "Hello"}
+    ]
+  end
+
+  @impl Phoenix.VerifiedRoutes
+  def verified_route?(_plug_opts, path) do
+    path == ["foo"]
+  end
+end
+
 defmodule Mix.Tasks.Phx.RoutesTest do
   use ExUnit.Case, async: true
 
@@ -33,6 +61,12 @@ defmodule Mix.Tasks.Phx.RoutesTest do
     assert routes =~ "page_path  GET  /  PageController :index"
   end
 
+  test "format routes for forwarded router that implements verified routes" do
+    Mix.Tasks.Phx.Routes.run(["PhoenixTestWeb.ForwardedRouter", "--no-compile"])
+    assert_received {:mix_shell, :info, [routes]}
+    assert routes =~ "GET  /foo  Hello"
+  end
+
   test "prints error when explicit router cannot be found" do
     assert_raise Mix.Error,
                  "the provided router, Foo.UnknownBar.CantFindBaz, does not exist",
diff --git a/test/phoenix/verified_routes_test.exs b/test/phoenix/verified_routes_test.exs
index 51d3862f3..f41cc8c84 100644
--- a/test/phoenix/verified_routes_test.exs
+++ b/test/phoenix/verified_routes_test.exs
@@ -62,6 +62,34 @@ defmodule Phoenix.VerifiedRoutesTest do
     get "/*path", PostController, :root, warn_on_verify: true
   end
 
+  defmodule ForwardedRouter do
+    use Phoenix.Router
+
+    forward "/", PhoenixTestWeb.PlugRouterWithVerifiedRoutes
+  end
+
+  defmodule PlugRouterWithVerifiedRoutes do
+    use Plug.Router
+
+    @behaviour Phoenix.VerifiedRoutes
+
+    get "/foo" do
+      send_resp(conn, 200, "ok")
+    end
+
+    @impl Phoenix.VerifiedRoutes
+    def formatted_routes(_plug_opts) do
+      [
+        %{verb: "GET", path: "/foo", label: "Hello"}
+      ]
+    end
+
+    @impl Phoenix.VerifiedRoutes
+    def verified_route?(_plug_opts, path) do
+      path == ["foo"]
+    end
+  end
+
   # Emulate regular endpoint functions
 
   defmodule Endpoint do
@@ -608,6 +636,29 @@ defmodule Phoenix.VerifiedRoutesTest do
         :code.purge(__MODULE__.VerifyFalseTrueMatchesFirst)
         :code.delete(__MODULE__.VerifyFalseTrueMatchesFirst)
       end
+
+      test "routers implementing verified routes behavior warn as expected" do
+        warnings =
+          ExUnit.CaptureIO.capture_io(:stderr, fn ->
+            defmodule VerifyForwardedRouter do
+              use Phoenix.VerifiedRoutes,
+                endpoint: unquote(@endpoint),
+                router: PlugRouterWithVerifiedRoutes
+
+              def test, do: ~p"/bar"
+              def test2, do: ~p"/foo"
+            end
+          end)
+
+        assert warnings =~
+                 "no route path for Phoenix.VerifiedRoutesTest.PlugRouterWithVerifiedRoutes matches \"/bar\""
+
+        refute warnings =~
+                 "no route path for Phoenix.VerifiedRoutesTest.PlugRouterWithVerifiedRoutes matches \"/foo\""
+      after
+        :code.purge(__MODULE__.VerifyForwardedRouter)
+        :code.delete(__MODULE__.VerifyForwardedRouter)
+      end
     end
   end
 end