defmodule <%= inspect @web_pascal_case %>.AuthenticationLiveTest do @moduledoc false use <%= inspect @web_pascal_case %>.ConnCase, async: true import Phoenix.LiveViewTest import ExUnit.CaptureLog alias <%= inspect @app_pascal_case %>.Identity alias <%= inspect @app_pascal_case %>.Identity.User alias <%= inspect @app_pascal_case %>.IdentityFixtures alias <%= inspect @app_pascal_case %>.Repo defp route, do: ~p"/sign-in" describe "mount & render" do test "includes expected elements", %{conn: conn} do assert {:ok, view, _html} = live(conn, route()) # SupportComponent assert has_element?(view, "[phx-hook='SupportHook'].hidden") # Passkey Form assert has_element?(view, "form[phx-change='update-form'][phx-submit]") # Registration assert has_element?(view, "form fieldset#fieldset-authentication") assert has_element?(view, "form input[type='email'][name='email']") assert has_element?(view, "[phx-hook='RegistrationHook'][phx-click='register']") # Authentication assert has_element?(view, "form fieldset#fieldset-registration") assert has_element?(view, "[phx-hook='AuthenticationHook'][phx-click='authenticate']") # Token/Session Form # This form is only rendered when registration or authentication is successful. refute has_element?(view, "form#token-form[action='/session'][method='post'].hidden") end end describe "handle_event: update-form" do test "results in updated form", %{conn: conn} do {:ok, view, _html} = live(conn, route()) attrs = %{email: IdentityFixtures.unique_email()} assert view |> element("form[phx-change='update-form']") |> render_change(attrs) assert has_element?(view, "input[type='email'][name='email'][value='#{attrs.email}']") end end describe "handle_info: passkeys_supported" do test "renders flash when not supported", %{conn: conn} do {:ok, view, _html} = live(conn, route()) Process.send(view.pid, {:passkeys_supported, false}, []) assert view |> has_element?("#flash", "Passkeys are not supported in this browser.") end test "does not render flash when supported", %{conn: conn} do {:ok, view, _html} = live(conn, route()) Process.send(view.pid, {:passkeys_supported, true}, []) refute view |> has_element?("#flash", "Passkeys are not supported in this browser.") end end # Since some events are handled internally by the RegistrationComponent, # we need to mock the messages sent from the component to the LiveView. describe "handle_info: registration_successful" do test "results in a new user token", %{conn: conn} do {:ok, view, _html} = live(conn, route()) email = IdentityFixtures.unique_email() assert render_change(view, "update-form", %{email: email}) key = IdentityFixtures.user_key_attrs() msg = {:registration_successful, key: key} send(view.pid, msg) render(view) assert {:ok, %User{} = user} = Identity.get_by_key_id(key.key_id) %User{tokens: [token | _other_tokens]} = Repo.preload(user, [:tokens]) token_value = Base.encode64(token.value, padding: false) token_form_selector = "form#token-form[method='post'][action='/session']" assert has_element?(view, token_form_selector) assert has_element?(view, "form#token-form input[name='value'][value='#{token_value}']") end end describe "handle_info: find_credentials" do test "logs error on invalid attestation", %{conn: conn} do {:ok, view, _html} = live(conn, route()) key = IdentityFixtures.user_key_attrs() user_attrs = %{ email: IdentityFixtures.unique_email(), keys: [key] } {:ok, _user} = Identity.create(user_attrs) assert view |> element("button#authentication-component") |> render_click() render(view) raw_id_64 = Base.encode64(key.key_id, padding: false) attestation = IdentityFixtures.attestation(%{"rawId64" => raw_id_64}) assert view |> element("button#authentication-component") |> render_hook("authentication-attestation", attestation) render(view) refute has_element?(view, "#flash", "Failed to sign in") msg = {:find_credential, key_id: key.key_id} {_result, log} = with_log(fn -> send(view.pid, msg) render(view) end) assert log =~ "authentication_error" end end # TODO # describe "handle_info: authentication_successful" do # end end