mirror of
https://github.com/team-alembic/ash_authentication_phoenix.git
synced 2024-09-19 21:13:52 +12:00
test: dynamic router - some additional tests, fixes and failures (#493)
* test: Scoped router tests * test: Initial controller tests * docs: Router helper docs, including :unscoped * chore: Code formatting * fix: ensure path params are processed on strategy router * test: Working controller tests for register, sign-in, sign-out --------- Co-authored-by: Zach Daniel <zach@zachdaniel.dev>
This commit is contained in:
parent
0618356731
commit
7c90908ad9
10 changed files with 244 additions and 27 deletions
|
@ -6,10 +6,7 @@ config :ash, :disable_async?, true
|
||||||
|
|
||||||
config :ash_authentication_phoenix, ash_domains: [Example.Accounts]
|
config :ash_authentication_phoenix, ash_domains: [Example.Accounts]
|
||||||
|
|
||||||
config :ash_authentication_phoenix, AshAuthentication.JsonWebToken,
|
config :ash_authentication_phoenix,
|
||||||
signing_secret: "All I wanna do is to thank you, even though I don't know who you are."
|
|
||||||
|
|
||||||
config :ash_authentication, AshAuthentication.Jwt,
|
|
||||||
signing_secret: "Marty McFly in the past with the Delorean"
|
signing_secret: "Marty McFly in the past with the Delorean"
|
||||||
|
|
||||||
config :phoenix, :json_library, Jason
|
config :phoenix, :json_library, Jason
|
||||||
|
|
|
@ -181,17 +181,20 @@ defmodule AshAuthentication.Phoenix.Router do
|
||||||
|
|
||||||
Available options are:
|
Available options are:
|
||||||
|
|
||||||
* `path` the path under which to mount the sign-in live-view. Defaults to `"/sign-in"`.
|
* `path` the path under which to mount the sign-in live-view. Defaults to `"/sign-in"` within the current router scope.
|
||||||
* `auth_routes_prefix` if set, this will be used instead of route helpers when determining routes.
|
* `auth_routes_prefix` if set, this will be used instead of route helpers when determining routes.
|
||||||
Allows disabling `helpers: true`.
|
Allows disabling `helpers: true`.
|
||||||
|
If a tuple {:unscoped, path} is provided, the path prefix will not inherit the current route scope.
|
||||||
* `register_path` - the path under which to mount the password strategy's registration live-view.
|
* `register_path` - the path under which to mount the password strategy's registration live-view.
|
||||||
If not set, and registration is supported, registration will use a dynamic toggle and will not be routeable to.
|
If not set, and registration is supported, registration will use a dynamic toggle and will not be routeable to.
|
||||||
|
If a tuple {:unscoped, path} is provided, the registration path will not inherit the current route scope.
|
||||||
* `reset_path` - the path under which to mount the password strategy's password reset live-view.
|
* `reset_path` - the path under which to mount the password strategy's password reset live-view.
|
||||||
If not set, and password reset is supported, password reset will use a dynamic toggle and will not be routeable to.
|
If not set, and password reset is supported, password reset will use a dynamic toggle and will not be routeable to.
|
||||||
|
If a tuple {:unscoped, path} is provided, the reset path will not inherit the current route scope.
|
||||||
* `live_view` the name of the live view to render. Defaults to
|
* `live_view` the name of the live view to render. Defaults to
|
||||||
`AshAuthentication.Phoenix.SignInLive`.
|
`AshAuthentication.Phoenix.SignInLive`.
|
||||||
* `auth_routes_prefix` the prefix to use for the auth routes. Defaults to `"/auth"`.
|
* `auth_routes_prefix` the prefix to use for the auth routes. Defaults to `"/auth"`.
|
||||||
* `as` which is passed to the generated `live` route. Defaults to `:auth`.
|
* `as` which is used to prefix the generated `live_session` and `live` route name. Defaults to `:auth`.
|
||||||
* `otp_app` the otp app or apps to find authentication resources in. Pulls from the socket by default.
|
* `otp_app` the otp app or apps to find authentication resources in. Pulls from the socket by default.
|
||||||
* `overrides` specify any override modules for customisation. See
|
* `overrides` specify any override modules for customisation. See
|
||||||
`AshAuthentication.Phoenix.Overrides` for more information.
|
`AshAuthentication.Phoenix.Overrides` for more information.
|
||||||
|
@ -240,6 +243,8 @@ defmodule AshAuthentication.Phoenix.Router do
|
||||||
mod -> mod
|
mod -> mod
|
||||||
end)
|
end)
|
||||||
|
|
||||||
|
sign_in_path = Phoenix.Router.scoped_path(__MODULE__, unquote(path))
|
||||||
|
|
||||||
register_path =
|
register_path =
|
||||||
case unquote(register_path) do
|
case unquote(register_path) do
|
||||||
nil -> nil
|
nil -> nil
|
||||||
|
@ -254,8 +259,12 @@ defmodule AshAuthentication.Phoenix.Router do
|
||||||
value -> Phoenix.Router.scoped_path(__MODULE__, value)
|
value -> Phoenix.Router.scoped_path(__MODULE__, value)
|
||||||
end
|
end
|
||||||
|
|
||||||
unquote(register_path) &&
|
auth_routes_prefix =
|
||||||
Phoenix.Router.scoped_path(__MODULE__, unquote(register_path))
|
case unquote(auth_routes_prefix) do
|
||||||
|
nil -> nil
|
||||||
|
{:unscoped, value} -> value
|
||||||
|
value -> Phoenix.Router.scoped_path(__MODULE__, value)
|
||||||
|
end
|
||||||
|
|
||||||
live_session_opts = [
|
live_session_opts = [
|
||||||
session:
|
session:
|
||||||
|
@ -263,15 +272,11 @@ defmodule AshAuthentication.Phoenix.Router do
|
||||||
[
|
[
|
||||||
%{
|
%{
|
||||||
"overrides" => unquote(overrides),
|
"overrides" => unquote(overrides),
|
||||||
"auth_routes_prefix" => unquote(auth_routes_prefix),
|
"auth_routes_prefix" => auth_routes_prefix,
|
||||||
"otp_app" => unquote(otp_app),
|
"otp_app" => unquote(otp_app),
|
||||||
"path" => Phoenix.Router.scoped_path(__MODULE__, unquote(path)),
|
"path" => sign_in_path,
|
||||||
"reset_path" =>
|
"reset_path" => reset_path,
|
||||||
unquote(reset_path) &&
|
"register_path" => register_path
|
||||||
Phoenix.Router.scoped_path(__MODULE__, unquote(reset_path)),
|
|
||||||
"register_path" =>
|
|
||||||
unquote(register_path) &&
|
|
||||||
Phoenix.Router.scoped_path(__MODULE__, unquote(register_path))
|
|
||||||
}
|
}
|
||||||
]},
|
]},
|
||||||
on_mount: on_mount
|
on_mount: on_mount
|
||||||
|
@ -286,17 +291,15 @@ defmodule AshAuthentication.Phoenix.Router do
|
||||||
Keyword.put(live_session_opts, :layout, layout)
|
Keyword.put(live_session_opts, :layout, layout)
|
||||||
end
|
end
|
||||||
|
|
||||||
live_session :sign_in, live_session_opts do
|
live_session :"#{unquote(as)}_sign_in", live_session_opts do
|
||||||
live(unquote(path), unquote(live_view), :sign_in, as: unquote(as))
|
live(unquote(path), unquote(live_view), :sign_in, as: unquote(as))
|
||||||
|
|
||||||
if unquote(reset_path) do
|
if reset_path do
|
||||||
live(unquote(reset_path), unquote(live_view), :reset, as: :"#{unquote(as)}_reset")
|
live(reset_path, unquote(live_view), :reset, as: :"#{unquote(as)}_reset")
|
||||||
end
|
end
|
||||||
|
|
||||||
if unquote(register_path) do
|
if register_path do
|
||||||
live(unquote(register_path), unquote(live_view), :register,
|
live(register_path, unquote(live_view), :register, as: :"#{unquote(as)}_register")
|
||||||
as: :"#{unquote(as)}_register"
|
|
||||||
)
|
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
@ -337,7 +340,8 @@ defmodule AshAuthentication.Phoenix.Router do
|
||||||
`AshAuthentication.Phoenix.Overrides` for more information. all other
|
`AshAuthentication.Phoenix.Overrides` for more information. all other
|
||||||
options are passed to the generated `scope`.
|
options are passed to the generated `scope`.
|
||||||
|
|
||||||
This is completely optional.
|
This is completely optional, in particular, if the `reset_path` option is passed to the
|
||||||
|
`sign_in_route` helper, using the `reset_route` helper is redundant.
|
||||||
"""
|
"""
|
||||||
@spec reset_route(
|
@spec reset_route(
|
||||||
opts :: [
|
opts :: [
|
||||||
|
@ -394,7 +398,7 @@ defmodule AshAuthentication.Phoenix.Router do
|
||||||
Keyword.put(live_session_opts, :layout, layout)
|
Keyword.put(live_session_opts, :layout, layout)
|
||||||
end
|
end
|
||||||
|
|
||||||
live_session :reset, live_session_opts do
|
live_session :"#{unquote(as)}_reset", live_session_opts do
|
||||||
live("/:token", unquote(live_view), :reset, as: unquote(as))
|
live("/:token", unquote(live_view), :reset, as: unquote(as))
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
|
@ -7,6 +7,9 @@ defmodule AshAuthentication.Phoenix.StrategyRouter do
|
||||||
|
|
||||||
@impl true
|
@impl true
|
||||||
def call(conn, opts) do
|
def call(conn, opts) do
|
||||||
|
# ensure query params have been fetched
|
||||||
|
conn = Plug.Conn.fetch_query_params(conn)
|
||||||
|
|
||||||
opts
|
opts
|
||||||
|> routes()
|
|> routes()
|
||||||
|> Enum.reduce_while(
|
|> Enum.reduce_while(
|
||||||
|
|
113
test/controller_test.exs
Normal file
113
test/controller_test.exs
Normal file
|
@ -0,0 +1,113 @@
|
||||||
|
defmodule AshAuthentication.Phoenix.ControllerTest do
|
||||||
|
@moduledoc false
|
||||||
|
use AshAuthentication.Phoenix.Test.ConnCase
|
||||||
|
|
||||||
|
describe "AshAuthentication Controller" do
|
||||||
|
test "sign-in renders", %{conn: conn} do
|
||||||
|
assert_mount_render(conn, ~p"/sign-in", "Sign in")
|
||||||
|
end
|
||||||
|
|
||||||
|
test "register renders", %{conn: conn} do
|
||||||
|
assert_mount_render(conn, ~p"/register", "Register")
|
||||||
|
end
|
||||||
|
|
||||||
|
test "reset renders", %{conn: conn} do
|
||||||
|
assert_mount_render(conn, ~p"/reset", "Request magic link")
|
||||||
|
end
|
||||||
|
|
||||||
|
test "sign-out renders", %{conn: conn} do
|
||||||
|
conn = get(conn, ~p"/sign-out")
|
||||||
|
assert html_response(conn, 200) =~ "Signed out"
|
||||||
|
assert {:error, :nosession} = live(conn)
|
||||||
|
end
|
||||||
|
|
||||||
|
defp assert_mount_render(conn, path, response) do
|
||||||
|
conn = get(conn, path)
|
||||||
|
|
||||||
|
# assert unconnected mount renders
|
||||||
|
assert html_response(conn, 200) =~ response
|
||||||
|
|
||||||
|
# assert connected mount also renders
|
||||||
|
assert {:ok, _view, html} = live(conn)
|
||||||
|
assert html =~ response
|
||||||
|
conn
|
||||||
|
end
|
||||||
|
|
||||||
|
test "register user with password", %{conn: conn} do
|
||||||
|
strategy = AshAuthentication.Info.strategy!(Example.Accounts.User, :password)
|
||||||
|
email = "register@email"
|
||||||
|
password = "register.secret"
|
||||||
|
|
||||||
|
{:ok, lv, _html} = live(conn, ~p"/register")
|
||||||
|
|
||||||
|
{:ok, conn} =
|
||||||
|
lv
|
||||||
|
|> form(~s{[action="/auth/user/password/register?"]},
|
||||||
|
user: %{
|
||||||
|
strategy.identity_field => email,
|
||||||
|
strategy.password_field => password,
|
||||||
|
strategy.password_confirmation_field => password
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|> render_submit()
|
||||||
|
|> follow_redirect(conn)
|
||||||
|
|
||||||
|
assert html_response(conn, 200) =~ "Success"
|
||||||
|
assert Ash.CiString.value(conn.assigns.current_user.email) == email
|
||||||
|
end
|
||||||
|
|
||||||
|
test "sign-in user with password", %{conn: conn} do
|
||||||
|
strategy = AshAuthentication.Info.strategy!(Example.Accounts.User, :password)
|
||||||
|
email = "sign.in@email"
|
||||||
|
password = "sign.in.secret"
|
||||||
|
create_user!(strategy, email, password)
|
||||||
|
conn = sign_in_user(conn, strategy, email, password)
|
||||||
|
|
||||||
|
assert html_response(conn, 200) =~ "Success"
|
||||||
|
assert get_session(conn, :user) != nil
|
||||||
|
assert Ash.CiString.value(conn.assigns.current_user.email) == email
|
||||||
|
end
|
||||||
|
|
||||||
|
test "sign-out user", %{conn: conn} do
|
||||||
|
strategy = AshAuthentication.Info.strategy!(Example.Accounts.User, :password)
|
||||||
|
email = "sign.out@email"
|
||||||
|
password = "sign.out.secret"
|
||||||
|
create_user!(strategy, email, password)
|
||||||
|
|
||||||
|
conn =
|
||||||
|
conn
|
||||||
|
|> sign_in_user(strategy, email, password)
|
||||||
|
|> get(~p"/sign-out")
|
||||||
|
|
||||||
|
assert html_response(conn, 200) =~ "Signed out"
|
||||||
|
assert get_session(conn, :user) == nil
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
defp sign_in_user(conn, strategy, email, password) do
|
||||||
|
{:ok, lv, _html} = live(conn, ~p"/sign-in")
|
||||||
|
|
||||||
|
{:ok, conn} =
|
||||||
|
lv
|
||||||
|
|> form(~s{[action="/auth/user/password/sign_in?"]},
|
||||||
|
user: %{
|
||||||
|
strategy.identity_field => email,
|
||||||
|
strategy.password_field => password
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|> render_submit()
|
||||||
|
|> follow_redirect(conn)
|
||||||
|
|
||||||
|
conn
|
||||||
|
end
|
||||||
|
|
||||||
|
defp create_user!(strategy, email, password) do
|
||||||
|
Example.Accounts.User
|
||||||
|
|> Ash.Changeset.for_create(:register_with_password, %{
|
||||||
|
strategy.identity_field => email,
|
||||||
|
strategy.password_field => password,
|
||||||
|
strategy.password_confirmation_field => password
|
||||||
|
})
|
||||||
|
|> Ash.create!()
|
||||||
|
end
|
||||||
|
end
|
|
@ -23,4 +23,48 @@ defmodule AshAuthentication.Phoenix.RouterTest do
|
||||||
}
|
}
|
||||||
]}
|
]}
|
||||||
end
|
end
|
||||||
|
|
||||||
|
test "sign_in_routes respects the inherited router scope" do
|
||||||
|
route =
|
||||||
|
AshAuthentication.Phoenix.Test.Router
|
||||||
|
|> Phoenix.Router.routes()
|
||||||
|
|> Enum.find(&(&1.path == "/nested/sign-in"))
|
||||||
|
|
||||||
|
{_, _, _, %{extra: %{session: session}}} = route.metadata.phoenix_live_view
|
||||||
|
|
||||||
|
assert session ==
|
||||||
|
{AshAuthentication.Phoenix.Router, :generate_session,
|
||||||
|
[
|
||||||
|
%{
|
||||||
|
"auth_routes_prefix" => "/nested/auth",
|
||||||
|
"otp_app" => nil,
|
||||||
|
"overrides" => [AshAuthentication.Phoenix.Overrides.Default],
|
||||||
|
"path" => "/nested/sign-in",
|
||||||
|
"register_path" => "/nested/register",
|
||||||
|
"reset_path" => "/nested/reset"
|
||||||
|
}
|
||||||
|
]}
|
||||||
|
end
|
||||||
|
|
||||||
|
test "sign_in_routes respects unscoped" do
|
||||||
|
route =
|
||||||
|
AshAuthentication.Phoenix.Test.Router
|
||||||
|
|> Phoenix.Router.routes()
|
||||||
|
|> Enum.find(&(&1.path == "/unscoped/sign-in"))
|
||||||
|
|
||||||
|
{_, _, _, %{extra: %{session: session}}} = route.metadata.phoenix_live_view
|
||||||
|
|
||||||
|
assert session ==
|
||||||
|
{AshAuthentication.Phoenix.Router, :generate_session,
|
||||||
|
[
|
||||||
|
%{
|
||||||
|
"auth_routes_prefix" => "/auth",
|
||||||
|
"otp_app" => nil,
|
||||||
|
"overrides" => [AshAuthentication.Phoenix.Overrides.Default],
|
||||||
|
"path" => "/unscoped/sign-in",
|
||||||
|
"register_path" => "/register",
|
||||||
|
"reset_path" => "/reset"
|
||||||
|
}
|
||||||
|
]}
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
|
@ -1,5 +1,6 @@
|
||||||
defmodule AshAuthentication.Phoenix.SignInTest do
|
defmodule AshAuthentication.Phoenix.SignInTest do
|
||||||
@moduledoc false
|
@moduledoc false
|
||||||
|
|
||||||
use ExUnit.Case, async: false
|
use ExUnit.Case, async: false
|
||||||
import Phoenix.ConnTest
|
import Phoenix.ConnTest
|
||||||
import Phoenix.LiveViewTest
|
import Phoenix.LiveViewTest
|
||||||
|
|
|
@ -11,7 +11,7 @@ defmodule AshAuthentication.Phoenix.Test.AuthController do
|
||||||
|> store_in_session(user)
|
|> store_in_session(user)
|
||||||
|> assign(:current_user, user)
|
|> assign(:current_user, user)
|
||||||
|> put_status(200)
|
|> put_status(200)
|
||||||
|> render("success.html")
|
|> render(:success)
|
||||||
end
|
end
|
||||||
|
|
||||||
@doc false
|
@doc false
|
||||||
|
@ -27,6 +27,6 @@ defmodule AshAuthentication.Phoenix.Test.AuthController do
|
||||||
def sign_out(conn, _params) do
|
def sign_out(conn, _params) do
|
||||||
conn
|
conn
|
||||||
|> clear_session()
|
|> clear_session()
|
||||||
|> render("sign_out.html")
|
|> render(:signed_out)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
6
test/support/auth_view.ex
Normal file
6
test/support/auth_view.ex
Normal file
|
@ -0,0 +1,6 @@
|
||||||
|
defmodule AshAuthentication.Phoenix.Test.AuthView do
|
||||||
|
use Phoenix.Component
|
||||||
|
|
||||||
|
def success(assigns), do: ~H"<p>Success</p>"
|
||||||
|
def signed_out(assigns), do: ~H"<p>Signed out</p>"
|
||||||
|
end
|
25
test/support/conn_case.ex
Normal file
25
test/support/conn_case.ex
Normal file
|
@ -0,0 +1,25 @@
|
||||||
|
defmodule AshAuthentication.Phoenix.Test.ConnCase do
|
||||||
|
use ExUnit.CaseTemplate
|
||||||
|
|
||||||
|
using do
|
||||||
|
quote do
|
||||||
|
# The default endpoint for testing
|
||||||
|
@endpoint AshAuthentication.Phoenix.Test.Endpoint
|
||||||
|
|
||||||
|
use Phoenix.VerifiedRoutes,
|
||||||
|
endpoint: @endpoint,
|
||||||
|
router: AshAuthentication.Phoenix.Test.Router
|
||||||
|
#statics: ~w(assets fonts images favicon.ico robots.txt)
|
||||||
|
|
||||||
|
# Import conveniences for testing with connections
|
||||||
|
import Plug.Conn
|
||||||
|
import Phoenix.ConnTest
|
||||||
|
import Phoenix.LiveViewTest
|
||||||
|
import AshAuthentication.Phoenix.Test.ConnCase
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
setup _tags do
|
||||||
|
{:ok, conn: Phoenix.ConnTest.build_conn()}
|
||||||
|
end
|
||||||
|
end
|
|
@ -81,6 +81,30 @@ defmodule AshAuthentication.Phoenix.Test.Router do
|
||||||
auth_routes AuthController, Example.Accounts.User, path: "/auth"
|
auth_routes AuthController, Example.Accounts.User, path: "/auth"
|
||||||
end
|
end
|
||||||
|
|
||||||
|
scope "/nested", AshAuthentication.Phoenix.Test do
|
||||||
|
pipe_through :browser
|
||||||
|
|
||||||
|
sign_in_route register_path: "/register",
|
||||||
|
reset_path: "/reset",
|
||||||
|
auth_routes_prefix: "/auth",
|
||||||
|
as: :nested
|
||||||
|
|
||||||
|
sign_out_route AuthController
|
||||||
|
reset_route as: :nested
|
||||||
|
end
|
||||||
|
|
||||||
|
scope "/unscoped", AshAuthentication.Phoenix.Test do
|
||||||
|
pipe_through :browser
|
||||||
|
|
||||||
|
sign_in_route register_path: {:unscoped, "/register"},
|
||||||
|
reset_path: {:unscoped, "/reset"},
|
||||||
|
auth_routes_prefix: {:unscoped, "/auth"},
|
||||||
|
as: :unscoped
|
||||||
|
|
||||||
|
sign_out_route AuthController
|
||||||
|
reset_route as: :unscoped
|
||||||
|
end
|
||||||
|
|
||||||
scope "/", AshAuthentication.Phoenix.Test do
|
scope "/", AshAuthentication.Phoenix.Test do
|
||||||
pipe_through(:browser)
|
pipe_through(:browser)
|
||||||
|
|
||||||
|
|
Loading…
Reference in a new issue