diff --git a/test/towerops_web/live/user_settings_live_test.exs b/test/towerops_web/live/user_settings_live_test.exs new file mode 100644 index 00000000..5d050ff3 --- /dev/null +++ b/test/towerops_web/live/user_settings_live_test.exs @@ -0,0 +1,233 @@ +defmodule ToweropsWeb.UserSettingsLiveTest do + use ToweropsWeb.ConnCase, async: true + + import Phoenix.LiveViewTest + import Towerops.AccountsFixtures + + alias Towerops.Accounts + + describe "UserSettingsLive without sudo mode" do + setup :register_and_log_in_user + + test "redirects to sudo mode verification", %{conn: conn} do + # Without sudo mode, should redirect to sudo verification + {:error, {:redirect, %{to: path}}} = live(conn, ~p"/users/settings") + + assert path == ~p"/users/sudo-verify" + end + end + + describe "UserSettingsLive with sudo mode" do + setup :register_and_log_in_user_with_sudo + + test "renders settings page", %{conn: conn} do + {:ok, _view, html} = live(conn, ~p"/users/settings") + + assert html =~ "Account Settings" + assert html =~ "Personal Information" + end + + test "allows updating profile", %{conn: conn, user: user} do + {:ok, view, _html} = live(conn, ~p"/users/settings") + + # Update profile + result = + view + |> form("#profile-form", user: %{name: "Updated Name"}) + |> render_submit() + + assert result =~ "Profile updated successfully" + + # Verify database was updated + updated_user = Accounts.get_user!(user.id) + assert updated_user.name == "Updated Name" + end + + test "allows updating email", %{conn: conn, user: user} do + new_email = unique_user_email() + {:ok, view, _html} = live(conn, ~p"/users/settings") + + # Update email + result = + view + |> form("#email-form", user: %{email: new_email}) + |> render_submit() + + assert result =~ "A link to confirm your email" + + # Old email should still be in use + assert Accounts.get_user_by_email(user.email) + end + + test "allows updating password", %{conn: conn} do + new_password = valid_user_password() + {:ok, view, _html} = live(conn, ~p"/users/settings") + + # Update password + result = + view + |> form("#password-form", + user: %{ + password: new_password, + password_confirmation: new_password, + current_password: valid_user_password() + } + ) + |> render_submit() + + assert result =~ "Password updated successfully" + end + + test "requires current password for password update", %{conn: conn} do + new_password = valid_user_password() + {:ok, view, _html} = live(conn, ~p"/users/settings") + + # Try to update password without current password + result = + view + |> form("#password-form", + user: %{ + password: new_password, + password_confirmation: new_password, + current_password: "wrong" + } + ) + |> render_submit() + + assert result =~ "is not valid" + end + + test "shows tabs navigation", %{conn: conn} do + {:ok, _view, html} = live(conn, ~p"/users/settings") + + # Check for tab navigation + assert html =~ "Personal" + assert html =~ "Security" + assert html =~ "Organizations" + assert html =~ "API Tokens" + assert html =~ "Sessions" + assert html =~ "Activity" + end + + test "allows switching tabs via params", %{conn: conn} do + {:ok, _view, html} = live(conn, ~p"/users/settings?tab=security") + + assert html =~ "Account Settings" + # Security tab should be active (would need to check for active class in real template) + end + end + + describe "UserSettingsLive TOTP management with sudo mode" do + setup :register_and_log_in_user_with_sudo + + test "shows enrolled TOTP devices", %{conn: conn, user: user} do + # Create a TOTP device + Accounts.create_user_totp_device(user, %{name: "Test Authenticator"}) + + {:ok, _view, html} = live(conn, ~p"/users/settings?tab=security") + + assert html =~ "Test Authenticator" + end + + test "allows removing TOTP devices", %{conn: conn, user: user} do + # Create two TOTP devices (can't remove the last one) + {:ok, device1} = Accounts.create_user_totp_device(user, %{name: "Device 1"}) + {:ok, _device2} = Accounts.create_user_totp_device(user, %{name: "Device 2"}) + + {:ok, view, _html} = live(conn, ~p"/users/settings?tab=security") + + # Remove device 1 + view + |> element("#remove-totp-device-#{device1.id}") + |> render_click() + + # Verify device was removed + assert Accounts.get_user_totp_device(device1.id) == nil + end + end + + describe "UserSettingsLive mobile sessions with sudo mode" do + setup :register_and_log_in_user_with_sudo + + test "shows mobile sessions", %{conn: conn} do + {:ok, _view, html} = live(conn, ~p"/users/settings?tab=sessions") + + assert html =~ "Browser Sessions" + assert html =~ "Mobile Sessions" + end + end + + describe "UserSettingsLive API tokens with sudo mode" do + setup :register_and_log_in_user_with_sudo + + test "shows API tokens tab", %{conn: conn} do + {:ok, _view, html} = live(conn, ~p"/users/settings?tab=api-tokens") + + assert html =~ "API Tokens" + assert html =~ "Create New Token" + end + + test "allows creating API tokens", %{conn: conn, user: user} do + {:ok, view, _html} = live(conn, ~p"/users/settings?tab=api-tokens") + + # Open create token modal + view + |> element("button", "Create New Token") + |> render_click() + + # Submit token creation form + result = + view + |> form("#token-form", token: %{name: "Test Token"}) + |> render_submit() + + assert result =~ "API token created successfully" + + # Verify token was created + tokens = Accounts.list_user_api_tokens(user) + assert Enum.any?(tokens, fn t -> t.name == "Test Token" end) + end + end + + describe "UserSettingsLive organizations with sudo mode" do + setup :register_and_log_in_user_with_sudo + + test "shows user organizations", %{conn: conn} do + {:ok, _view, html} = live(conn, ~p"/users/settings?tab=organizations") + + assert html =~ "Organizations" + end + end + + describe "UserSettingsLive activity with sudo mode" do + setup :register_and_log_in_user_with_sudo + + test "shows login history", %{conn: conn} do + {:ok, _view, html} = live(conn, ~p"/users/settings?tab=activity") + + assert html =~ "Login History" + assert html =~ "Security Alerts" + end + + test "paginates login history", %{conn: conn} do + {:ok, _view, html} = live(conn, ~p"/users/settings?tab=activity&page=1") + + assert html =~ "Login History" + end + end + + # Helper to register user and grant sudo mode + # Note: user_fixture() already creates users with TOTP enabled by default + defp register_and_log_in_user_with_sudo(%{conn: conn}) do + user = user_fixture() + + # Log in and grant sudo mode + conn = + conn + |> log_in_user(user) + |> init_test_session(%{}) + |> put_session(:user_sudo_granted_at, System.system_time(:second)) + + %{conn: conn, user: user} + end +end